Skip to main content

The GoF Hot Spots - Bridge vs Strategy

As part of my "GoF Design Patterns - The Hot Spots" posts series, this post is focused on two Design Patterns: Bridge and Strategy.

Although these two DPs are completely different, when googling them we encounter several common misconceptions that may make us think that Bridge and Strategy are similar or even same patterns.

I am not sure why but one of the popular misconceptions is that these two DPs shares the same UML diagram.

We will dig into the original GoF Design Patterns (aka: DP) description trying to figure out the real Hot Spots (aka: specific particular parts that differentiating them from each other) of these two DPs and the relationship between them.

In order to maximize the clarity of this article, I used two conventions:
  • Phrases inside [square brackets] are meant to help understanding GoF definitions
  • Italic sentences are GoF's book citations

Strategy

GoF Definition

"Define a family of algorithms [Classes that inherits from the same Abstract/Interface], encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it"

GoF Description & Hot Spots

The intend of this DP is to separate specific algorithms (Strategies) from the classes that require them, so the algorithms can vary independently and even at runtime.
Suppose a CarPool class responsible for managing a pool of cars of a rental cars company. In addition to the many features CarPoolclass implements, it must expose a way to order the cars by parameters:
by size, by price, by color etc'. And we want to be able to add more order algorithms as time goes i.e. by country, by age etc.

Internally CarPool contains a field of Strategy type (the Abstract Class/Interface) as a placeholder for the ConcreteStrategy object externally configured (injected) by the client.
When the client calls the CarPool Order() method, CarPoolforwards the request to the injected ConcreteStrategy.



As described by the Gof book:

"A Composition [CarPool Class] maintains a reference to a Compositor [OrderByColor, OrderBySize] object. Whenever a Composition reformats its text, it forwards this responsibility to its Compositor object. The client of Composition specifies which Compositor should be used by installing the Compositor it desires into the Composition."



Following a clear implementation of the Strategy DP (Thanks to DoFactory):
 
namespace DoFactory.GangOfFour.Strategy.RealWorld
{
  /// 
  /// MainApp startup class for Real-World
  /// Strategy Design Pattern.
  /// 
  class MainApp
  {
    /// 
    /// Entry point into console application.
    /// 
    static void Main()
    {
      // Two contexts following different strategies
      SortedList studentRecords = new SortedList();
 
      studentRecords.Add("Samual");
      studentRecords.Add("Jimmy");
      studentRecords.Add("Sandra");
      studentRecords.Add("Vivek");
      studentRecords.Add("Anna");
 
      studentRecords.SetSortStrategy(new QuickSort());
      studentRecords.Sort();
 
      studentRecords.SetSortStrategy(new ShellSort());
      studentRecords.Sort();
 
      studentRecords.SetSortStrategy(new MergeSort());
      studentRecords.Sort();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// 
  /// The 'Strategy' abstract class
  /// 
  abstract class SortStrategy
  {
    public abstract void Sort(List[string] list);
  }
 
  /// 
  /// A 'ConcreteStrategy' class
  /// 
  class QuickSort : SortStrategy
  {
    public override void Sort(List[string] list)
    {
      list.Sort(); // Default is Quicksort
      Console.WriteLine("QuickSorted list ");
    }
  }
 
  /// 
  /// A 'ConcreteStrategy' class
  /// 
  class ShellSort : SortStrategy
  {
    public override void Sort(List[string] list)
    {
      //list.ShellSort(); not-implemented
      Console.WriteLine("ShellSorted list ");
    }
  }
 
  /// 
  /// A 'ConcreteStrategy' class
  /// 
  class MergeSort : SortStrategy
  {
    public override void Sort(List[string] list)
    {
      //list.MergeSort(); not-implemented
      Console.WriteLine("MergeSorted list ");
    }
  }
 
  /// 
  /// The 'Context' class
  /// 
  class SortedList
  {
    private List[string] _list = new List[string]();
    private SortStrategy _sortstrategy;
 
    public void SetSortStrategy(SortStrategy sortstrategy)
    {
      this._sortstrategy = sortstrategy;
    }
 
    public void Add(string name)
    {
      _list.Add(name);
    }
 
    public void Sort()
    {
      _sortstrategy.Sort(_list);
 
      // Iterate over list and display results
      foreach (string name in _list)
      {
        Console.WriteLine(" " + name);
      }
      Console.WriteLine();
    }
  }
}

Bridge

GoF Definition

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

GoF Description & Hot Pots

"When an abstraction can have one of several possible implementations, the usual way to accommodate them is to use inheritance. An abstract class defines the interface to the abstraction, and concrete subclasses implement it in different ways.
But this approach isn't always flexible enough. Inheritance binds an implementation to the abstraction permanently, which makes it difficult to modify, extend, and reuse abstractions and implementations independently"


The intends of the Bridge DP is to enable programmers switching different concrete business logic implementors at runtime, without using inheritance.

But when we carefully read the GoF description we will notice another very important Hot Spot:

"...[using inheritance] makes client code platform-dependent. Whenever a client creates a window, it instantiates a concrete class that has a specific implementation. For example, creating an XWindow object binds the Window abstraction to the X Window implementation, which makes the client code dependent on the X Window implementation. This, in turn, makes it harder to port the client code to other platforms."

That means that an additional important goal is to completely hide the concrete implementation from the final client. This is a crucial Hot Spot that we should remind, don't worry I will explain why later in this article.

Instead of binding concrete implementors to their abstractions (Abstract Classes) we can create two separate class hierarchies (like Abstract Factory & Factory Method), one for the abstraction interfaces (the interfaces that are exposed to the final clients) and another one for the various concrete implementations, that are known only to the concrete abstractions and not to the final DP clients.



"All operations on Window subclasses are implemented in terms of abstract operations from the WindowImp interface. This decouples the window abstractions from the various platform-specific implementations.
We refer to the relationship between Window and WindowImp as a bridge, because it bridges the abstraction and its implementation, letting them vary independently."


The following is a correct implementation of the Bridge DP:

namespace BridgePattern
{
  class Application
  {
    /// 
    /// Entry point into console application.
    /// 
    static void Main()
    {
      // NOTE: The client interacts only with the Abstractions objects.
      // Internals concrete implementations are not exposed to the client.

      Graphic gr = new BlueThemeGraphicHandler();
 
      gr.LoadImages();
      gr.Paint();
      gr.RenderPixels();

      Graphic gr1 = new OrangeThemeGraphicHandler();
 
      gr1.LoadImages();
      gr1.Paint();
      gr1.RenderPixels();

      Console.ReadKey();
    }
  }
 
  /// 
  /// The 'Abstraction' class
  /// 
  class Graphic
  {
    protected Renderer implementor;
    
       // Constructor.
       // NOTE: The implementor IS NOT injected externally from the client!!
       public Graphic(){
         if(some condition){
            implementor = new GDIRenderer();
         }else{
            implementor = new XAMLRenderer();
         }
   }
  
    public abstract void LoadImages();
    public abstract void Paint();
    public abstract void RenderPixels();
  }
 
 
  class BlueThemeGraphicHandler: Graphic
  {
         public override void LoadImages()
         {
              // DO SOME MORE.
              implementor.GetImages();
         }

         public override void Paint()
         {
               // DO SOME MORE.
               implementor.Draw();
         }

         public override void RenderPixels()
         {
               // DO SOME MORE.
               implementor.Render();
         }
  }

  class OrangeThemeGraphicHandler: Graphic
  {
          public override void LoadImages()
         {
              // DO SOME MORE.
              implementor.GetImages();
         }

         public override void Paint()
         {
               // DO SOME MORE.
              implementor.Draw();
         }

         public override void RenderPixels()
         {
               // DO SOME MORE.
               implementor.Render();
         }
  }


   /// 
   /// The 'Implementor' abstract class
   /// 
   abstract Renderer
   {
       public abstract void GetImages();
       public abstract void Draw();
       public abstract void Render();
  }

  class GDIRenderer: Renderer
  {
    public override void GetImages()
    {
      Console.WriteLine("GDI Renderer - GetImages");
    }

    public override void Draw()
    {
      Console.WriteLine("GDI Renderer - Draw");
    }

    public override void Render()
    {
      Console.WriteLine("GDI Renderer  - Renderer");
    }
  }
 
  
  class XAMLRenderer :Renderer
  {
    public override void GetImages()
    {
      Console.WriteLine("XAML Renderer - GetImages");
    }

    public override void Draw()
    {
      Console.WriteLine("XAML Renderer - Draw");
    }

    public override void Render()
    {
      Console.WriteLine("XAML Renderer  - Renderer");
    }
  }
}

Misconceptions & Hot Spots

The first Hot Spot I want to highlight is that when the GoF mention the various possible implementation of the Bridge pattern they don't mention an implementation where the concrete implementation is injected from the client like in Strategy DP, on the contrary, as explained before, one of the goals of this DP is to hide form the clients the instantiation of any concrete implementation class in order to be able to "...port the client code to other platforms".

And still, after all that said, I am not sure why, but the most common implementation of the Bridge pattern (Wikipedia) consist of an abstraction that is configured externally with a concrete implementor by the client. The following is the DoFactory Bridge implementation that DOESN'T follow the GoF description :
namespace DoFactory.GangOfFour.Bridge.Structural
{
  /// 
  /// MainApp startup class for Structural
  /// Bridge Design Pattern.
  /// 
  class MainApp
  {
    /// 
    /// Entry point into console application.
    /// 
    static void Main()
    {
      Abstraction ab = new RefinedAbstraction();
 
      // NOTE: The implementation is injected by the client !!
      ab.Implementor = new ConcreteImplementorA();
      ab.Operation();
 
      // NOTE: The implementation is injected by the client !!
      ab.Implementor = new ConcreteImplementorB();
      ab.Operation();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// 
  /// The 'Abstraction' class
  /// 
  class Abstraction
  {
    protected Implementor implementor;
 
    // Property
    public Implementor Implementor
    {
      set { implementor = value; }
    }
 
    public virtual void Operation()
    {
      implementor.Operation();
    }
  }
 
  /// 
  /// The 'Implementor' abstract class
  /// 
  abstract class Implementor
  {
    public abstract void Operation();
  }
 
  /// 
  /// The 'RefinedAbstraction' class
  /// 
  class RefinedAbstraction : Abstraction
  {
    public override void Operation()
    {
      implementor.Operation();
    }
  }
 
  /// 
  /// The 'ConcreteImplementorA' class
  /// 
  class ConcreteImplementorA : Implementor
  {
    public override void Operation()
    {
      Console.WriteLine("ConcreteImplementorA Operation");
    }
  }
 
  /// 
  /// The 'ConcreteImplementorB' class
  /// 
  class ConcreteImplementorB : Implementor
  {
    public override void Operation()
    {
      Console.WriteLine("ConcreteImplementorB Operation");
    }
  }
}


This "small" deviation from the original GoF Bridge design makes one of these DPs redundant:

  1. Both of them are responsible to inject a concrete implementation into abstraction objects
  2. In both cases, final clients, after injecting a concrete implementor into the Abstraction object, interacts only with the Abstraction interface
  3. Both of them can be used to vary algorithms implementations at runtime

The second noticeable difference is that the Strategy pattern, obviously, doesn't consist of two separate classes hierarchies as the Bridge DP does.

After reading this post I hope that the highlighted Hot Spots emphasize the big differences between these two Design Patterns and their correct implementation.


Comments

The Best

Closures in C# vs JavaScript -
Same But Different

Closure in a Nutshell Closures are a Software phenomenon which exist in several languages, in which methods declared inside other methods (nested methods), capture variables declared inside the outer methods. This behavior makes captured variables available even after the outer method's scope has vanished.

The following pseudo-code demonstrates the simplest sample:
Main() //* Program starts from here { Closures(); } AgeCalculator() { int myAge = 30; return() => { //* Returns the correct answer although AgeCalculator method Scope should have ordinarily disappear return myAge++; }; } Closures() { Func ageCalculator = AgeCalculator(); //* At this point AgeCalculator scopeid cleared, but the captured values keeps to live Log(ageCalculator()); //* Result: 30 Log(ageCalculator()); //* Result: 31 } JavaScript and C# are two languages that support…

Formatting Data with IFormatProvider & ICustomFormatter

This post provides an internal overview of IFormatProvider & ICustomFormatter interfaces, and they way they are handled by .NET.

IFormatProvider is a .NET Framework Interface that should be used, by implementing its single public object GetFormat(Type) method, when there is a need to implement custom formatting of data like String and DateTime.

The public object GetFormat(Type) method simply returns an object that in turns is available to supply all available information to continue the formatting process. The Type passed in by the Framework is meant to give the implementor a way to decide which type to return back. Its like a Factory Method Design Pattern where the "formatType" is the type expected to be returned.
class MyProvider : IFormatProvider { public object GetFormat(Type formatType) { object result = null; //* Factory Method if (formatType == typeof( ICustomFormatter)) //* Some object, will be disc…

Design API for Multiple Different Clients

Today I want to talk about common design challenges related to architecture of robust APIs, designed to be consumed by multiple clients with different needs.

Our use case is the following: We need to build a N-Tier Web REST/SOAP API that is supposed to read/write data from a DB, perform some processing on that data and expose those methods to our API consumers.

In addition we have multiple different API clients each with different needs, meaning we can't just expose a rigid set of functions with a defined group of DTOs (Data Transfer Objects).
DTO vs POCO Before start diving I want to explain shortly the difference between these two controversial concepts.
DTO Objects that are designed to transfer data between edges (i.e. between processes, functions, server & clients etc'). Typically DTOs will contain only simple properties with no behavior.
POCO Objects that are designed to reflect the internal business data model. For example if you have an eCommerce platform you will…