I want to learn how/when/why to use the GoF Design Patterns. These last days are dedicated to the Bridge Pattern, which means:
Decouple an abstraction from its implementation so that the two can vary independently.
Everyone has a different understanding of this vague description, but if I well-understood the global point, it cuts an abstraction into multiple independant implementations which relies on each other, in a chain.
On this premise, let's try to implement a four-arrow remote control. First, let's define what's awaited with interfaces:
public interface ITopBottomButtonsControl
{
void TopButtonPressed();
void BottomButtonPressed();
}
public interface ILeftRightButtonsControl
{
void LeftButtonPressed();
void RightButtonPressed();
}
public interface IRemoteControl :
ILeftRightButtonsControl,
ITopBottomButtonsControl
{ }
Here is an example of the BP implementation:
public class ChannelControl : ILeftRightButtonsControl
{
public void LeftButtonPressed() => Console.WriteLine("Previous channel");
public void RightButtonPressed() => Console.WriteLine("Next channel");
}
public class ChapterControl : ILeftRightButtonsControl
{
public void LeftButtonPressed() => Console.WriteLine("Previous chapter");
public void RightButtonPressed() => Console.WriteLine("Next chapter");
}
public abstract class BPRemoteControl : IRemoteControl
{
ILeftRightButtonsControl LeftRightControl { get; }
public BPRemoteControl(ILeftRightButtonsControl leftRight)
{
LeftRightControl = leftRight;
}
public void LeftButtonPressed() => LeftRightControl.LeftButtonPressed();
public void RightButtonPressed() => LeftRightControl.RightButtonPressed();
public abstract void TopButtonPressed();
public abstract void BottomButtonPressed();
}
public class SpeedControl : BPRemoteControl
{
public SpeedControl(ILeftRightButtonsControl leftRight) : base(leftRight) { }
public override void BottomButtonPressed() => Console.WriteLine("Slow down");
public override void TopButtonPressed() => Console.WriteLine("Speed up");
}
public class VolumeControl : BPRemoteControl
{
public VolumeControl(ILeftRightButtonsControl leftRight) : base(leftRight) { }
public override void BottomButtonPressed() => Console.WriteLine("Volume down");
public override void TopButtonPressed() => Console.WriteLine("Volume up");
}
The fact is that, when I implemented this to test the pattern, it made me think back the Entity Component System. So, I tried to implement the same application, but this time with an ECS approach.
The classes ChannelControl and ChapterControl remains the same, but the rest is different:
public class SpeedControl : ITopBottomButtonsControl
{
public void BottomButtonPressed() => Console.Write("Slow down");
public void TopButtonPressed() => Console.WriteLine("Speed up");
}
public class VolumeControl : ITopBottomButtonsControl
{
public void BottomButtonPressed() => Console.WriteLine("Volume down");
public void TopButtonPressed() => Console.WriteLine("Volume up");
}
public class ECSRemoteController : IRemoteControl
{
private ITopBottomButtonsControl TopBottomButtonsComponent { get; }
private ILeftRightButtonsControl LeftRightButtonsComponent { get; }
public ECSRemoteController(ITopBottomButtonsControl topBottom, ILeftRightButtonsControl leftRight)
{
TopBottomButtonsComponent = topBottom;
LeftRightButtonsComponent = leftRight;
}
public void TopButtonPressed() => TopBottomButtonsComponent.TopButtonPressed();
public void BottomButtonPressed() => TopBottomButtonsComponent.BottomButtonPressed();
public void LeftButtonPressed() => LeftRightButtonsComponent.LeftButtonPressed();
public void RightButtonPressed() => LeftRightButtonsComponent.RightButtonPressed();
}
Finally, these two solutions seems to be the same, with the following difference:
The Bridge Pattern is vertically-organized:
S(a -> b -> c)
The Entity-Component-System is horizontally-organized:S(a, b, c)
Is there really a difference between these two solutions? When should one be prefered to the other?
HOW TO TEST: separate the two solutions into two folders BridgePattern and EntityComponentPattern, and fill the console application with the following code:
static void Main(string[] args)
{
BridgePattern();
Console.WriteLine();
EntityComponentSystem();
Console.ReadLine();
}
static void BridgePattern()
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("BridgePattern version");
Console.WriteLine("----------");
IRemoteControl TVRemoteControl = new BridgePattern.VolumeControl(new BridgePattern.ChannelControl());
IRemoteControl DVDRemoteControl = new BridgePattern.SpeedControl(new BridgePattern.ChapterControl());
Console.WriteLine($"1. {nameof(TVRemoteControl)}:");
UseRemoteControl(TVRemoteControl);
Console.WriteLine($"2. {nameof(DVDRemoteControl)}:");
UseRemoteControl(DVDRemoteControl);
}
static void EntityComponentSystem()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("EntityComponentSystem version");
Console.WriteLine("----------");
IRemoteControl TVRemoteControl = new EntityComponentSystem.ECSRemoteController(new EntityComponentSystem.VolumeControl(), new EntityComponentSystem.ChannelControl());
IRemoteControl DVDRemoteControl = new EntityComponentSystem.ECSRemoteController(new EntityComponentSystem.SpeedControl(), new EntityComponentSystem.ChapterControl());
Console.WriteLine($"1. {nameof(TVRemoteControl)}:");
UseRemoteControl(TVRemoteControl);
Console.WriteLine($"2. {nameof(DVDRemoteControl)}:");
UseRemoteControl(DVDRemoteControl);
}
static void UseRemoteControl(IRemoteControl remoteControl)
{
remoteControl.LeftButtonPressed();
remoteControl.RightButtonPressed();
remoteControl.TopButtonPressed();
remoteControl.BottomButtonPressed();
}