Table of Contents
Explanation
The Bridge design pattern is a structural pattern that helps you to make complex objects easier to maintain. It is a pattern that helps to separate abstraction from implementation. If you want to construct a complex object, the object which you want to create is called the abstraction, “the what”. This object holds references to the implementation classes (the “how” classes) that actually build this complex object.
Use cases:
- You want to separate abstraction from implementation. This allows, for instance, switching the implementation at runtime.
- You want to be able to work on abstraction and implementation independently.
Pitfalls:
- Implementors might not be compatible with each-other
- More abstract code that might be harder to follow.
Example
Characters for an RPG game.
Suppose we want to create an RPG (Role Playing Game) with character classes. For the base functionality of the Character class, we define an abstract class with methods like: Move(), Action() and Jump(). Suppose we work together with two other programmers who will each implement their own characters class while using the Abstract Character class as the parent:
Changes in the game
Suppose that the Mage and Warrior in the example are Human characters. Now suppose we made a decision that there should be an extra Reptilian “race” which also has a Mage and Warrior character type. The next class structure is not completely unthinkable if the project stays small, but at the meantime looks a little cumbersome as well.
Now imagine we would add more character classes beside Mage and Warrior, such as: Paladin, Rogue and Bard and more races such as: Elf, Dwarf and Orc. Furthermore, we would have a wild growth of classes if we continued in the same fashion.
The solution
The AbstractCharacter class is the abstraction, with Human, Orc and Elf as RefinedAbstractions, which represent different playable races in the game. The CharacterClass is the parent class for an implementation of a character, which in this game is either a Paladin, Warrior or Mage. Because the CharacterClass is now a component of the AbstractCharacter class, which is called the composition class. With this setup, development for races and classes can be done separately. Notice that the CharacterClass should also be an abstract class.
C# Example
// AbstractCharacter.cs
namespace DesignPatterns
{
public abstract class AbstractCharacter
{
protected int hitpoints;
protected int speed;
public CharacterClass? CharacterClass { get; set; }
abstract public void Move();
abstract public void Action();
abstract public void Jump();
public override string ToString()
{
return $"I am: {GetType().Name} and {(CharacterClass != null ? CharacterClass.ToString() : "")}\n";
}
}
public class Human : AbstractCharacter
{
public override void Action() { }
public override void Jump() { }
public override void Move() { }
}
public class Orc : AbstractCharacter
{
public override void Action() { }
public override void Jump() { }
public override void Move() { }
}
public class Elf : AbstractCharacter
{
public override void Action() { }
public override void Jump() { }
public override void Move() { }
}
}
C#// CharacterClass.cs
namespace DesignPatterns
{
public abstract class CharacterClass
{
protected List<string> weaponTypes;
protected bool canUseMagic;
public override string ToString()
{
return $"{GetType().Name}";
}
}
public class Paladin : CharacterClass
{
}
public class Warrior : CharacterClass
{
}
public class Mage : CharacterClass
{
}
}
C#// Program.cs
using DesignPatterns;
class Program
{
static void Main(string[] args)
{
Human humanPaladin = new Human();
humanPaladin.CharacterClass = new Paladin();
Console.Write(humanPaladin.ToString());
Elf elfWarrior = new Elf();
elfWarrior.CharacterClass = new Warrior();
Console.Write(elfWarrior.ToString());
}
}
C#Output:
I am: Human and Paladin
I am: Elf and Warrior
Conclusion
With the Bridge design pattern we can separate abstraction from implementation. The overridden methods such as Move(), Action() and Jump() can be implemented either by the Character or via the Character to the CharacterClass component. Or maybe the jumping power can have a default for a specific race and a multiplier for a specific class. Either way, the bridge design pattern makes sure that you won’t have to create an enormous amount of classes to implement all combinations of classes and races. Meanwhile, this pattern makes it easier to develop (extend abstractions) independently.
References
Freeman, E., Bates, B., & Sierra, K. (2004). Head first design patterns.
Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software.
Wikipedia contributors. (2024, 10 september). Software design pattern. Wikipedia. https://en.wikipedia.org/wiki/Software_design_pattern