前言:
今天我们一起来聊一聊桥接模式,是一种结构型的设计模式。
一、原理及示例代码
桥接模式是一种结构型设计模式,用于将抽象部分与它的实现部分分离,以便两者可以独立地变化。这种模式涉及一个接口,它充当一个桥,使得具体类可以独立地改变。
在桥接模式中,抽象类和实现类可以通过组合关系进行连接,而不是继承关系。这样可以减少类之间的耦合,使得抽象类和实现类可以独立地变化。
下面是一个简单的 C++ 示例代码,演示了桥接模式的原理:
#include <iostream>
// 实现部分的接口
class Implementor {
public:
virtual void operationImpl() = 0;
};
// 具体的实现类 A
class ConcreteImplementorA : public Implementor {
public:
void operationImpl() override {
std::cout << "Concrete Implementor A operation" << std::endl;
}
};
// 具体的实现类 B
class ConcreteImplementorB : public Implementor {
public:
void operationImpl() override {
std::cout << "Concrete Implementor B operation" << std::endl;
}
};
// 抽象部分的接口
class Abstraction {
protected:
Implementor* implementor;
public:
Abstraction(Implementor* impl) : implementor(impl) {}
virtual void operation() = 0;
};
// 扩展抽象部分的具体类
class RefinedAbstraction : public Abstraction {
public:
RefinedAbstraction(Implementor* impl) : Abstraction(impl) {}
void operation() override {
std::cout << "Refined Abstraction operation" << std::endl;
implementor->operationImpl();
}
};
int main() {
Implementor* implA = new ConcreteImplementorA();
Implementor* implB = new ConcreteImplementorB();
Abstraction* abs1 = new RefinedAbstraction(implA);
Abstraction* abs2 = new RefinedAbstraction(implB);
abs1->operation();
abs2->operation();
delete implA;
delete implB;
delete abs1;
delete abs2;
return 0;
}
在这个示例中,Implementor
是实现部分的接口,ConcreteImplementorA
和 ConcreteImplementorB
是具体的实现类。Abstraction
是抽象部分的接口,RefinedAbstraction
是扩展抽象部分的具体类。在 main
函数中,我们创建了具体的实现类对象,并将其传递给抽象类对象,然后调用了相应的操作。
这样,通过桥接模式,实现部分和抽象部分可以独立地变化,而不会相互影响。
二、结构图
桥接模式的结构图如下所示:
+---------------------+ +---------------------+
| Abstraction | | Implementor |
+---------------------+ +---------------------+
| - implementor | | |
+---------------------+ +---------------------+
| + operation() | | + operationImpl() |
+---------------------+ +---------------------+
| |
| |
| |
+---------------------+ +---------------------+
| RefinedAbstraction | | ConcreteImplementor |
+---------------------+ +---------------------+
| | | |
+---------------------+ +---------------------+
在这个结构图中,Abstraction
是抽象部分的接口,其中包含了一个对 Implementor
接口的引用。RefinedAbstraction
是扩展抽象部分的具体类,它继承自 Abstraction
。Implementor
是实现部分的接口,其中定义了实际操作的接口。ConcreteImplementor
是具体的实现类,实现了 Implementor
接口。
三、场景
桥接模式通常用于以下情况:
-
当一个类存在多个独立变化的维度,且希望在这些维度上可以独立扩展和变化时,可以使用桥接模式。例如,一个文档编辑器可能有多种不同的格式输出(如 PDF、HTML 等),同时还有多种不同的主题(如暗色主题、亮色主题等)。使用桥接模式可以将格式输出和主题两个维度进行分离,使得它们可以独立地变化和扩展。
-
当一个类需要在运行时可以选择不同的实现时,可以使用桥接模式。例如,一个图形绘制程序可能需要在运行时选择不同的绘制引擎(如 OpenGL、DirectX 等)。使用桥接模式可以将图形绘制和绘制引擎两个维度进行分离,使得它们可以在运行时独立地选择和切换。
下面是一个简单的示例代码,演示了桥接模式的使用场景及其 C++ 实现:
#include <iostream>
// 实现部分的接口
class DrawingAPI {
public:
virtual void drawCircle(double x, double y, double radius) = 0;
};
// 具体的实现类 A
class DrawingAPI1 : public DrawingAPI {
public:
void drawCircle(double x, double y, double radius) override {
std::cout << "API1.circle at " << x << ":" << y << " radius " << radius << std::endl;
}
};
// 具体的实现类 B
class DrawingAPI2 : public DrawingAPI {
public:
void drawCircle(double x, double y, double radius) override {
std::cout << "API2.circle at " << x << ":" << y << " radius " << radius << std::endl;
}
};
// 抽象部分的接口
class Shape {
protected:
DrawingAPI* drawingAPI;
public:
Shape(DrawingAPI* api) : drawingAPI(api) {}
virtual void draw() = 0;
};
// 扩展抽象部分的具体类
class Circle : public Shape {
private:
double x, y, radius;
public:
Circle(double x, double y, double radius, DrawingAPI* api) : Shape(api), x(x), y(y), radius(radius) {}
void draw() override {
drawingAPI->drawCircle(x, y, radius);
}
};
int main() {
DrawingAPI* api1 = new DrawingAPI1();
DrawingAPI* api2 = new DrawingAPI2();
Shape* circle1 = new Circle(1, 2, 3, api1);
Shape* circle2 = new Circle(5, 7, 11, api2);
circle1->draw();
circle2->draw();
delete api1;
delete api2;
delete circle1;
delete circle2;
return 0;
}
在这个示例中,DrawingAPI
是实现部分的接口,DrawingAPI1
和 DrawingAPI2
是具体的实现类。Shape
是抽象部分的接口,Circle
是扩展抽象部分的具体类。在 main
函数中,我们创建了具体的实现类对象,并将其传递给抽象类对象,然后调用了相应的操作。
这样,通过桥接模式,我们可以将图形的形状和绘制的方式两个维度进行分离,使得它们可以独立地变化和扩展。
假设我们有一个项目,需要实现不同品牌的手机和不同型号的手机,同时还需要支持不同的操作系统。我们可以使用桥接模式来实现这个场景。
首先,我们定义手机的抽象类 Phone
和操作系统的抽象类 OperatingSystem
,并定义实现部分的接口 Brand
:
// 实现部分的接口
class Brand {
public:
virtual void showBrand() = 0;
};
// 具体的实现类 A
class Apple : public Brand {
public:
void showBrand() override {
std::cout << "Apple";
}
};
// 具体的实现类 B
class Samsung : public Brand {
public:
void showBrand() override {
std::cout << "Samsung";
}
};
// 抽象部分的接口
class Phone {
protected:
Brand* brand;
public:
Phone(Brand* brand) : brand(brand) {}
virtual void run() = 0;
};
// 扩展抽象部分的具体类
class IPhone : public Phone {
public:
IPhone(Brand* brand) : Phone(brand) {}
void run() override {
std::cout << "Run on ";
brand->showBrand();
std::cout << " with iOS" << std::endl;
}
};
class Galaxy : public Phone {
public:
Galaxy(Brand* brand) : Phone(brand) {}
void run() override {
std::cout << "Run on ";
brand->showBrand();
std::cout << " with Android" << std::endl;
}
};
然后,在 main
函数中,我们可以创建具体的实现类对象,并将其传递给抽象类对象,然后调用相应的操作:
int main() {
Brand* apple = new Apple();
Brand* samsung = new Samsung();
Phone* iPhone = new IPhone(apple);
Phone* galaxy = new Galaxy(samsung);
iPhone->run();
galaxy->run();
delete apple;
delete samsung;
delete iPhone;
delete galaxy;
return 0;
}
在这个示例中,我们使用桥接模式将手机的品牌和操作系统两个维度进行了分离,使得它们可以独立地变化和扩展。
四、优缺点
桥接模式的优点包括:
- 分离抽象和实现部分:桥接模式通过将抽象部分和实现部分分离,使得它们可以独立地变化和扩展,从而提高了系统的灵活性和可维护性。
- 可以减少子类的数量:桥接模式可以避免使用多层继承来处理多个维度的变化,从而减少了子类的数量,使得系统更加简洁和易于理解。
- 支持动态切换实现:桥接模式允许在运行时动态地选择和切换实现部分,从而可以根据需要来改变系统的行为。
桥接模式的缺点包括:
- 增加了类的数量:引入桥接模式会增加抽象部分和实现部分的类数量,从而可能使得系统的结构变得更加复杂。
- 需要对系统有一定的抽象能力:使用桥接模式需要对系统中的抽象部分和实现部分有一定的抽象能力,否则可能会增加理解和设计的难度。
总的来说,桥接模式适用于需要处理多个维度变化并且希望这些维度可以独立变化的情况,可以带来灵活性和可维护性的提升。然而,也需要根据具体的场景来权衡使用桥接模式所带来的优劣势。
五、常见面试题
-
问题:请解释桥接模式的定义和作用。 答案:桥接模式是一种结构型设计模式,它将抽象部分和实现部分分离,使它们可以独立地变化和扩展。桥接模式的作用是实现多个维度的变化分离,从而提高系统的灵活性和可维护性。
-
问题:桥接模式和适配器模式有什么区别? 答案:桥接模式和适配器模式都是结构型设计模式,但它们的目的和用途不同。桥接模式用于将抽象部分和实现部分分离,以处理多个维度的变化;而适配器模式用于将一个类的接口转换成客户端所期望的另一个接口。
-
问题:桥接模式和装饰器模式有何异同? 答案:桥接模式和装饰器模式都是结构型设计模式,但它们的目的和使用方式不同。桥接模式用于将抽象和实现分离,以处理多个维度的变化;而装饰器模式用于动态地给一个对象添加一些额外的职责。
-
问题:请举例说明桥接模式在实际开发中的应用场景。 答案:桥接模式在实际开发中常用于处理多个维度的变化,例如不同品牌的手机和不同操作系统的手机。通过桥接模式,可以将手机的品牌和操作系统两个维度分离,使得它们可以独立变化和扩展。
-
问题:桥接模式和组合模式有何联系? 答案:桥接模式和组合模式都是设计模式,它们之间没有直接的联系。桥接模式用于处理多个维度的变化,而组合模式用于将对象组合成树形结构以表示“部分-整体”的层次结构。
-
问题:桥接模式的优点有哪些? 答案:桥接模式的优点包括分离抽象和实现、减少子类数量、支持动态切换实现等。
-
问题:桥接模式的缺点有哪些? 答案:桥接模式的缺点包括增加类的数量、需要对系统有一定的抽象能力等。
-
问题:桥接模式和策略模式有何联系? 答案:桥接模式和策略模式都是设计模式,它们之间没有直接的联系。桥接模式用于处理多个维度的变化,而策略模式用于定义一系列算法,并使其可以相互替换。
-
问题:桥接模式和代理模式有何异同? 答案:桥接模式和代理模式都是设计模式,但它们的目的和使用方式不同。桥接模式用于将抽象部分和实现部分分离,以处理多个维度的变化;而代理模式用于控制对对象的访问。
-
问题:请举例说明桥接模式在现实生活中的应用。 答案:桥接模式在现实生活中的应用场景包括不同品牌的汽车和不同类型的引擎、不同品牌的音响和不同类型的音源等。通过桥接模式,可以将汽车的品牌和引擎类型、音响的品牌和音源类型等分离,使得它们可以独立变化和扩展。
4
5 |-----|
+---+ | |
| | | |
| | 3 3 | |
| | | |
| +---+ ----- | |
| | | | | | |----|3
2 | | 2 | | 2 | | | |
| | | | | | | |
+---+ | ----+ | +---+ | | | |
| | | | | | | | | |
1 | | 1 1 | | 1 | | 1 | | | |
| | | | | | | | | |
+---+ +---+ +---+ +---+ +---+ | | | |
| | | | | | | |
0 | | 0 0 | | 0 | | | |
| | | | | | | |
+---+ +-------+ +---+ | +--- | |+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18