Bridge

Introduction

Bridge pattern is a structural pattern, which is always used to decouple abstraction from its implementation.

Let’s go through the motivation in <<Design Patterns>> :

When abstraction can have one of serveral possible implementations, the usual way to accomodate them is to use inheritance. An abstract class defines the interface to the abstraction, and concrete subclasses implement it in different ways. But this approach is not always flexible enough. Inheritance binds an implementation to the abstraction permanently, which make it difficult to modify, extend, and reuse abstraction and implementations independently.

Well, Here is an example help you to understand it. Think about that you have a Sender class as an abstract class to send information to you customers, and you have several providers such as WechatSender, MailSender and so on as implementations, if we use inheritance to build the program, it may like:

non-bridge

And you want to implement a ImageSender to send images, your program structure may like: non-bridge2

Obviously, it’s not a good structure, and if you have more Sender, the program become very complex and it’s difficult to change your sender.

Definition

Decouple an abstraction from its implementation so that the two can vary independently.

UML class diagram

bridge

Participants

  • Abstraction
    • defines the abstraction’s interfaces.
    • maintains a reference to an object of type Implementor.
  • RefinedAbstraction
    • extends the interface defined by Abstraction
  • Implementor
    • defines the interface for implementation classes. This interface doesn’t have to correspond exactly to Abstraction’s interface; in fact the two interface can be quite different. Typically the Implementor interface provides only primitive operations, and Abstraction defines higher-level operations based on these primitives.
  • ConcreteImplementor
    • implements the Implementor interface and defines its conrete implementation.

Example

// Abstraction
public abstract class Sender {
	protected SenderImplementor implementor;
	
	public Sender(SenderImplementor implementor) {
		this.implementor = implementor;
	}
	
	public abstract void send();
		
}

// RefinedAbstraction
public class ImageSender extends Sender {

	private String imageName;

	public ImageSender(String imageName, SenderImplementor implementor) {
		super(implementor);
		this.imageName = imageName;
	}

	@Override
	public void send() {
		// TODO Auto-generated method stub
		System.out.println("image name: " + imageName);
		implementor.sendImage();
	}

}

// Implementor
public interface SenderImplementor {
	public void sendText();
	public void sendImage();
}

// ConcreteImplementor
public class MailSenderImp implements SenderImplementor {

	@Override
	public void sendText() {
		// TODO Auto-generated method stub

	}

	@Override
	public void sendImage() {
		// TODO Auto-generated method stub
		System.out.println("sending image by MailSender...");
	}

}

public class WechatSenderImp implements SenderImplementor {

	@Override
	public void sendText() {
		// TODO Auto-generated method stub

	}

	@Override
	public void sendImage() {
		// TODO Auto-generated method stub
		System.out.println("sending image by WechatSender...");
	}

}

// Client
public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Sender[] sender = new Sender[] {
			new ImageSender("Test1", new MailSenderImp()),
			new ImageSender("Test2", new WechatSenderImp())
		};
		
		for(Sender as : sender) {
			as.send();
		}
	}

}

Output:

image name: Test1
sending image by MailSender...
image name: Test2
sending image by WechatSender...

Usage

  • When you want to avoid a permanent binding between an abstraction and its implementation.
  • When both the abstraction and implementation should be extensible using subclasses.
  • When changes in the implemenation of an abstraction should have no impact on clients, which means that you should not have to recompile their code.

See Also

blog comments powered by Disqus