Menu Close

Object Oriented Design Pattern: Bridge

paladin and warrior

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.

Bridge design pattern class diagram

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:

Class diagram for Character, Mage and Warrior

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.

Class diagram with an extra race

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 bridge design pattern implemented

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

Related Posts