Understanding and Implementing Bridge Pattern in C#

Let us discuss “Bridge Pattern” in this article. When we think about bridge pattern the first thing that come to our mind is “it is some kind of bridge between two different implementations”. But this is not true. The above mentioned This is the task of Adapter pattern which is discussed here. But to talk about bridge, this pattern is designed specifically to let the abstraction and the implementation vary independently.

Background

GoF defines Bridge Pattern as “Decouple an abstraction from its implementation so that the two can vary independently.”

The best example for where this pattern can be used is the use of plugins or driver. If we have an application that can take use of any specific driver then we can use bridge pattern. A classic example of a scenario where this pattern could be used in a Video Configuration selection screen of a game like “Half-life”. This screen gives the user an option to run the game in OpenGL, Direct3D or software mode. When the user makes a selection then we can use a concrete implementation specific to the selection and not effect the abstraction at all.

Let us visualize the bridge pattern now.

bridge

Now lets us see what each of these components in this class diagram represents.

  • Implementor: This interface provides preliminary operations that the all the plugins should provide.
  • ConcreteImplementor: These are the actual classes that implements the Implementor interface. These classes will be used selectively like plugins.
  • Abstraction: This class provides an abstraction to the clients. The clients will continue to use this abstraction without even caring about what actual plugin i.e. ConcreteImplementor is being selected internally.
  • RefinedAbstraction: This class will implement the actual functionality if Abstraction class. Selecting the appropriate ConcreteImplementor is the responsibility of this class.

 

Using the Code

let us now try to implement a small example using bridge pattern. Let us think of an imaginary smart TV that gives the user a possibility of playing local cable TV, a local satellite TV or an IPTV source. Here the user can request TV guide from the selected source. He can also choose to play the selected TV source.

So from the TV’s perspective the abstraction for getting the guide and play the TV. The actual implementation will differ for each source. So let us go ahead and first define the abstraction that each TV source will provide i.e. The Implementor from the above diagram.

interface IVideoSource
{
	string GetTvGuide();
	string PlayVideo();
}

Let us now define the ConcreteImplementors for each TV source.

class LocalCabelTv : IVideoSource
{
	const string SOURCE_NAME = "Local Cabel TV";

	string IVideoSource.GetTvGuide()
	{
		return string.Format("Getting TV guide from - {0}", SOURCE_NAME);
	}

	string IVideoSource.PlayVideo()
	{
		return string.Format("Playing - {0}", SOURCE_NAME);
	}
}

class LocalDishTv : IVideoSource
{
	const string SOURCE_NAME = "Local DISH TV";

	string IVideoSource.GetTvGuide()
	{
		return string.Format("Getting TV guide from - {0}", SOURCE_NAME);
	}

	string IVideoSource.PlayVideo()
	{
		return string.Format("Playing - {0}", SOURCE_NAME);
	}
}

class IPTvService : IVideoSource
{
	const string SOURCE_NAME = "IP TV";

	string IVideoSource.GetTvGuide()
	{
		return string.Format("Getting TV guide from - {0}", SOURCE_NAME);
	}

	string IVideoSource.PlayVideo()
	{
		return string.Format("Playing - {0}", SOURCE_NAME);
	}
}

So we can see that each ConcreteImplementor provide the same abstraction but the actual implementation differs (Right now we have dummy messages to show the different implementations).

Let us now implement the RefinedAbstraction i.e. The actual TV class that will use these ConcreteImplementors to perform the playback. (For Simplicity, I have merger Abstraction and RefinedAbstraction in single class without any interface)

class MySuperSmartTV
{
	IVideoSource currentVideoSource = null;

	public IVideoSource VideoSource
	{
		get
		{
			return currentVideoSource;
		}
		set
		{
			currentVideoSource = value;
		}
	}

	public void ShowTvGuide()
	{
		if (currentVideoSource != null)
		{
			Console.WriteLine(currentVideoSource.GetTvGuide());
		}
		else
		{
			Console.WriteLine("Please select a Video Source to get TV guide from");
		}
	}

	public void PlayTV()
	{
		if (currentVideoSource != null)
		{
			Console.WriteLine(currentVideoSource.PlayVideo());
		}
		else
		{
			Console.WriteLine("Please select a Video Source to play");
		}
	}
}

Next thing would be to use the appropriate implementation based on user selection. So let us give the user a choice to select the appropriate TV source and then get the TV guide from that source and even start playing that source.

class SuperSmartTvController
{
	static void Main(string[] args)
	{
		MySuperSmartTV myTv = new MySuperSmartTV();
					
		Console.WriteLine("Select A source to get TV Guide and Play");
		Console.WriteLine("1. Local Cable TV\n2. Local Dish TV\n3. IP TV");

		ConsoleKeyInfo input = Console.ReadKey();

		// Let us see what user has selected and select the video source accrodingly
		switch (input.KeyChar)
		{
			case '1':
				myTv.VideoSource = new LocalCabelTv();
				break;

			case '2':
				myTv.VideoSource = new LocalDishTv();
				break;

			case '3':
				myTv.VideoSource = new IPTvService();
				break;                
		}

		Console.WriteLine(); //some whitespace on output for readability

		//Let us show the TV guide from selected source
		myTv.ShowTvGuide();

		//Let us now play the selected TV source.
		myTv.PlayTV();

		Console.WriteLine(); //some whitespace on output for readability
	}
}

Now the user can select the TV source and each TV source will have its own implementation but can be used with the same abstraction like a plugin based architecture.The interface for TV source now is totally independent of the implementations of each TV source.

Let us run the application and select IPTV as the source

running

Before concluding our discussion let us look at the class diagram of our application and map it with the class diagram of bridge pattern shown above.

myBridge

Point of Interest

Since the interface are more stable (i.e less probability of getting changed) than the implementation it sounds like a good idea to put the interfaces of a class separate from the implementation.

Download sample code for this article: BridgeTest