1 外观模式的基本概念
C++ 外观模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供了一个统一的高级接口,从而使得子系统更容易使用。外观模式定义了一个高层次的接口,这个接口使得这一子系统更加容易使用。这一模式为子系统中的各类(或结构和/或行为)提供了一个简单而一致的视图,它为涉及一组类的子系统提供了一种更高级别的接口,使得子系统更容易使用。
在 C++ 中实现外观模式时,通常会创建一个外观类,该类封装了子系统中多个类的复杂交互。外观类对外提供一个简单的接口,隐藏了子系统的复杂性,使得客户端代码只需要与外观类交互,而不需要了解子系统的具体实现细节。
外观模式通常包含以下几个主要的组成成员:
(1)外观类(Facade):
- 外观类为子系统中的一组接口提供了一个统一的高级接口。
- 它封装了子系统中复杂的功能和交互,使得客户端代码能够更容易地与子系统交互。
- 外观类通常隐藏了子系统的具体实现细节,只暴露出一个简单的接口给客户端使用。
(2)子系统类(Subsystem Classes):
- 子系统类是实现具体功能的类,它们包含了复杂的业务逻辑和交互。
- 子系统类通常不会直接暴露给客户端使用,而是通过外观类进行间接访问。
- 子系统类之间可能存在复杂的依赖关系和交互逻辑。
(3)客户端代码(Client Code):
- 客户端代码是使用外观模式的代码,它不需要直接了解子系统的具体实现细节。
- 客户端代码通过调用外观类提供的方法来使用子系统的功能。
- 客户端代码与外观类进行交互,而不需要关心子系统内部的复杂性。
2 外观模式的实现步骤
在 C++ 实现外观模式的实现步骤如下:
(1)识别子系统:
首先,需要确定你的系统中哪些类和方法构成了复杂或不易直接使用的子系统。这些子系统可能包含多个类和方法,它们一起工作以完成特定的功能。
(2)定义外观类:
接下来,创建一个新的类作为外观类。这个类将作为子系统的统一接口,隐藏子系统的复杂性。
(3)建立子系统引用:
在外观类中,你需要创建对子系统类实例的引用或指针。这些引用将用于在外观类的方法中调用子系统的方法。
(4)实现外观方法:
在外观类中,实现一些方法,这些方法将调用子系统类的方法,并按照特定的逻辑顺序来执行它们。这些方法应该提供用户所需要的简化接口,隐藏了子系统的具体细节。
(5)封装子系统交互:
确保外观类的方法能够封装所有必要的子系统交互,以便用户只需要通过外观类来与子系统交互,而不需要直接访问子系统的方法。
如下为样例代码:
#include <iostream>
#include <string>
#include <memory>
class SubSystemA {
public:
void MethodA() {
// 子系统A的具体实现
std::cout << "Subsystem A method called." << std::endl;
}
};
class SubSystemB {
public:
void MethodB() {
// 子系统B的具体实现
std::cout << "Subsystem B method called." << std::endl;
}
};
class SubSystemC {
public:
void MethodC() {
// 子系统C的具体实现
std::cout << "Subsystem C method called." << std::endl;
}
};
// 外观类
class Facade {
public:
Facade()
{
subSystemA = std::make_shared<SubSystemA>();
subSystemB = std::make_shared<SubSystemB>();
subSystemC = std::make_shared<SubSystemC>();
}
~Facade() {}
// 外观方法,提供简化的接口
void UnifiedOperation() {
// 按照特定逻辑顺序调用子系统方法
subSystemA->MethodA();
subSystemB->MethodB();
subSystemC->MethodC();
}
private:
std::shared_ptr<SubSystemA> subSystemA;
std::shared_ptr<SubSystemB> subSystemB;
std::shared_ptr<SubSystemC> subSystemC;
};
// 主程序
int main()
{
// 使用外观类
Facade facade;
facade.UnifiedOperation(); // 调用外观方法,隐藏了子系统的复杂性
return 0;
}
在这个示例中,SubSystemA、SubSystemB和SubSystemC 代表子系统的类,它们各自包含了一些具体实现的方法。Facade 类作为外观类,它内部持有了对子系统类的引用,并提供了一个 UnifiedOperation 方法,这个方法按照特定的逻辑顺序调用了子系统的方法。
在main函数中,创建了一个 Facade 对象,并调用了它的 UnifiedOperation 方法,从而隐藏了子系统的复杂性,使得客户端代码只需要与外观类交互。
3 外观模式的应用场景
C++ 外观模式的应用场景通常出现在以下情况中:
(1)简化复杂系统: 当系统变得非常复杂,由许多子系统组成时,外观模式可以提供一个简单的接口,隐藏系统的复杂性,使客户端更容易使用系统。这样,客户端只需通过外观类来访问子系统,而无需直接与多个子系统交互。
(2)封装遗留代码: 在现有的系统中,可能存在一些老旧的代码或者接口,外观模式可以用于封装这些遗留代码,使其对客户端透明。同时,外观模式也可以为逐步进行系统重构提供便利。
提供统一接口:当系统提供了多个接口,而客户端只需要使用其中的一部分时,外观模式可以提供一个统一的接口,简化客户端的使用。这样,客户端只需与外观类交互,而无需关心底层子系统的具体实现。
(3)降低耦合度: 外观模式可以降低系统中各个模块之间的耦合度,使得系统更易于维护和扩展。通过将子系统的交互封装在外观类中,可以减少客户端与子系统之间的直接依赖关系。
总的来说,C++ 外观模式适用于需要简化复杂系统、封装遗留代码、提供统一接口以及降低系统模块之间耦合度的场景。通过引入外观类,可以隐藏子系统的复杂性,提供一个简单且统一的接口给客户端使用,从而提高系统的可维护性和可扩展性。
3.1 外观模式在简化复杂系统的典型应用
C++外观模式在简化复杂系统的典型应用主要体现在以下几个方面:
首先,当系统由多个相互关联的子系统组成,且这些子系统之间的交互复杂时,外观模式可以提供一个简化的接口来隐藏这些复杂性。通过外观类,客户端只需要与这个单一的接口进行交互,而无需了解子系统之间的详细关系和交互逻辑。这大大简化了客户端的代码,并降低了客户端与子系统之间的耦合度。
其次,当系统需要进行重构或修改时,外观模式可以提供一个稳定的接口,减少对客户端的影响。由于客户端只与外观类进行交互,因此当子系统发生变化时,只需要修改外观类的实现,而无需修改客户端的代码。这提高了系统的可维护性和可扩展性。
此外,当存在多个客户端,每个客户端需要不同的接口访问系统时,外观模式可以提供定制的接口。通过为不同的客户端提供不同的外观类,可以满足不同客户端的特定需求,同时保持系统的统一性和一致性。
一个典型的例子是多媒体播放器系统,其中包含音频播放器、视频播放器、字幕显示器等多个子系统。通过使用外观模式,我们可以创建一个多媒体播放器外观类,该类封装了这些子系统的实例和相应的控制方法。客户端只需要调用这个外观类中的方法,就可以方便地控制整个多媒体播放器的功能,而无需关心底层子系统的具体实现细节。
首先,定义音频播放器、视频播放器、字幕显示器等子系统的接口和具体实现:
#include <iostream>
#include <string>
#include <memory>
// 音频播放器接口
class AudioPlayer {
public:
virtual void play() = 0;
virtual void stop() = 0;
// 其他音频播放功能...
};
// 音频播放器实现
class AudioPlayerImpl : public AudioPlayer {
public:
void play() override {
// 实现音频播放逻辑
std::cout << "Playing audio..." << std::endl;
}
void stop() override {
// 实现音频停止逻辑
std::cout << "Stopping audio..." << std::endl;
}
// 其他音频播放功能实现...
};
// 视频播放器接口
class VideoPlayer {
public:
virtual void play() = 0;
virtual void pause() = 0;
virtual void stop() = 0;
// 其他视频播放功能...
};
// 视频播放器实现
class VideoPlayerImpl : public VideoPlayer {
public:
void play() override {
// 实现视频播放逻辑
std::cout << "Playing video..." << std::endl;
}
void pause() override {
// 实现视频暂停逻辑
std::cout << "Pausing video..." << std::endl;
}
void stop() override {
// 实现视频停止逻辑
std::cout << "Stopping video..." << std::endl;
}
// 其他视频播放功能实现...
};
// 字幕显示器接口
class SubtitleDisplay {
public:
virtual void show() = 0;
virtual void hide() = 0;
// 其他字幕显示功能...
};
// 字幕显示器实现
class SubtitleDisplayImpl : public SubtitleDisplay {
public:
void show() override {
// 实现字幕显示逻辑
std::cout << "Showing subtitles..." << std::endl;
}
void hide() override {
// 实现字幕隐藏逻辑
std::cout << "Hiding subtitles..." << std::endl;
}
// 其他字幕显示功能实现...
};
然后,创建多媒体播放器外观类,封装音频播放器、视频播放器、字幕显示器等子系统的实例,并提供统一的接口:
// 多媒体播放器外观类
class MultimediaPlayerFacade {
public:
MultimediaPlayerFacade() {
audioPlayer = std::make_shared<AudioPlayerImpl>();
videoPlayer = std::make_shared<VideoPlayerImpl>();
subtitleDisplay = std::make_shared<SubtitleDisplayImpl>();
}
~MultimediaPlayerFacade() {}
// 提供统一的接口来控制多媒体播放
void play() {
videoPlayer->play();
audioPlayer->play();
subtitleDisplay->show();
}
void pause() {
videoPlayer->pause();
audioPlayer->stop(); // 假设暂停时停止音频播放
}
void stop() {
videoPlayer->stop(); // 假设存在stop方法
audioPlayer->stop();
subtitleDisplay->hide();
}
// 其他多媒体播放控制功能...
private:
std::shared_ptr<AudioPlayer> audioPlayer;
std::shared_ptr<VideoPlayer> videoPlayer;
std::shared_ptr<SubtitleDisplay> subtitleDisplay;
};
最后,在客户端代码中,通过多媒体播放器外观类来使用多媒体播放器系统:
int main()
{
// 创建多媒体播放器外观类的实例
MultimediaPlayerFacade facade;
// 使用外观类提供的接口来控制多媒体播放
facade.play(); // 开始播放视频、音频,并显示字幕
// ... 其他操作,如暂停、停止等
facade.pause(); // 暂停播放
facade.stop(); // 停止播放,隐藏字幕
return 0;
}
在这个例子中,客户端代码只需要与 MultimediaPlayerFacade 类进行交互,而无需关心底层子系统的具体实现细节。外观类隐藏了子系统的复杂性,并提供了简化的接口来控制多媒体播放器的功能。这使得客户端代码更加简洁和易于维护,同时也降低了客户端与子系统之间的耦合度。
3.2 外观模式在降低耦合度的典型应用
C++ 外观模式在降低耦合度方面的典型应用主要体现在解耦客户端与复杂的子系统之间的依赖关系。通过将子系统的内部细节封装在外观类中,客户端只需要与外观类进行交互,而无需直接了解或依赖子系统的实现。
下面是一个使用C++外观模式降低耦合度的典型应用示例:
假设有一个复杂的图书馆管理系统,其中包含了图书借阅、归还、查询等多个子系统。这些子系统之间存在复杂的交互和依赖关系。如果没有使用外观模式,客户端代码将需要直接与这些子系统交互,导致耦合度较高,难以维护和扩展。
通过使用外观模式,可以创建一个图书馆管理系统的外观类,该类封装了与各个子系统的交互逻辑。客户端只需要与这个外观类进行交互,而无需关心子系统的具体实现。
下面是简化后的示例代码:
#include <iostream>
#include <string>
#include <memory>
// 图书借阅子系统接口
class BorrowSubsystem {
public:
virtual void borrowBook(const std::string& bookId) = 0;
// 其他借阅功能...
};
// 图书归还子系统接口
class ReturnSubsystem {
public:
virtual void returnBook(const std::string& bookId) = 0;
// 其他归还功能...
};
// 图书查询子系统接口
class QuerySubsystem {
public:
virtual bool isBookAvailable(const std::string& bookId) = 0;
// 其他查询功能...
};
// 图书借阅子系统实现
class BorrowSubsystemImpl:public BorrowSubsystem {
public:
void borrowBook(const std::string& bookId)
{
std::cout << "borrow " << bookId << std::endl;
}
// 其他借阅功能...
};
// 图书归还子系统实现
class ReturnSubsystemImpl :public ReturnSubsystem {
public:
void returnBook(const std::string& bookId)
{
std::cout << "return " << bookId << std::endl;
}
// 其他归还功能...
};
// 图书查询子系统实现
class QuerySubsystemImpl :public QuerySubsystem {
public:
bool isBookAvailable(const std::string& bookId)
{
std::cout << bookId << " is available" << std::endl;
return true;
}
// 其他查询功能...
};
// 图书馆管理系统外观类
class LibraryFacade {
public:
LibraryFacade() {
borrowSubsystem = std::make_shared<BorrowSubsystemImpl>();
returnSubsystem = std::make_shared<ReturnSubsystemImpl>();
querySubsystem = std::make_shared<QuerySubsystemImpl>();
}
~LibraryFacade() {}
// 提供简化的接口供客户端使用
void borrowBook(const std::string& bookId) {
if (querySubsystem->isBookAvailable(bookId)) {
borrowSubsystem->borrowBook(bookId);
std::cout << "Book borrowed successfully." << std::endl;
}
else {
std::cout << "Book is not available." << std::endl;
}
}
void returnBook(const std::string& bookId) {
returnSubsystem->returnBook(bookId);
std::cout << "Book returned successfully." << std::endl;
}
// 其他简化的接口...
private:
std::shared_ptr<BorrowSubsystem> borrowSubsystem;
std::shared_ptr<ReturnSubsystem> returnSubsystem;
std::shared_ptr<QuerySubsystem> querySubsystem;
};
// 客户端代码
int main()
{
LibraryFacade facade; // 使用外观类
// 客户端通过外观类进行图书借阅操作
facade.borrowBook("book1");
// 客户端通过外观类进行图书归还操作
facade.returnBook("book1");
return 0;
}
上面代码的输出为:
book1 is available
borrow book1
Book borrowed successfully.
return book1
Book returned successfully.
在上面的代码中,BorrowSubsystem、ReturnSubsystem 和 QuerySubsystem 分别代表了图书借阅、归还和查询子系统。这些子系统具体的实现细节被封装在相应的实现类中(如 BorrowSubsystemImpl、ReturnSubsystemImpl 和 QuerySubsystemImpl),客户端代码无需关心。
LibraryFacade 类作为图书馆管理系统的外观类,封装了与各个子系统的交互逻辑。客户端代码只需要与 LibraryFacade 类进行交互,通过调用其提供的简化接口(如 borrowBook 和 returnBook)来完成图书借阅和归还的操作。
通过使用外观模式,客户端代码与子系统之间的耦合度被降低了。客户端不再需要直接了解或依赖子系统的具体实现,只需要与外观类进行交互即可。这使得系统更加灵活和可扩展,当子系统发生变化时,只需要修改外观类的实现,而无需修改客户端代码。同时,外观类也提供了一层抽象,使得客户端代码更加简洁和易于理解。
4 外观模式的优点与缺点
C++ 外观模式的优点主要包括:
(1)简化复杂系统: 当系统由多个复杂的子系统组成时,外观模式可以为客户端提供一个简单的接口,隐藏子系统的复杂性,使得客户端更容易使用系统。
(2)封装遗留代码: 在现有系统中,可能存在一些老旧的代码或接口,使用外观模式可以将这些遗留代码封装起来,使其对客户端透明,并可以在此基础上逐步进行系统重构。
(3)提供统一接口: 当系统提供多个接口,而客户端只需要使用其中的一部分时,外观模式可以提供一个统一的接口,简化客户端的使用。
(4)降低耦合度: 外观模式使得客户端与子系统之间实现了松耦合,降低了客户端与子系统之间的依赖关系。子系统的内部变化不会影响外观类,只需调整外观类即可,提高了系统的灵活性和可维护性。
然而,C++ 外观模式也存在一些缺点:
(1)可能限制客户端的灵活性: 如果对外观类设计不当,它可能会限制客户端直接使用子系统类。这种限制虽然可以减少客户端对子系统的直接访问,但也可能减少系统的可变性和灵活性。
(2)可能违反开闭原则: 如果外观类设计得过于庞大和复杂,当需要增加新的子系统时,可能需要修改外观类的源代码,这违背了开闭原则,即软件实体应当对扩展开放,对修改封闭。
5 外观模式与桥接模式的区别
C++ 中的桥接模式(Bridge Pattern)和外观模式(Facade Pattern)是两种不同但各有其特色的设计模式。它们在应用中的目的、结构和解决的问题上存在一些明显的区别。
首先,桥接模式的主要目的是将抽象与实现分离,使它们可以独立变化。桥接模式使用组合关系替代继承关系,降低了抽象和实现这两个可变维度的耦合度。当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时,桥接模式特别有用。它允许用户只依赖于一个抽象,即使业务变更,也不用去变动里面的代码,只需添加新的实现即可。
而外观模式的主要目标是提供一个统一的接口,简化子系统的使用。它将一个复杂的子系统及其交互关系进行封装,使得客户端只与外观类交互,而无需关心子系统的具体实现。外观模式减少了客户端与子系统的耦合,降低了客户端的使用成本,提升了系统的灵活性。
从结构上看,桥接模式更侧重于在抽象和实现之间建立桥梁,使得它们可以独立变化;而外观模式则是通过封装子系统的复杂性,为客户端提供一个简单的接口。
在解决问题方面,桥接模式主要用于处理抽象和实现之间的灵活变化问题,而外观模式则主要用于简化复杂系统的使用,隐藏子系统的细节。
总结来说,桥接模式和外观模式都是用于解决复杂系统设计中的不同问题,它们各自有着独特的优势和适用场景。桥接模式关注的是抽象和实现的分离与独立变化,而外观模式则注重于简化复杂系统的使用,降低客户端的使用成本。