C++ 设计模式 —— 桥接模式
0. 引用连接
本文主要的思路和代码,来自于对以下连接的学习和实现:
桥接模式
1. 引言
1.1 什么是桥接模式?
- 桥接模式的定义
- 桥接模式的作用
桥接模式,顾名思义,就像是一座连接两岸的桥梁。在软件开发中,我们可以将桥接模式看作是一座连接抽象部分和实现部分的“桥”,通过这座“桥”,我们可以方便地在抽象部分和实现部分之间进行切换,而不需要关心它们之间的实现细节。
桥接模式的核心作用就是降低系统的耦合度,提高扩展性和可维护性。想象一下你正在修建一座连接两个城市的大桥,如果没有这座桥,你需要绕行很远的距离才能到达对岸。同样地,在软件开发中,如果没有桥接模式,抽象部分和实现部分之间的依赖关系可能会变得非常复杂,导致系统难以扩展和维护。
1.2 桥接模式与其他设计模式的关系
- 桥接模式和其他设计模式的关系就像是一组不同的工具,每个工具都有其独特的用途。就像在修建一座桥梁时,我们可以选择使用各种不同的材料和工具来完成任务,而不仅仅是一种。同样地,在软件开发中,我们可以结合使用不同的设计模式来解决不同的问题,而桥接模式通常会于开发前期进行设计。
- 桥接模式、状态模式和策略模式(在某种程度上包括适配器)都具有相似的接口,它们都基于组合模式——即将工作委派给其他对象。但是它们各自解决了不同的问题。例如,如果我们需要将一个类与它的子类进行关联,并且希望在运行时动态地创建子类的实例,那么桥接模式就非常适合。而如果我们需要对一个类的多个行为进行控制,那么状态模式就是一个不错的选择。
- 我们可以将抽象工厂模式和桥接搭配使用。如果由桥接定义的抽象只能与特定实现合作,这一模式搭配就非常有用。在这种情况下,抽象工厂可以对这些关系进行封装,并且对客户端代码隐藏其复杂性。
- 生成器模式和桥接模式也可以很好地结合使用:主管类负责抽象工作,各种不同的生成器负责实现工作。这就好比我们在修建桥梁时,需要不同的工人来完成不同的任务一样,生成器模式可以帮助我们更好地管理代码的生成过程。
桥接模式和其他设计模式都是非常有用的工具,它们可以帮助我们更好地组织和管理代码,提高系统的可维护性和可扩展性。在使用这些工具时,我们需要根据具体的需求和场景来选择合适的模式,以最大程度地发挥它们的效用。
1.3 桥接模式适用的场景
桥接模式的应用场景也非常广泛。比如我们正在开发一个电商网站,其中有一个订单管理系统和一个支付系统。这两个系统分别负责订单的处理和支付的操作,但是它们之间的关系非常紧密。如果没有桥接模式,我们需要在订单管理系统和支付系统中都实现相同的逻辑,这显然是不合理的。而如果我们使用桥接模式,可以将订单管理系统和支付系统分别抽象成两个独立的类,并通过一个桥接器来协调它们之间的交互,这样就可以避免代码冗余和耦合度过高的问题。如果我们概括一下桥接模式的使用场景的话,主要有以下几个:
- 系统中存在多个独立但相互关联的类时,可以使用桥接模式来解耦它们的依赖关系。
- 当需要动态地创建一系列具有相似特性的对象时,可以使用桥接模式来实现对象的创建和初始化过程。
- 当需要在一个系统中灵活地切换不同的算法或策略时,可以使用桥接模式来实现算法或策略的切换和组合。
- 需要通过组合的方式在运行时对算法进行选择时,可以使用桥接模式来避免使用多重条件语句。
- 需要创建一个可以与多种类型接口进行交互的类时,可以使用桥接模式来实现这个抽象。
2. 桥接模式的实现
桥接模式的实现方式就像是一座连接不同平台的桥梁,它通过抽象部分和实现部分之间的关联来实现。如果以连接桥梁为例,其大致的步骤为:
2.1 抽象部分
抽象部分在桥接模式中起到了至关重要的连接作用。它通过定义接口、封装逻辑、委派工作等方式,将不同的平台进行连接,并帮助客户端与实现部分进行交互。
抽象部分就像是一座桥梁的桥墩,它具体负责了下面的一些内容:
- 定义和封装与实现部分相关的接口和逻辑。
- 需要承受来自不同平台的负荷,并将这些负荷传递到实现部分。同样地,抽象部分也需要承载客户端的业务需求,并将这些需求传递给实现部分来完成具体的操作。
- 定义一些通用的方法和属性,这些方法和属性可以被多个实现类共享和使用。这就好比我们在建造一座桥梁时,需要使用一些通用的材料和工具,以便在不同的地方建造出相同的结构。
- 通过添加引用成员变量来委派具体的实现对象。这样,抽象部分就可以将大部分的工作交给具体实现来完成,而自己只需要关注与客户端的交互即可。这就好比我们选择了一个优秀的工人来建造桥梁的一部分,让他来完成整个工作,而自己则专注于监督和管理。
2.2 具体部分
- 定义具体的子类,这些子类实现了抽象部分定义的接口
- 这些子类可以在运行时动态地被创建,并通过抽象部分提供的接口与客户端代码进行交互
具体部分在桥接模式中同样起到了至关重要的作用。它通过实现抽象部分所定义的方法和属性,完成具体的操作,并帮助客户端与实现部分进行交互。同时,具体部分还可以根据抽象部分的定义,使用不同的实现方式来完成不同的业务逻辑。
具体部分就像是一座桥梁的桥面和支撑结构,它具体负责了下面的一些内容:
- 实现抽象部分定义的接口和逻辑。
- 承载客户端的业务需求,并将这些需求实现成具体的操作。
- 通过添加额外的实现类来实现不同的业务逻辑。这就好比我们在建造一座桥梁时,需要使用不同的材料和工具来建造出不同的结构。
- 具体部分也需要根据抽象部分的定义,使用不同的实现方式来完成不同的业务逻辑。
- 通过扩展抽象基类为每个变体创建一个精确抽象。这样,我们就可以针对不同的平台创建不同的具体实现,以满足不同的业务需求。这就好比我们在不同的路段上修建不同类型的桥梁,以满足不同的交通需求。
3. 桥接模式的优点和缺点
3.1 优点
- 允许我们创建与平台无关的类和程序。这意味着我们可以将不同的平台抽象出来,形成一个统一的高层接口。这样,无论我们在哪个平台上进行开发,都可以使用相同的类和接口来进行交互,而不需要关心具体的平台细节。
- 客户端代码仅与高层抽象部分进行互动,不会接触到平台的详细信息。这就好比我们通过一座桥梁来连接两个不同的地点,我们只需要关注桥梁上的路径和交通情况,而不需要了解每个地点的具体信息。
- 桥接模式还遵循了开闭原则。我们可以新增抽象部分和实现部分,并且它们之间不会相互影响。这就好比我们可以通过添加新的桥梁来连接更多的地点,而不需要修改已有的桥梁结构。这种灵活性使得我们的系统可以更容易地适应新的需求和变化。
- 桥接模式也符合单一职责原则。抽象部分专注于处理高层逻辑,实现部分处理平台细节。这就好比一个桥梁的设计者只需要负责设计出合适的桥梁结构和路径规划,而不需要关心具体的材料和施工细节。这种分离使得各个部分的职责更加明确,易于维护和扩展。
综上所述,桥接模式的优点在于它可以帮助我们更好地组织和管理代码,提高系统的可维护性和可扩展性。通过创建与平台无关的类和程序、客户端代码仅与高层抽象部分进行互动、遵循开闭原则以及单一职责原则等特性,桥接模式为我们提供了一个灵活、高效且易于维护的设计方案。
3.2 缺点
- 对于高内聚的类来说,使用桥接模式可能会让代码变得更加复杂。这是因为在桥接模式中,我们需要将抽象部分和实现部分分离开来,这可能会增加代码的复杂度。就像建造一座复杂的桥梁需要更多的设计和施工工作一样,使用桥接模式也需要更多的思考和编码工作。
- 使用桥接模式可能会导致抽象部分的代码量增加,从而影响系统的性能。这是因为抽象部分需要承担更多的职责和功能,可能需要处理更多的细节和逻辑。就像一座桥梁上的支撑结构需要承受更大的负荷一样,抽象部分也需要处理更多的业务逻辑和操作。
- 使用桥接模式可能会增加系统的复杂性,使得理解和修改代码变得更加困难。这是因为桥接模式将抽象部分和实现部分分离开来,可能会增加代码的耦合度和依赖关系。就像建造一座复杂的桥梁需要更多的协调和管理一样,使用桥接模式也需要更多的关注和维护。
- 客户端代码不正确地使用了抽象部分提供的接口,可能会导致系统出现错误或异常行为。这是因为抽象部分需要负责定义和封装接口,如果客户端代码没有正确地调用这些接口,就会导致系统的错误和异常。就像使用错误的桥梁路径会导致交通拥堵一样,客户端代码的错误使用也会给系统带来问题。
综上所述,桥接模式是一种灵活的设计模式,可以帮助我们更好地组织和管理代码。然而,在使用该模式时也需要注意一些潜在的问题和挑战。就像建造一座桥梁需要考虑许多因素一样,使用桥接模式也需要综合考虑各种因素,以确保系统的正确性和可靠性。
4. 代码实现
4.1 真实案例伪代码描述
// “抽象部分”定义了两个类层次结构中“控制”部分的接口。它管理着一个指向“实
// 现部分”层次结构中对象的引用,并会将所有真实工作委派给该对象。
class RemoteControl is
protected field device: Device
constructor RemoteControl(device: Device) is
this.device = device
method togglePower() is
if (device.isEnabled()) then
device.disable()
else
device.enable()
method volumeDown() is
device.setVolume(device.getVolume() - 10)
method volumeUp() is
device.setVolume(device.getVolume() + 10)
method channelDown() is
device.setChannel(device.getChannel() - 1)
method channelUp() is
device.setChannel(device.getChannel() + 1)
// 你可以独立于设备类的方式从抽象层中扩展类。
class AdvancedRemoteControl extends RemoteControl is
method mute() is
device.setVolume(0)
// “实现部分”接口声明了在所有具体实现类中通用的方法。它不需要与抽象接口相
// 匹配。实际上,这两个接口可以完全不一样。通常实现接口只提供原语操作,而
// 抽象接口则会基于这些操作定义较高层次的操作。
interface Device is
method isEnabled()
method enable()
method disable()
method getVolume()
method setVolume(percent)
method getChannel()
method setChannel(channel)
// 所有设备都遵循相同的接口。
class Tv implements Device is
// ……
class Radio implements Device is
// ……
// 客户端代码中的某个位置。
tv = new Tv()
remote = new RemoteControl(tv)
remote.togglePower()
radio = new Radio()
remote = new AdvancedRemoteControl(radio)
4.2 概念示例代码
#include
#include
/**
* The Implementation defines the interface for all implementation classes. It
* doesn't have to match the Abstraction's interface. In fact, the two
* interfaces can be entirely different. Typically the Implementation interface
* provides only primitive operations, while the Abstraction defines higher-
* level operations based on those primitives.
*/
class Implementation
{
public:
virtual ~Implementation() {}
virtual std::string OperationImplementation() const = 0;
};
/**
* Each Concrete Implementation corresponds to a specific platform and
* implements the Implementation interface using that platform's API.
*/
class ConcreteImplementationA : public Implementation
{
public:
std::string OperationImplementation() const override
{
return "ConcreteImplementationA: Here's the result on the platform A.n";
}
};
class ConcreteImplementationB : public Implementation
{
public:
std::string OperationImplementation() const override
{
return "ConcreteImplementationB: Here's the result on the platform B.n";
}
};
/**
* The Abstraction defines the interface for the "control" part of the two class
* hierarchies. It maintains a reference to an object of the Implementation
* hierarchy and delegates all of the real work to this object.
*/
class Abstraction
{
/**
* @var Implementation
*/
protected:
Implementation *implementation_;
public:
Abstraction(Implementation *implementation) : implementation_(implementation)
{
}
virtual ~Abstraction()
{
}
virtual std::string Operation() const
{
return "Abstraction: Base operation with:n" +
this->implementation_->OperationImplementation();
}
};
/**
* You can extend the Abstraction without changing the Implementation classes.
*/
class ExtendedAbstraction : public Abstraction
{
public:
ExtendedAbstraction(Implementation *implementation) : Abstraction(implementation)
{
}
std::string Operation() const override
{
return "ExtendedAbstraction: Extended operation with:n" +
this->implementation_->OperationImplementation();
}
};
/**
* Except for the initialization phase, where an Abstraction object gets linked
* with a specific Implementation object, the client code should only depend on
* the Abstraction class. This way the client code can support any abstraction-
* implementation combination.
*/
void ClientCode(const Abstraction &abstraction)
{
// ...
std::cout disable();
} else {
m_pDevice->enable();
}
}
void RemoteControl::volumeDown()
{
m_pDevice->setVolume(m_pDevice->getVolume() - 10);
}
void RemoteControl::volumeUp()
{
m_pDevice->setVolume(m_pDevice->getVolume() + 10);
}
void RemoteControl::channelDown()
{
m_pDevice->setChannel(m_pDevice->getChannel() - 1);
}
void RemoteControl::channelUp()
{
m_pDevice->setChannel(m_pDevice->getChannel() + 1);
}
void AdvancedRemoteControl::mute()
{
m_pDevice->setVolume(0);
}
TVDevice::TVDevice(bool isEnabled /*= true*/, int volume /*= 1*/, int channel /*= 0*/)
: m_isEnabled(isEnabled)
, m_volume(volume)
, m_channel(channel)
{
}
bool TVDevice::isEnabled()
{
return m_isEnabled;
}
void TVDevice::enable()
{
m_isEnabled = true;
}
void TVDevice::disable()
{
m_isEnabled = false;
}
int TVDevice::getVolume()
{
return m_volume;
}
void TVDevice::setVolume(int volume)
{
if (checkIsValidVolume(volume))
{
m_volume = volume;
}
else
{
std::cout