Diagram of the bridge pattern
The Bridge design pattern is used to separate the implementation from the abstract interface presented to the caller. This is also colloquially known as the "pimpl" pattern, since the abstract interface forwards all method calls to an underlying implementation via a "pointer to the
implementation" or "pimpl".
The Bridge pattern separates the abstraction from the implementation so that both abstraction and, more importantly, implementation can vary without affecting the other side of the bridge. The Bridge pattern is not limited to a single class with one or more implementations. The abstract side can have a hierarchy of classes inheriting from a base abstraction. The implementation side can also have a parallel hierarchy of inherited classes.
The key idea is the caller is presented with an abstract class that knows how to create a specific implementation based on input from the caller. The definition of the implementation is completely hidden from the caller, who sees only the abstract class.
That abstract class must be a class since it must contain some kind of reference to the underlying implementation.
How to Use
In this example, the Logger class is what the program uses for all logging. The program logs information at various levels of granularity to some output. The program doesn't care where the logged output ends up.
The Logger abstract class can make use of three different implementations of loggers: File, Console, Null. Each of these loggers implements the ILogger interface so the Logger object doesn't have to care about the implementation details. And the user of the Logger class has no idea of how the Logger class does what it does, only that it provides the same methods regardless of the actual implementation.
In this way, the Logger class is the bridge between the program and the logger implementations.
Note: The Bridge_NullLogger class is an example of the Null Object pattern.
An alternative implementation would use a class factory method to take the LoggerType and optional parameter as arguments and return an actual implementation represented by the ILogger class. This eliminates the need for a bridge class altogether and completely hides any details of the implementation. In other words, the interface itself is the "bridge".
Basically, the use of an interface and a class factory can hide any implementation and it works in any programming language that supports the idea of a base class or interface.
C++
void Bridge_Exercise()
{
std::cout << std::endl;
std::cout << "Bridge Exercise" << std::endl;
{
Logger logger("Bridge.log");
std::cout << " Example of writing to a log file..." << std::endl;
}
{
Logger logger(Logger::LoggerTypes::ToConsole);
std::cout << " Example of writing to the console..." << std::endl;
}
{
Logger logger(Logger::LoggerTypes::ToNull);
std::cout << " Example of writing to a Null object (no output)..."
<< std::endl;
}
std::cout << " Done." << std::endl;
}
static void _Bridge_Exercise_Demonstrate_Logging(ILogger *logger, const char *loggerType)
Helper function to show an example of writing to a logger.
C#
public void Run()
{
Console.WriteLine();
Console.WriteLine("Bridge Exercise");
using (Logger logger = new Logger(Logger.LoggerTypes.ToFile, "Bridge.log"))
{
Console.WriteLine(" Example of writing to a log file...");
}
using (Logger logger = new Logger(Logger.LoggerTypes.ToConsole))
{
Console.WriteLine(" Example of writing to the console...");
}
using (Logger logger = new Logger(Logger.LoggerTypes.ToNull))
{
Console.WriteLine(" Example of writing to a Null object (no output)...");
}
Console.WriteLine(" Done.");
}
Python
def Bridge_Exercise():
print()
print("Bridge Exercise")
with Logger(Logger.LoggerTypes.ToFile, "Bridge.log") as logger:
print(" Example of writing to a log file...")
with Logger(Logger.LoggerTypes.ToConsole) as logger:
print(" Example of writing to the console...")
with Logger(Logger.LoggerTypes.ToNull) as logger:
print(" Example of writing to a Null object (no output)...")
print(" Done.")
C
void Bridge_Exercise(void)
{
printf("\nBridge_Exercise\n");
{
if (fileLogger != NULL)
{
printf(" Example of writing to a log file...\n");
}
else
{
printf(" Error! Failed to create a file logger\n");
}
}
{
if (consoleLogger != NULL)
{
printf(" Example of writing to the console...\n");
}
else
{
printf(" Error! Failed to create a console logger\n");
}
}
{
if (nullLogger != NULL)
{
printf(" Example of writing to a Null object (no output)...\n");
}
else
{
printf(" Error! Failed to create a console logger\n");
}
}
printf(" Done.\n");
}
ILogger * CreateLogger(LoggerTypes loggerType, const char *filename)
Return an interface for the specified logger.
void DestroyLogger(ILogger *logger)
Release any resources associated with the given logger. The given ILogger instance is no longer valid...
@ LoggerType_ToFile
Log to a file. One additional parameter: the name of the file to log to.
@ LoggerType_ToNull
Log to nowhere, that is, throw out all logging. No additional parameters.
@ LoggerType_ToConsole
Log to the console. No additional parameters.
Rust
(Apologies. Doxygen does not understand Rust syntax and therefore cannot colorize the code.)
pub fn bridge_exercise() -> Result<(), String> {
println!("");
println!("Bridge Exercise");
{
let mut logger = create_logger(LoggerType::ToFile, "bridge.log");
println!(" Example of writing to a log file...");
_bridge_exercise_demonstrate_logging(&mut logger, "file");
}
{
let mut logger = create_logger(LoggerType::ToConsole, "");
println!(" Example of writing to the console...");
_bridge_exercise_demonstrate_logging(&mut logger, "console");
}
{
let mut logger = create_logger(LoggerType::ToNull, "");
println!(" Example of writing to a Null object (no output)...");
_bridge_exercise_demonstrate_logging(&mut logger, "null");
}
println!(" Done.");
Ok(())
}
See Also