Question

The bridge pattern is defined as 'The Bridge pattern decouples an abstraction from its implementation, so that the two can vary independently'. I get that abstraction here doesn't mean abstract class / interface, but its not clear as to what abstraction actually means. But some sources online imply this.

https://sourcemaking.com/design_patterns/bridge describes it as:

"Hardening of the software arteries" has occurred by using subclassing of an abstract base class to provide alternative implementations. This locks in compile-time binding between interface and implementation. The abstraction and implementation cannot be independently extended or composed.

It seems to suggest that an abstract class / interface should be decoupled from its implementations, which sounds odd. How else would you provide alternative implementations ? My understanding of bridge pattern is that it is a way to organise some functionality that varies in multiple independent dimensions, into multiple hierarchies for each dimension, instead of having an exponential number of variants. It also seems like a consequence of the two principles: (i) Code against an interface instead of concrete implementations and (ii) Encapsulate what varies.

Can someone please explain what the definition of bridge pattern as described in the GoF book means ? Am I missing something here, or is it just a poorly worded description ?

Was it helpful?

Solution

According to GoF, the bridge pattern has the following intent:

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

Indeed, you got it right, it's not about abstraction in the sense of OOP techniques such as abstract classes. It's about an abstraction in the design, i.e. a broader generalization or a higher level concept without unnecessary details. And it uses composition over inheritance for the implementation.

It's a relatively complex pattern, that is not used so much. Let's look at the GoF example in the GUI domain, since it's straightforward:

  • We can imagine the concept of Window in a GUI with two methods, drawText() and drawRectangle().
  • Now imagine a window implementation WindowImp, with two methods, drawText() and drawLine().
  • Each Window is associated to a WindowImp (runtime object composition, so like with the strategy, you can chose at runtime the most suitable implementation). It simply forwards drawText() to the implementation, but converts a call to a more abstract drawRectangle() into four drawLine().

We have just decoupled an abstraction from its implementation. Interestingly, we have a design abstraction mapped to a concrete OOP class, and we have a design implementation that is mapped to an abstract OOP class. This makes this pattern confusing and difficult to understand.

All this appears unnecessarily complex. But now, let's try to let the two evolve independently:

  • The WindowImp can be specialized into a MSWindowsImp, an OSXWindowsImp and a ConsoleWindowsImp (I've adapted somewhat GoF example). So here, the implementation can evolve based on the technology platform, which provides the concrete methods that call the corresponding OS functions.
  • But the abstraction can be specialized using a completely different angle, for example into WarningWindow and a ImageWindow. These specializations may add extra operations that are based on the abstraction's operation.

The advantage is the separation of concerns and maintenance requirements. In this example, you have in the 3 abstractions and 4 implementation classes, with a clearly delited set of duties (e.g. application needs vs OS needs). Without the bridge, you'd have either a combinatorial explosion with at least 9 classes and each class would have to take care of both application and OS concerns, so you'd risk to have redundant maintenance (e.g. a change in the OS API would lead to 3 classes being updated without the bridge instead of just one with the bridge).

OTHER TIPS

It seems to me, that the pattern term XXX_Implementation is poorly chosen, or at least unfortunate. It has nothing to do with the "implements/inherits" relation between classes.

The pattern example describes an abstraction ThreadScheduler, which needs different implementations for different strategies (Preemptive/TimeSliced) and different execution environments (Unix/Windows/JVM). The problem is, that solving this with two layers of inheritance leads to multiplicative growth in the number of classes. The pattern solves this by defining an abstraction for the needed resources of the execution environment (ThreadScheduler_Implementation). A ThreadScheduler depends on a ThreadScheduler_Implementation to use environment resources. Thus ThreadScheduler strategies can be implemented once, agnostic of their execution environment.

Perhaps a better name for ThreadScheduler_Implementation would be SchedulingResourcesProvider. Pegging it to the same level of abstraction as ThreadScheduler with a generic _Implementation addendum doesn't seem very descriptive.

EDIT: This description of the pattern seems to make more sense. In its naming style, the ThreadScheduler_Implementation could be called SchedulingImplementor.

Licensed under: CC-BY-SA with attribution
scroll top