外观模式是一种简化复杂子系统的软件设计模式,它通过提供一个统一的高层接口来隐藏子系统的内部细节,使得客户端调用更加便捷。就像餐厅的点餐系统,顾客无需了解厨房操作,只需通过系统点餐。这降低了系统耦合度,提高了可维护性。外观模式将复杂流程简化,优化了用户体验,同时方便系统扩展与修改。
定义
外观模式(Facade Pattern)是一种常用的软件设计模式,它为子系统中的一组接口提供了一个统一的高层接口,从而使得子系统更加容易使用,外观模式定义了一个高层接口,这个接口使得客户端可以方便地调用子系统中的一组接口,而无需关心子系统内部的复杂结构,这样可以降低系统的耦合度,提高系统的可维护性。
举一个业务中形象的例子来说明外观模式,假设你是一家餐厅的顾客,你想要点一份餐食,餐厅提供了各种美食,如汉堡、披萨、炸鸡等,但你不需要知道这些美食是如何制作的,你只需通过餐厅的点餐系统(即外观)来选择你想要的餐食,然后支付费用,等待片刻,餐厅就会为你准备好美食并送到你的桌上。
在这个例子中,餐厅的点餐系统就相当于外观模式的外观类,它简化了顾客点餐的流程,顾客无需了解厨房内部的各种复杂操作(如食材准备、烹饪、调味等),只需通过点餐系统这一高层接口来完成点餐,而厨房内部的各种操作则相当于子系统中的一组接口,它们协同工作以完成顾客的点餐请求。
通过外观模式,餐厅将复杂的点餐流程简化为了一个简单的操作,提高了顾客的就餐体验,同时,餐厅还可以根据需要对点餐系统进行扩展或修改,而不会影响到顾客的使用。
代码案例
下面我们通过反例代码和正例代码来分别演示外观模式的实现。
下面是一个未使用外观模式的反例代码,如下。在未使用外观模式的情况下,客户端代码通常需要直接与子系统中的多个组件进行交互,这可能导致客户端代码变得复杂且难以维护,如下代码所示:
// 子系统组件A
public class SubSystemA {
public void operationA() {
System.out.println("SubSystemA operationA");
}
}
// 子系统组件B
public class SubSystemB {
public void operationB() {
System.out.println("SubSystemB operationB");
}
}
// 子系统组件C
public class SubSystemC {
public void operationC() {
System.out.println("SubSystemC operationC");
}
}
然后,我们编写一个客户端类,该类需要直接调用这些子系统组件的方法:
// 客户端类,未使用外观模式
public class ClientWithoutFacade {
public static void main(String[] args) {
// 创建子系统组件实例
SubSystemA subSystemA = new SubSystemA();
SubSystemB subSystemB = new SubSystemB();
SubSystemC subSystemC = new SubSystemC();
// 客户端直接调用子系统的方法来完成某项任务
subSystemA.operationA(); // 调用子系统A的方法
subSystemB.operationB(); // 调用子系统B的方法
subSystemC.operationC(); // 调用子系统C的方法
// 输出结果表示任务完成
System.out.println("Task is completed without Facade Pattern.");
}
}
运行上述客户端代码会得到以下输出:
SubSystemA operationA
SubSystemB operationB
SubSystemC operationC
Task is completed without Facade Pattern.
尽管此代码能够正确运行并完成任务,但它展示了未使用外观模式时的一些缺点:
- 客户端代码与子系统紧密耦合,必须了解子系统的具体实现和组成。
- 如果子系统的内部结构发生变化,客户端代码可能需要进行大量修改。
- 客户端代码需要处理与多个子系统组件的交互,增加了复杂性和出错的可能性。
下面是一个使用外观模式的正例代码,当使用外观模式时,我们创建一个外观类,它为客户端提供了一个简化的接口来访问子系统中的一组功能,如下代码:
// 子系统组件A
public class SubSystemA {
public void operationA() {
System.out.println("SubSystemA performing operationA.");
}
}
// 子系统组件B
public class SubSystemB {
public void operationB() {
System.out.println("SubSystemB performing operationB.");
}
}
// 子系统组件C
public class SubSystemC {
public void operationC() {
System.out.println("SubSystemC performing operationC.");
}
}
接下来,创建外观类来封装对子系统组件的调用:
// 外观类,为客户端提供简化的接口来访问子系统
public class Facade {
// 子系统组件的实例,可以被外观类封装和管理
private SubSystemA subSystemA;
private SubSystemB subSystemB;
private SubSystemC subSystemC;
// 构造函数,初始化子系统组件
public Facade() {
this.subSystemA = new SubSystemA();
this.subSystemB = new SubSystemB();
this.subSystemC = new SubSystemC();
}
// 外观方法,客户端通过这个方法访问子系统功能
public void performComplexOperation() {
System.out.println("Facade initiating complex operation...");
subSystemA.operationA(); // 调用子系统A的方法
subSystemB.operationB(); // 调用子系统B的方法
subSystemC.operationC(); // 调用子系统C的方法
System.out.println("Facade completed complex operation.");
}
}
最后,编写客户端代码来使用外观类:
// 客户端类,使用外观模式来访问子系统功能
public class ClientWithFacade {
public static void main(String[] args) {
// 创建外观对象,客户端只需要知道外观类,不需要了解子系统的具体实现和组成
Facade facade = new Facade();
// 客户端通过外观对象的方法来访问子系统功能,无需直接与子系统组件交互
facade.performComplexOperation(); // 执行复杂的操作,实际上是由多个子系统组件协同完成的
// 输出结果表示任务完成,客户端代码简洁且易于维护
System.out.println("Client task is completed with Facade Pattern.");
}
}
运行上述客户端代码会得到以下输出:
Facade initiating complex operation...
SubSystemA performing operationA.
SubSystemB performing operationB.
SubSystemC performing operationC.
Facade completed complex operation.
Client task is completed with Facade Pattern.
这个正例代码展示了使用外观模式时的一些优点:
- 客户端代码与子系统解耦,只依赖于外观类。
- 外观类提供了简化的接口,隐藏了子系统的复杂性。
- 客户端代码更加简洁、清晰,易于理解和维护。
核心总结
外观模式总结
外观模式是一种常用的软件设计模式,它通过为子系统提供一个统一的接口,简化了客户端与子系统的交互。它能够降低系统的复杂性和耦合度,使得客户端代码更加简洁和易于维护,它将客户端与子系统的实现细节隔离开来,客户端只需与外观类交互,无需了解子系统的内部结构,提高了系统的可扩展性和可重用性。但是外观模式可能会增加额外的代码量,因为需要创建新的外观类来封装子系统的接口,同时,如果子系统过于庞大或复杂,外观类可能会变得臃肿,难以维护,此外,过度使用外观模式可能导致系统结构变得不清晰,增加理解和开发的难度。因此在使用外观模式时,应根据实际情况权衡其优缺点,当子系统较复杂且需要简化客户端接口时,可以考虑使用外观模式,但应注意避免过度使用,以免引入不必要的复杂性和维护成本,同时,应合理划分子系统和外观类的职责,保持代码的清晰和可维护性。
疑问
外观模式和装饰模式的区别?
外观模式和装饰模式都是设计模式中的常用类型,但它们在目的和用法上存在显著的差异。首先外观模式主要是为了提供一个统一的接口来简化子系统间的交互,它隐藏了子系统的复杂性,使得客户端只需与外观类打交道,而无需了解子系统的具体实现和组成,外观模式更像是一个“中介”或“协调者”,它将客户端的请求转发给相应的子系统,并将结果返回给客户端,通过这种方式,外观模式降低了系统的耦合度,提高了客户端的易用性。而装饰模式则是一种动态地给一个对象添加一些额外的职责的方式。它提供了一种灵活的机制来扩展对象的功能,而无需修改其本身的代码,装饰模式通过创建一个包装对象(即装饰器),将原始对象包裹在其中,并在需要时动态地添加额外的功能或行为,这使得我们可以在不改变原始对象结构的情况下,动态地扩展其功能,从而提高了系统的灵活性和可扩展性。
总体来说,外观模式侧重于简化客户端与子系统的交互,而装饰模式则侧重于在不修改对象结构的前提下动态地扩展其功能。