|
|
The "Road to the Bridge"This is a story of how we get from patterns like the Interface pattern to Factory, Strategy, Proxy, Adapter, and finally to the Bridge Design Pattern. This is an exploration of how to swap implementations of objects within our software architectures. The problemWe want to be flexible in our architecture. We want to be able to swap implementations of objects/classes easily.
|
| IAbstractProductFactory | |
| IProduct1 | |
| IProduct2 | |
| IProduct3 |

IAbstractProductFactory f = new ProductFactoryVersionA() // choice is made at compile time, via factory method (run time) via strategy (runtime) IProduct1 p1 = f.CreateProduct1() IProduct2 p2 = f.CreateProduct2() IProduct3 p3 = f.CreateProduct3()
All products p1, p2, p3 are in the above example A versions, and compatible with each other.
My further thoughts on Abstract Factory here.
![]()
Rather than instantiate A or B and refer to them directly (albiet via a flexible interface variable), another approach is to refer to the same object all the time and hide the switching behind that object.

Now, because what is behind the intermediate object is hidden (and rightly so), you no longer need to program to the Strategy interface.

If you want to still program to an interface (good idea) then program to the Intermediary interface. If you want to run free and wild, program to the intermediary object api.
Variants are as follows:

Responsibility of the client to know the API of the strategy. So still programming to the strategy interface. You have to since the intermediary has no methods, or rather, has no methods specifically related to accessing the A & B classes.
o = new Intermediary() o.SetStrategy(new A()) // done in setup somewhere, or via a factory or via dependency injection framework
o.impl.DoSomething()

o = new Intermediary() o.SetStrategy(new A()) // done in setup somewhere, or via a factory or via dependency injection framework
o.DoSomething()
later you can switch the strategy without the client code caring.
o.SetStrategy(new B()) o.DoSomething() // different behaviour or different implementation occurs
If your implementation has a slightly different API than the one your client code wants to use, then you can adapt it at the same time as you are strategizing...

If your only have the same methods in your intermediary object as you have in your implementation, then you can have the intermediary inherit from the abstract implementation interface. This turns the pattern into proxy, and lets you optionally, program to the Strategy interface again.

The proxy, whilst inheriting from Strategy, can also implement extra
methods, though this is diverging a little from the intent of Proxy.
An alternative to inheritance, the proxy can implement the interface of
the Strategy class, and get some similar polymorphic substitutability benefits.
![]()
This is still a variant on accessing different behaviour via an intermediary.
Bridge is just strategy with a oversized lhs context.
Same as strategy except there is
| Massive subclassing going on on the lhs (the 'context' side). | |
| The nature of the lhs methods are more compositional, adaptive and far reaching (not just a simply strategy delegation) |

The reason is that you are wanting lots of methods and lots of functionality, lots of classes. E.g. you want to have a GUI or DB subsystem, not just a single strategy.
Typically rhs (implementation/driver) calls are more primitive, and one lhs method will call the rhs. many times. e.g. see the DoTalk() method, above.
The lhs methods can be diverse, comprising
| lhs method simply calls rhs method. Method names can change or be the same. Simple delegation with no extra work. | |
| lhs methods more complex and adapt and do extra lines of code as needed | |
| Lots of logic in the lhs methods and may have associated helper classes. But in the end they call stuff on the intermediary api. |
Client is insulated from changes. Should not talk talk to implementation, even if it is the abstract implementation interface because the abs impl. may change. If the abstract implementation interface does change then this affects only the Intermediary but not the client code. Client code should thus only talk to intermediary.
Similarly, if you change the Intermediary API, then only the client is affected - the r.h.s. (the abstract implementation interface and concrete implementations) are not affected.
In this sense the lhs and rhs can vary independently. Ok - so there are repercussions when things vary - but they are limited, as discussed above.
You could simplify Bridge and have the client code talk directly to the rhs. abstract implementation interface. You would be reverting to where we started on this "road to Bridge". Nothing wrong with that - but you would lose the 'insulation against change' that Bridge gets you. And with Bridge the lhs can have lots of complex logic and the rhs implementations need only implement the more primitive operations. That is a big win.
Summary of the ways of coupling your components
| Technique | Meta-Pattern | Pattern | Description | |
| To implementation A or B directly | Program to Interface | Interface | Interface, compile time choice | Alternative implementations of an interface. Instantiate one or the other implementation of that interface. The code that uses the object is unaware of which object it is using. "Program to an interface" |
| Interface, conditional code | Same solution as above, except choose particular implementation dynamically at runtime using a flag. | |||
| Factory | Factory Method | conditional code | ||
| registry | ||||
| polymorphic factory method | ||||
| Abstract Factory | abstract factory - polymorphic | |||
| conditional code | ||||
| class registry | ||||
| To implementation A or B via intermediary | Indirection | Dot notation drilling | methodless proxy using demeter referencing | |
| Strategy | strategy - may be extra methods not related to the strategising | |||
| Proxy | proxy, all methods mapped (demeter is happy). inherit | |||
| Adapter | adapted proxy-like strategy. different method names sometimes | |||
| Bridge | rhs - methods usually more primitive. Only talk to
abs. impl.
lhs - all adapted & thus changeable. can build hierarchies |
The presentation of the patterns form a story of simple to complex.
And its a story of two broadly different techniques,
| Getting to the implementation A or B directly | |
| Getting to the implementation A or B via an intermediary object |
Adapter is closer to Bridge in that the adaptation on the lhs. (the context) can be not just a renaming and mapping of methods, but extra logic and whatever it takes to make the adapation work. So the lhs. is closer to the free wheeling compositional lhs of Bridge Pattern. By compositional I mean that a single lhs. method can comprise of complex code and multiple calls to the rhs. methods. In Bridge the lhs methods can even call on other methods in the same lhs, whereas in Adapter this is not really the intent.
Dependency injection. Inject a context object or wire up dependent objects. Allows you to program normally. Allows different implementations to be injected in.
Amongst other things, a Microkernel style architecture allows alternative plugins (services) to fulfil the implementation.
| Maybe think of it as service A or B. | |
| Or plugin A or B. |
There seem to be three types of MicroKernel:
Maybe one or more of the above three descriptions of a Microkernel is not actually a microkernel - I am just learning about this stuff. But I have seen references that suggest my analysis is correct. E.g. The Castle IOC framework for .NET calls itself a microkernel.
I have made a broad distinction between accessing implementations A or B either directly or via an intermediary. Thinking about it some more, when you do access A or B directly, you do so via an an intermediary variable declared of type abstract/interface. This is when you are being good and 'programming to interfaces'.
Thus you could argue that even even when you are accessing an object (implementation A or B) directly, you are in fact still acting through an intermediary - the interface variable. !
![]()
Comments? Please email me.