理解和实现 C# 中的桥模式






4.71/5 (20投票s)
本文讨论桥接模式,并尝试使用一个简单的应用程序来演示它。
引言
让我们在本文中讨论“桥接模式”。当我们想到桥接模式时,首先想到的是“它是在两个不同的实现之间的一种桥梁”。但事实并非如此。上面提到的这是适配器模式的任务,这里讨论了适配器模式。但要谈论桥接模式,该模式专门设计用于使抽象和实现独立变化。
背景
GoF 将桥接模式定义为“将抽象与其实现解耦,以便两者可以独立变化。”
此模式可以使用的最佳示例是插件或驱动程序的使用。如果我们有一个应用程序可以使用任何特定的驱动程序,那么我们可以使用桥接模式。一个经典的例子是像“半条命”这样的游戏中视频配置选择屏幕的场景。该屏幕为用户提供了以OpenGL
,Direct3D
或软件
模式运行游戏的选项。当用户进行选择时,我们可以使用特定于选择的具体的实现,而不会对抽象产生任何影响。
现在让我们可视化桥接模式。

现在让我们看看此类图中每个组件代表什么。
Implementor
:此接口提供了所有插件应该提供的初步操作。ConcreteImplementor
:这些是实现Implementor
接口的实际类。这些类将被有选择地使用,比如插件。Abstraction
:此类为客户端提供了一个抽象。客户端将继续使用此抽象,甚至无需关心内部选择了哪个实际插件,即ConcreteImplementor
。RefinedAbstraction
:此类将实现 Abstraction 类的实际功能。选择适当的ConcreteImplementor
是此类的责任。
使用代码
现在让我们尝试使用桥接模式实现一个小例子。让我们想象一个虚构的智能电视,它为用户提供了播放本地有线电视、本地卫星电视或 IPTV 源的可能性。在这里,用户可以从选定的源请求电视指南。他还可以选择播放选定的电视频道。
因此,从电视的角度来看,获取指南和播放电视的抽象。每个源的实际实现将有所不同。因此,让我们继续,首先定义每个电视源将提供的抽象,即来自上述图表的Implementor
。
interface IVideoSource
{
string GetTvGuide();
string PlayVideo();
}
现在让我们为每个电视源定义ConcreteImplementors
。
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);
}
}
因此,我们可以看到每个ConcreteImplementor
提供相同的抽象,但实际实现有所不同(目前我们有虚拟消息来显示不同的实现)。
现在让我们实现RefinedAbstraction
,即实际的电视类,它将使用这些ConcreteImplementors
来执行播放。(为了简单起见,我将Abstraction
和RefinedAbstraction
合并在单个类中,没有任何接口)
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"); } } }
下一步是根据用户的选择使用适当的实现。因此,让我们让用户选择适当的电视源,然后从该源获取电视指南,甚至开始播放该源。
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
}
}
现在用户可以选择电视源,每个电视源将有自己的实现,但可以与相同的抽象一起使用,就像基于插件的架构一样。电视源的接口现在完全独立于每个电视源的实现。
让我们运行应用程序并选择 IPTV 作为源

在结束我们的讨论之前,让我们看一下我们应用程序的类图,并将其与上面显示的桥接模式的类图进行映射。
关注点
由于接口比实现更稳定(即更改的可能性较小),因此将类的接口与实现分开似乎是一个好主意。
历史
- 2012年8月4日:第一个版本