Implement the Bridge pattern in Java by separating the abstraction your clients use from the implementation family that actually does the work.
Bridge: A pattern that separates an abstraction from its implementation so both sides can change independently.
Bridge works well in Java when subclassing would otherwise combine two separate dimensions of variation. If a team starts inventing types such as UrgentEmailNotification, UrgentSmsNotification, DigestEmailNotification, and DigestSmsNotification, the design is asking for a bridge.
The abstraction side models the concept the client uses:
1public abstract class Notification {
2 protected final DeliveryChannel channel;
3
4 protected Notification(DeliveryChannel channel) {
5 this.channel = channel;
6 }
7
8 public abstract void send(String recipient, String message);
9}
The implementation side models how the work is carried out:
1public interface DeliveryChannel {
2 void deliver(String recipient, String payload);
3}
1public final class EmailChannel implements DeliveryChannel {
2 @Override
3 public void deliver(String recipient, String payload) {
4 System.out.println("Email to " + recipient + ": " + payload);
5 }
6}
7
8public final class SmsChannel implements DeliveryChannel {
9 @Override
10 public void deliver(String recipient, String payload) {
11 System.out.println("SMS to " + recipient + ": " + payload);
12 }
13}
14
15public final class UrgentNotification extends Notification {
16 public UrgentNotification(DeliveryChannel channel) {
17 super(channel);
18 }
19
20 @Override
21 public void send(String recipient, String message) {
22 channel.deliver(recipient, "[URGENT] " + message);
23 }
24}
Now the abstraction can vary:
And the implementation can vary:
Without a subclass for every combination.
Bridge is a composition-first pattern. The abstraction owns a reference to the implementation interface. That keeps the two axes separate and testable.
Bridge is strong when the design has two real dimensions. If there is only one, the extra split is probably overhead.