design-patternsrefactoringobject-oriented-analysisbridge

How should a "Bridge" design pattern be implemented for more than two hierarchies?


This explains the "Bridge" pattern I'm referring to: https://refactoring.guru/design-patterns/bridge

Here's a scenario from the post above:

Say you have a geometric Shape class with a pair of subclasses: Circle and Square. You want to extend this class hierarchy to incorporate colors, so you plan to create Red and Blue shape subclasses. However, since you already have two subclasses, you’ll need to create four class combinations such as BlueCircle and RedSquare.

The problem this scenario presents:

Adding new shape types and colors to the hierarchy will grow it exponentially. For example, to add a triangle shape you’d need to introduce two subclasses, one for each color. And after that, adding a new color would require creating three subclasses, one for each shape type. The further we go, the worse it becomes.

To avoid this problem, we implement the Bridge pattern like so:

Extract the color-related code into its own class with two subclasses: Red and Blue. The Shape class then gets a reference field pointing to one of the color objects. Now the shape can delegate any color-related work to the linked color object. That reference will act as a bridge between the Shape and Color classes. From now on, adding new colors won’t require changing the shape hierarchy, and vice versa.

I understand the how and why of this implementation.

But what if we need a third hierarchy, e.g. BorderStyle (where a border style can be Straight, Wavy, or ZigZag?)

I guess we could implement a second Implementor class for BorderStyle and pass it into a Shape constructor like so:

# Blue inherits from the Color class (Implementation)
color_implementation = Blue.new()

# Wavy inherits from the BorderStyle class (Implementation)
border_style_implementation = Wavy.new()

# Triangle inherits from the Shape class, which we consider our Abstraction
shape = Triangle.new(color_implementation, border_style_implementation)

shape.draw() # prints "I'm a Blue Triangle with Wavy borders!"

So my questions are:

1.) Would the example above "work?" (Maybe it's working code, but does it introduce technical debt in some way?)

1a.) If this doesn't "work," why not?

1b.) If this does "work," is it a proper application of the Bridge pattern? Is there a better design pattern to use for managing more than 2 hierarchies/properties (was thinking maybe the Decorator pattern?)

I apologize if I left out any relevant info--this design pattern is brand new to me. Thanks for your help!


Solution

  • Yes, this works. There's nothing wrong with adding two Bridge relationships to one abstraction (beyond the complexity of juggling three different hierarchies).

    Decorator would certainly not work for this purpose, because it maintains a single hierarchy, which is known to the client. The Implementor hierarchy in a Bridge (or hierarchies in this case) are unknown to the client.

    I would make a clarification to the linked article, where it says,

    You want to extend this [shape] class hierarchy to incorporate colors

    I think this oversimplifies the motivation for a Bridge. The Implementors are not just some attributes you choose to add to your Abstraction to enhance it. Your Abstraction requires an Implementor in order to function at all. The method implementations within subclasses of Abstraction generally do little except call methods of the Implementor.

    The Abstraction represents your high-level, business API, while the Implementor represents a lower-level, primitive API. They are both abstractions, but at different levels. I don't think this is conveyed adequately by shape & color examples because shape and color seem like abstractions at the same level. Both shape and color would be known to the clients, and neither one strictly depends on the other.

    So a Bridge is applied for more specific reasons than the given example, but you certainly can have two.