一、模式介绍
定义
门面模式也称外观模式。门面模式能为子系统中的一组接口提供一个一致(稳定)的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
类比
当通过电话给商店下达订单时, 接线员就是该商店的所有服务和部门的门面。 接线员为消费者提供了一个同购物系统、 支付网关和各种送货服务进行互动的简单语音接口。
适用场景
- 当需要一个指向随着时间推进变得越来越复杂的子系统的接口, 且该接口的功能有限, 则可以使用门面模式。
- 当需要将子系统组织为多层结构时。
二、问题及动机
上述A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合会面临很多变化的挑战。
如何简化外部客户程序和系统间的交互接口,如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦。
三、模式结构
- 门面(Facade)提供了一种访问特定子系统功能的便捷方式,其了解如何重定向客户端请求,知晓如何操作一切活动部件。
- 创建附加门面(Additional Facade)类可以避免多种不相关的功能污染单一外观,使其变成又一个复杂结构。客户端和其他外观都可使用附加外观。
- 复杂子系统(Complex Subsystem)由数十个不同对象构成。如果要用这些对象完成有意义的工作,必须深入了解子系统的实现细节,比如按照正确顺序初始化对象和为其提供正确格式的数据。子系统类不会意识到门面的存在,它们在系统内运作并且相互之间可直接进行交互。
- 客户端(Client)使用门面代替对子系统对象的直接调用。
五、示例代码
在以下示例代码中,门面模式简化了客户端与复杂视频转换框架之间的交互。
/**
* 子系统可以直接接受来自Facade或客户端的请求。
* 在任何情况下,对于子系统来说,Facade是另一个客户端,
* 但它不是子系统的一部分。
*/
class Subsystem1 {
public:
std::string Operation1() const {
return "Subsystem1: Ready!\n";
}
// ...
std::string OperationN() const {
return "Subsystem1: Go!\n";
}
};
/**
* 有些Facade可以同时与多个子系统一起工作。
*/
class Subsystem2 {
public:
std::string Operation1() const {
return "Subsystem2: Get ready!\n";
}
// ...
std::string OperationZ() const {
return "Subsystem2: Fire!\n";
}
};
/**
* Facade类为一个或几个子系统的复杂逻辑提供了一个简单的接口。
* Facade将客户端请求委托给子系统中的适当对象。
* Facade还负责管理它们的生命周期。
* 所有这些都使客户端免受子系统所不需要的复杂性的影响。
*/
class Facade {
protected:
Subsystem1 *subsystem1_;
Subsystem2 *subsystem2_;
/**
* 根据应用程序的需要,可以向Facade提供现有的子系统对象,或者强制Facade自己创建。
*/
public:
/**
* 在本例中,我们将把内存所有权委托给Facade类。
*/
Facade(
Subsystem1 *subsystem1 = nullptr,
Subsystem2 *subsystem2 = nullptr) {
this->subsystem1_ = subsystem1 ?: new Subsystem1;
this->subsystem2_ = subsystem2 ?: new Subsystem2;
}
~Facade() {
delete subsystem1_;
delete subsystem2_;
}
/**
* Facade的方法是通向子系统复杂功能的方便捷径。
* 然而,客户端只能获得子系统功能的一小部分。
*/
std::string Operation() {
std::string result = "Facade initializes subsystems:\n";
result += this->subsystem1_->Operation1();
result += this->subsystem2_->Operation1();
result += "Facade orders subsystems to perform the action:\n";
result += this->subsystem1_->OperationN();
result += this->subsystem2_->OperationZ();
return result;
}
};
/**
* 客户端代码通过Facade提供的简单接口处理复杂的子系统。
* 当facade管理子系统的生命周期时,客户端甚至可能不知道子系统的存在。
* 这种方法使得复杂性被降低。
*/
void ClientCode(Facade *facade) {
// ...
std::cout << facade->Operation();
// ...
}
/**
* 客户端代码可能已经创建了子系统的一些对象。
* 在这种情况下,可以使用这些对象初始化Facade而不是让Facade创建新实例。
*/
int main() {
Subsystem1 *subsystem1 = new Subsystem1;
Subsystem2 *subsystem2 = new Subsystem2;
Facade *facade = new Facade(subsystem1, subsystem2);
ClientCode(facade);
delete facade;
return 0;
}
Facade initializes subsystems:
Subsystem1: Ready!
Subsystem2: Get ready!
Facade orders subsystems to perform the action:
Subsystem1: Go!
Subsystem2: Fire!
六、优缺点
优点
- 可以让自己的代码独立于复杂子系统。
缺点
- Facade可能成为与程序中所有类都耦合的上帝对象。
七、模式关系
- 外观模式为现有对象定义了一个新接口, 适配器模式则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。
- 当只需对客户端代码隐藏子系统创建对象的方式时, 可以使用抽象工厂模式来代替外观/门面模式。
- 享元模式展示了如何生成大量的小型对象, 外观/门面模式则展示了如何用一个对象来代表整个子系统。
- 外观/门面模式和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
- 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
- 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
- 外观类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
- 外观与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。