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();
}
-
5\$\begingroup\$ I find it's very cool that you've created this code with volume and remote controls rather than with meaningless foos and bars ;-) \$\endgroup\$t3chb0t– t3chb0t2018年11月08日 11:49:30 +00:00Commented Nov 8, 2018 at 11:49
-
\$\begingroup\$ I for some reason cannot post an answer (CR throws errors and CAPTCHA on me), so, short answer is: those two sub-interfaces appear to be independent (you don't need the other for implementation), therefore I would use ECS. BP is suitable e.g. for IList extending ICollection (IList without Count from ICollection would be nonsense and you need access to internal data - array of elements and size). \$\endgroup\$user52292– user522922018年11月09日 09:24:33 +00:00Commented Nov 9, 2018 at 9:24
-
1\$\begingroup\$ So, BP is when a step rely on an other one, and ECS is when interfaces are autonomous? PS: if you want, I can create an answer for you, you'll edit it and I'll accept \$\endgroup\$Maxime Recuerda– Maxime Recuerda2018年11月09日 09:47:55 +00:00Commented Nov 9, 2018 at 9:47
-
\$\begingroup\$ @MaximeRecuerda: Let us wait for somebody with better understanding of the theory. I am more practical than theoretical guy, so, your ECS example appears more appropriate to me, because the two interfaces are independent. I am not so sure how BP is actually specified and used, IList/ICollection was just a counter example where the other approach would be appropriate (because you simply cannot use ECS for it). \$\endgroup\$user52292– user522922018年11月09日 12:23:01 +00:00Commented Nov 9, 2018 at 12:23
1 Answer 1
Hybrid Pattern
The bridge pattern you have implemented is a combination of the composite pattern and inheritance. Why did you decide to let your ILeftRightButtonsControl
implementation use composition and ITopBottomButtonsControl
use inheritance?
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(); }
Bridge Pattern
A useful implementation of the bridge pattern would be if ChannelControl
, ChapterControl
were third-party classes from let's say OtherDomain
that do not adhere to your interfaces. If these other classes would have other interfaces, you might also consider the adapter pattern instead.
You would then create a bridge for OtherDomain.ChannelControl
.
public class ChannelControlBridge : ILeftRightButtonsControl
{
private OtherDomain.ChannelControl impl;
public ChannelControlBridge (OtherDomain.ChannelControl impl) {
this.impl = impl;
}
// the name of the operations happens to be the same in their domain
// but this is not a requirement
public void LeftButtonPressed() => impl.LeftButtonPressed();
public void RightButtonPressed() => impl.RightButtonPressed();
}
And a bridge for OtherDomain.ChapterControl
.
public class ChapterControlBridge : ILeftRightButtonsControl
{
private OtherDomain.ChapterControl impl;
public ChapterControlBridge (OtherDomain.ChapterControl impl) {
this.impl = impl;
}
// the name of the operations happens to be the same in their domain
// but this is not a requirement
public void LeftButtonPressed() => impl.LeftButtonPressed();
public void RightButtonPressed() => impl.RightButtonPressed();
}
Adapter Pattern
The adapter pattern could be implemented if ChannelControl
, ChapterControl
were third-party classes from let's say OtherDomain
that do not adhere to your interfaces, but use their own interfaces instead.
You would then create and adapter for OtherDomain.IChannelControl
.
public class ChannelControlAdapter : ILeftRightButtonsControl
{
private OtherDomain.IChannelControl impl;
public ChannelControlAdapter (OtherDomain.IChannelControl impl) {
this.impl = impl;
}
// the name of the operations happens to be the same in their domain
// but this is not a requirement
public void LeftButtonPressed() => impl.LeftButtonPressed();
public void RightButtonPressed() => impl.RightButtonPressed();
}
And an adapter for OtherDomain.IChapterControl
.
public class ChapterControlAdapter : ILeftRightButtonsControl
{
private OtherDomain.IChapterControl impl;
public ChapterControlAdapter (OtherDomain.IChapterControl impl) {
this.impl = impl;
}
// the name of the operations happens to be the same in their domain
// but this is not a requirement
public void LeftButtonPressed() => impl.LeftButtonPressed();
public void RightButtonPressed() => impl.RightButtonPressed();
}
Composite Pattern
Your ECS is a perfect example of the composite pattern by forwarding the operations to your inner components.
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(); }
-
2\$\begingroup\$ This is the kind of answer that everyone would receive. Clear, examples, explanation, thank you so much. \$\endgroup\$Maxime Recuerda– Maxime Recuerda2019年05月27日 07:17:27 +00:00Commented May 27, 2019 at 7:17
Explore related questions
See similar questions with these tags.