- The Bridge pattern is used for decoupling an abstraction from its implementation so that the two can vary independently.
- Bridge is a high-level architectural patterns which is created by moving a set of abstract operations to an interface so that both the client and the service can vary independently.
- The abstraction decouples the client, the interface, and the implementation.
- A classic example of the Bridge pattern is when coding against device drivers.
- A driver is an object that independently operates a computer system or external hardware device.
- It is important to realize that the client application is the abstraction.
- Interestingly enough, each driver instance is an implementation of the Adapter pattern.
- The overall system, the application together with the drivers, represents an instance of a Bridge.
Participants
Abstraction
(ContactBase)
- It defines the abstraction's interface.
- It also maintains a reference to an object of type Implementer.
RefinedAbstraction
(FriendContact, CustomerContact)
- Extends the interface defined by Abstraction.
Implementer
(IMessageSender)
- It defines a common interface that is consumed by the Abstraction's interface.
- Typically it provides only primitive operations compare to the Abstraction that defines higher-level operations based on these primitives.
ConcreteImplementor
(SMSSender, EmailSender)
- It implements the Implementer interface and defines its concrete implementation.
var emailSender = new EmailSender();
var smsSender = new EmailSender();
var contacts = new List<ContactBase>
{
new CustomerContact(emailSender)
{
Id = 1000,
Name = "Client Name"
},
new FriendContact(smsSender)
{
Name = "John Smith"
}
};
foreach (var contact in contacts)
contact.SendMessage();
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message)
{
// Email forwarding logic...
}
}
public class SMSSender : IMessageSender
{
public void Send(string message)
{
// SMS forwarding logic...
}
}
public abstract class ContactBase
{
protected IMessageSender MessageSender { get; set; }
abstract public void SendMessage();
}
public class CustomerContact : ContactBase
{
public int Id { get; set; }
public string Name { get; set; }
public CustomerContact(IMessageSender messageSender)
{
MessageSender = messageSender;
}
public override void SendMessage() => MessageSender.Send($"Hello customer {Name} with id {Id}.");
}
public class FriendContact : ContactBase
{
public string Name { get; set; }
public FriendContact(IMessageSender messageSender)
{
MessageSender = messageSender;
}
public override void SendMessage() => MessageSender.Send($"Hello {Name}! How is it going?");
}
Rules of Thumb
- Bridge makes things work before they are designed.
- Adapter makes things work after they're designed
- Bridge is designed up-front to let the abstraction and the implementation vary independently.
- Adapter is retrofitted to make unrelated classes work together.
- State, Strategy, Bridge (and to some degree Adapter) have similar solution structures.
- They all share elements of the "handle/body" idiom.\
- They differ in intent - that is, they solve different problems.
- The structure of State and Bridge are identical (except that Bridge admits hierarchies of envelope classes, whereas State allows only one).
- The two patterns use the same structure to solve different problems: State allows an object's behavior to change along with its state, while Bridge's intent is to decouple an abstraction from its implementation so that the two can vary independently.
- If interface classes delegate the creation of their implementation classes (instead of creating/coupling themselves directly), then the design usually uses the Abstract Factory pattern to create the implementation objects.