Bridge & Adapter Pattern
Introduction
Developers in .NET sometimes come from scripting language environments that are not strong on Object Oriented Methodologies. OO methodologies like refactoring and using design patterns can be intimidating and the value for the developer is hard to see. What developers new to the OO world, and even more seasoned developers need to understand is that good design is not beyond scope in any project. Good habits simply must be learned that will make the developer a better designer.
In that mind set, I am submitting the first of several "how to" real world
examples, used in my professional life, on how to use patterns in a simple, easy
to follow manner. Many of us have ended up either writing or working on in-line,
scripting based code. Taking some ideologies from refactoring methodology, this
is not necessarily a bad thing. Of course, when you are first writing an
algorithm or a series of logic statements, it seems easier to start with
if....then....else
. But as you continue to expand this code to more
complex forms, this model can quickly become unmanageable. This is where
refactoring and design pattern methodology can play an important and useful part
in simplifying, enhancing and making code more useable (and understandable) to
those who practice good OO design.
This article details a real world example of how to use the bridge and adapter patterns to help you develop a method to allow decoupling of an abstraction class from it's implementation class or to allow a change of implementation from an interface. This is generally used when you have a class that might change, but you don't want those changes to affect a number of clients.
NOTE: This article is for helping developers understand how to convert their inline code to patterns better, not provide strict examples on patterns. There are more than enough strict pattern examples, but to facilitate developers and others out there who are not familiar with patterns, I have written this and other articles like this.
The adaptor and a bridge patterns are very similar. The main difference between an adaptor and a bridge pattern, is that a bridge pattern serves to decouple an abstraction class from it's implementation, and an adaptor pattern converts the interface between classes with less inheritance.
I have reworked this article, from demand from my readers, to illustrate the differences between the adaptor and a bridge patterns. We will start with the bridge pattern, and at the end of the article I will show the difference between the two.
BackGround
Using a bridge or an adapter pattern is a form of encapsulation, which allows a developer to better control the way a client code base deals with the implementation class structure.
How to use the code
We start this example with the class WorkerObject
, which we see
has one constructor and one method. If this object changes or we want to use
another object in our client code instead of WorkerObject
, we might
have to re-write or re-compile a lot of that code. To decouple this
object from the client would seem to make sense.
public class WorkerObject { public WorkerObject() { } public virtual void DoWork() { Console.WriteLine("Working..."); } } .....client code WorkerObject obj = new WorkerObject(); obj.DoWork();
So, to solve this problem, we need to create a bridge or adapter
class that will allow a client to access methods on this object, but also makes
sure changes in this object will not to break the client object's calls to the
methods, even if we change the WorkerObject
for another object.
Here we see the Bridge
class. This class can be called and the
standard implementation of WorkerObject's
method DoWork
will be executed. The Bridge
object is an example of the bridge
pattern because of it's access to it's implementation object by inherited objects,
and it's inherited relationships.
NOTE: Notice the ImplementationObject
accessor, that allows
access to the implementation object. Using this accessor in inherited classes,
we can change the WorkerObject
to an inherited class, with
different functionality, without affecting any client code, or changing the base
WorkerObject
class.
class Bridge { private WorkerObject _workerObject; public Bridge { ImplementationObject = new WorkerObject(); } protected WorkerObject ImplementationObject { get{return _workerObject;} set{_workerObject = value;} } public virtual void DoWork() { ImplementationObject.DoWork(); } }
Now lets say we wanted to use a different object other than
WorkerObject
for our implementation class. We still want to provide
a standard ImplementationObject
accessor, so we create a class
inherited from WorkerObject
, named LoaferObject
, which
overrides the DoWork
method, in effect changing the way the object
functions. We set this object as the implementation object, giving new
functionality, while still offering the same method access to the client.
public class LoaferObject : WorkerObject { public LoaferObject() { } public override void DoWork() { Console.WriteLine("Loafing..."); } } class AlternateBridge : Bridge { public AlternateBridge() { ImplementationObject = new LoaferObject(); } public virtual void DoWork() { ImplementationObject.DoWork(); } }
Now lets say we did not want to use WorkerObject
at all, but instead
wanted to house some other functionality in the DoWork
method,
such as another object or different code. We can do this easily, just by inheriting
from Bridge
and changing the DoWork
method's code
in any way we like.
class TotallyDifferntBridge : Bridge { public TotallyDifferntBridge() { ImplementationObject = null; } public virtual void DoWork() { TotallyDifferentObject diffObj = new TotallyDifferentObject(); diffObj.DoWork(); } }
Now lets see how this would affect the client code. Well we started with a
single class WorkerObject
, and a call to it's method DoWork
.
To make the client code more flexible, and to get the proper object implementation
for each client instance, we might call the Bridge
object, or it's
inherited classes from a factory, which based on some state or parameter or
client type gets the correct adapter object.
.....client code calls the factory Bridge adapter = BridgeFactory.GetAdapter(); adapter.DoWork();
Another way of using the Bridge
class to keep client code
flexible and not change basic class functionality is to simply construct the
class directly for each client instance it is needed, constructing different
polymorphic inherited instances for different needs.
.....client code Bridge adapter = new Bridge(); adapter.DoWork(); adapter = new AlternateBridge(); adapter.DoWork(); adapter = new TotallyDifferntBridge(); adapter.DoWork();
Now lets take a look at an alternate, less involved way to do the same kind
of thing, with less need for inheritance, using the adaptor pattern.
Here we see the Adaptor
class. Notice that this class has no access
to it's implementation object, hence no need for inheritance. We can provide
access to the Adaptor
class to client code, but have no need to
give different implementations of that class outward. We might use the adaptor
pattern if we foresaw changes to the implementation object that we did not want
to be seen by the client, but had no immediate need to provide a range of different
class types to encapsulate these changes. Also to be included in this consideration
of pattern implementation is that the implementation class itself would not
likely become another class altogether, where we might need to retain the old
class's functionality. Basically we might use the adaptor pattern if
changes to the implementation object could be accomplished with out the need
for backward compatibility, or polymorphic changes to the DoWork
method.
class Adaptor { private WorkerObject _workerObject; public Adaptor { _workerObject = new WorkerObject(); } public virtual void DoWork() { _workerObject.DoWork(); } }
Points of Interest
The adapter & bridge patterns offer us another way to make our code more flexible, and gives us another option for code design. Developers new to OO may have already found they have used a version of one of these patterns in their code before, or can find a myriad of uses immediately. Like many of the more commonly used patterns, these two patterns are useful in a wide variety of code situations.
This is the fifth installment in the series I am writing on real world design patterns. All examples and the bulk of this article are taken from my professional experience as an architect. The examples given are templates only, and the designer must keep in mind that they are the ones who must decide where different patterns, if any, may be best used in their code.
Deciding to perform a refactoring effort from existing code to a pattern must be weighed on the necessity and need of the code itself. Patterns are only design templates, helpers to accommodate better overall design. I might stress that making the effort to use patterns will strengthen your overall design ability, but like your basic coding skills, it is something learned and cultivated.
If this or any other in this series on design patterns is helpful or you have questions or comments please e-mail me at chris.lasater@gmail.com.
History
This is the second revision and is the fifth installment in a series.
Related Articles
Other articles in this series include: