Bridge Design Pattern is used where we need to decouple
an abstraction from its implementation so that the two can vary independently
This DP decouples implementation class
and abstract class by providing a bridge structure between them.
It involves an interface which acts as a bridge which
makes the functionality of concrete classes independent from interface
implementer classes. Both types of classes can be altered structurally without
affecting each other.
Simple
example
1)
If we have a USB cable (bridge), then we
can connect any printer to any computer to start printing. It really doesn't
matter if it's a laser printer or a color printer, either Windows PC or Mac.
Because we know all the printers will allow the computers to print, makes
sense?
2)
In particular this pattern is useful in
graphic toolkits that need to run on multiple platforms. You'll see this
in AWT, where a component has a component peer which does the OS specific
operations. Also the Collections framework has examples of the bridge interface:
ArrayList and LinkedList are implementing List. And List provides common
methods to add, remove or check size.
The Bridge pattern should be used when both the class
as well as what it does vary often. The bridge pattern can also be thought of
as two layers of abstraction. When the abstractions and implementations should
not be bound at compile time, and should be independently extensible the
pattern should be used.
The bridge pattern applies when there is a need to
avoid permanent binding between an abstraction and an implementation and when
the abstraction and implementation need to vary independently. Using the bridge
pattern would leave the client code
unchanged with no need to recompile the code.
Sometimes an abstraction should have different implementations;
consider an object that handles persistence of objects over different platforms
using either relational databases or file system structures (files and folders)
An Abstraction can be implemented by an abstraction
implementation, and this implementation does not depend on any concrete
implementers of the Implementer interface. Extending the abstraction does not
affect the Implementor. Also extending the Implementor has no effect on the
Abstraction.
Example 1 . Where remote control is used
1: //Implementer
2: public interface TV {
3: public void on();
4: public void off();
5: public void tuneChannel(int channel);
6: }
And then we create two
specific implementations - one for Sony and one for Samsung
1: // Concrete Implementer
2: public class Sony implements TV {
3: @Override
4: public void off() {
5: System.out.println(" Sony Companies TV switched OFF ");
6: }
7: @Override
8: public void on() {
9: System.out.println(" Sony Companies TV switched ON ");
10: }
11: @Override
12: public void tuneChannel(int channel) {
13: System.out.println(" Sony Companies TV Tune to channel number "+channel);
14: }
15: }
These classes deal with the
specific implementations of the TV from each vendor.
1: 3) //Concrete Implementor
2: public class Samsung implements TV {
3: @Override
4: public void off() {
5: System.out.println(" Samsung Companies TV switched OFF ");
6: }
7: @Override
8: public void on() {
9: System.out.println(" Samsung Companies TV switched ON ");
10: }
11: @Override
12: public void tuneChannel(int channel) {
13: System.out.println(" Samsung Companies TV Tune to channel number "+channel);
14: }
15: }
Example
2 : Vehicle Demo
Suppose we have a Vehicle
class. We can extract out the implementation of the engine into an Engine
class. We can reference this Engine implementor in our Vehicle via an Engine
field. We'll declare Vehicle to be an abstract class. Subclasses of Vehicle
need to implement the drive() method. Notice that the Engine reference can be
changed via the setEngine() method.
1: public abstract class Vehicle {
2: Engine engine;
3: int weightInKilos;
4: public abstract void drive();
5: public void reportOnSpeed(int horsepower) {
6: int ratio = weightInKilos / horsepower;
7: if (ratio < 3) {
8: System.out.println("The vehicle have fast speed.");
9: } else if ((ratio >= 3) && (ratio < 8)) {
10: System.out.println("The vehicle have average speed.");
11: } else {
12: System.out.println("The vehicle have slow speed.");
13: }
14: }
15: public Engine getEngine() {
16: return engine;
17: }
18: public void setEngine(Engine engine) {
19: this.engine = engine;
20: }
21: }
Our implementer interface is the Engine interface,
which declares the go() method.
BigBus is a subclass of
Vehicle. It has a weight of 3000 kg. Its drive() method displays a message,
calls the engine's go() method, and then calls reportOnSpeed() with the
horsepower of the engine to report on how fast the vehicle is moving.
1: public class BigBus extends Vehicle {
2: public BigBus(Engine engine) {
3: this.weightInKilos = 3000;
4: this.engine = engine;
5: }
6: @Override
7: public void drive() {
8: System.out.println("\nThe big bus is driving");
9: int horsepower = engine.go();
10: reportOnSpeed(horsepower);
11: }
12: }
SmallCar is similar to
BigBus but is much lighter.
1: public class SmallCar extends Vehicle {
2: public SmallCar(Engine engine) {
3: this.weightInKilos = 600;
4: this.engine = engine;
5: }
6: @Override
7: public void drive() {
8: System.out.println("\nThe Small Car is driving");
9: int horsepower = engine.go();
10: reportOnSpeed(horsepower);
11: }
12: }
A BigEngine implements
Engine. BigEngine has 600 horsepower. It's go() method reports that it is
running and returns the horsepower.
1: public class BigEngine implements Engine {
2: int horsepower;
3: public BigEngine() {
4: horsepower = 350;
5: }
6: @Override
7: public int go() {
8: System.out.println("The big engine is running");
9: return horsepower;
10: }
11: }
SmallEngine is similar
to BigEngine. It has only 100 horsepower.
1: public class SmallEngine implements Engine {
2: int horsepower;
3: public SmallEngine() {
4: horsepower = 100;
5: }
6: @Override
7: public int go() {
8: System.out.println("The small engine is running");
9: return horsepower;
10: }
11: }
The BridgeDemo class demonstrates
our bridge pattern. We create a BigBus vehicle with a SmallEngine implementor. We call the vehicle's drive() method.
Next, we change the implementor to a BigEngine and once again call drive().
After this, we create a SmallCar vehicle with a SmallEngine implementor. We
call drive(). Next, we change the engine to a BigEngine and once again call
drive().
The big bus is driving
The small engine is running
The vehicle is going at a slow
speed.
The big bus is driving
The big engine is running
The vehicle is going at a slow
speed.
The Small Car is driving
The small engine is running
The vehicle is going an average
speed.
The Small Car is driving
The big engine is running
The vehicle is going at a fast speed.
Notice that we were able to change the implementor
(engine) dynamically for each vehicle. These changes did not affect the client
code in BridgeDemo. In addition, since BigBus and SmallCar were both subclasses
of the Vehicle abstraction, we were even able to point the vehicle reference to
a BigBus object and a SmallCar object and call the same drive() method for both
types of vehicles.
There you are, designing car remotes
for various types of cars. But it’s getting confusing. You have an abstract
class that’s extended to create various types of car remotes: those that just
control the car alarm, those that start the car remotely, and so on. But you need
to deal with various different car types, such as Toyota, Honda, and so on. And
to support new remotes that are planned, your abstract Remote class has to
change as needed.
As you’d expect, where there are two
things that can change, and they’re tied together, there’s a pattern that can
help out. The Bridge pattern comes to the rescue by saying that you should
separate out the Car type into its own class. The remote will contain a car
using a “has-a” relationship so that it knows what kind of car it’s dealing
with. This relationship looks like the one shown in Figure below — the “has-a” connection between the remote
and the car type is called the bridge
The inspiration here is that when
you have an abstraction that can vary,and that’s tied to an implementation that
can also vary, you should decouple the two.
Bridge design pattern in java API
Notes :
1)
One of the major drawbacks of this pattern is that, in
providing flexibility, it increases complexity. There's also possible
performance issues with the indirection of messages - the abstraction needs to
pass messages along to the implementer for the operation to get executed.
2)
Bridge design pattern can be used when both abstraction
and implementation can have different hierarchies independently and we want to
hide the implementation from the client application.
3) What is the basic problem
being solved by the Bridge pattern ?
The derivations of an
abstract class must use multiple implementations without causing an explosion
in the number of classes.
What is the intent of
Bridge pattern ?
Decouple an abstraction
from its implementation so that the two can vary independently
4)
Proxy, Decorator, Adapter, and Bridge are all
variations on "wrapping" a class. But their uses are different.
Proxy could be used when you want
to lazy-instantiate an object, or hide the fact that you're calling a remote
service, or control access to the object.
Decorator is also called
"Smart Proxy." This is used when you want to add functionality to an
object, but not by extending that object's type. This allows you to do so at
runtime.
Adapter is used when you have an
abstract interface, and you want to map that interface to another object which
has similar functional role, but a different interface.
Bridge is very similar to Adapter, but we call it Bridge when you
define both the abstract interface and the underlying implementation. I.e.
you're not adapting to some legacy or third-party code, you're the designer of
all the code but you need to be able to swap out different implementations.
5) Facade is a higher-level (read:
simpler) interface to a subsystem of one or more classes. Think of Facade as a
sort of container for other objects, as opposed to simply a wrapper.
6)
Bridge and Adapter
both point at an existing type. But the bridge will point at an abstract type,
and the adapter might point to a concrete type.
7)
The bridge will allow you to pair the implementation at
runtime, whereas the adapter usually won't.
No comments:
Post a Comment