文章目录
- 1 基本介绍
- 2 案例
- 2.1 Person 类
- 2.2 Computer 类
- 2.3 Player 类
- 2.4 TV 类
- 2.5 StudyManager 类
- 2.6 Client 类
- 2.7 Client 类运行结果
- 2.8 总结
- 3 各角色之间的关系
- 3.1 角色
- 3.1.1 SubSystem ( 子系统 )
- 3.1.2 Facade ( 窗口 )
- 3.1.3 Client ( 客户端 )
- 3.2 类图
- 4 注意事项
- 5 在源码中的使用
- 6 优缺点
- 7 适用场景
- 8 总结
1 基本介绍
外观模式(Facade Pattern),又称 门面模式,是一种 结构型 设计模式,主要用于 为复杂的子系统提供一个简单、统一的接口,使得外部程序能够更加方便地与这些子系统交互,无需关心使用子系统的具体细节。
2 案例
本案例分为两个操作:
- 学习:先播放指定类型的音乐,然后打开电脑的某个软件进行学习,学习完毕后关闭电脑的这个软件,并暂停播放音乐。
- 娱乐:观看电影。
2.1 Person 类
public class Person { // 人
public static void study(String name) {
System.out.println("- [" + name + "]开始学习");
}
public static void watchFilm(String name) {
System.out.println("- [" + name + "]开始看电影");
}
}
2.2 Computer 类
public class Computer { // 电脑
public static void openComputer() {
System.out.println("- 打开电脑");
}
public static void openSoftware(String softwareName) {
System.out.println("- 打开[" + softwareName +"]软件");
}
public static void closeSoftware(String softwareName) {
System.out.println("- 关闭[" + softwareName +"]软件");
}
public static void closeComputer() {
System.out.println("- 关闭电脑");
}
}
2.3 Player 类
public class Player { // 播放器
public static void playMusic(String musicType) {
System.out.println("- 播放[" + musicType + "]音乐");
}
public static void pauseMusic(String musicType) {
System.out.println("- 暂停播放[" + musicType + "]音乐");
}
}
2.4 TV 类
public class TV { // 电视机
public static void openTV() {
System.out.println("- 打开电视");
}
public static void play(String fileName) {
System.out.println("- 播放[" + fileName + "]电影");
}
public static void closeTV() {
System.out.println("- 关闭电视");
}
}
2.5 StudyManager 类
public class StudyManager { // 学习管理者
public static void study(String name, String musicType, String softwareName) {
System.out.println("以下是学习部分:");
Player.playMusic(musicType);
Computer.openComputer();
Computer.openSoftware(softwareName);
Person.study(name);
Computer.closeSoftware(softwareName);
Computer.closeComputer();
Player.pauseMusic(musicType);
}
public static void relax(String name, String filmName) {
System.out.println("以下是娱乐部分:");
TV.openTV();
TV.play(filmName);
Person.watchFilm(name);
TV.closeTV();
}
}
2.6 Client 类
public class Client { // 客户端,测试 StudyManager 的 study() 和 relax()
public static void main(String[] args) {
StudyManager.study("sam", "爵士", "IDEA");
StudyManager.relax("sam", "《加勒比海盗》");
}
}
2.7 Client 类运行结果
以下是学习部分:
- 播放[爵士]音乐
- 打开电脑
- 打开[IDEA]软件
- [sam]开始学习
- 关闭[IDEA]软件
- 关闭电脑
- 暂停播放[爵士]音乐
以下是娱乐部分:
- 打开电视
- 播放[《加勒比海盗》]电影
- [sam]开始看电影
- 关闭电视
2.8 总结
本案例将 学习 和 娱乐 所需的复杂操作封装到两个方法中,从而使客户端 Client
在执行这两个操作时只需要调用方法,无需知道具体的实现细节。如果没有封装,则需要将这些冗长的代码写在 Client
中重写一遍。
StudyManager
类就像一个 窗口 一样,窗口内部封装了调用 Person, Computer, Player, TV
类的逻辑,向外部只暴露两个接口来进行不同的操作。
3 各角色之间的关系
3.1 角色
3.1.1 SubSystem ( 子系统 )
该角色负责 完成自己的工作,无需知道 Facade 角色,也就是 SubSystem 角色不能调用 Facade 角色的方法。在本案例中,Person, Computer, Player, TV
类扮演本角色。
3.1.2 Facade ( 窗口 )
该角色负责 封装子系统的业务逻辑,向外部暴露简单的调用接口。在本案例中,StudyManager
类扮演本角色。
3.1.3 Client ( 客户端 )
该角色负责 调用 Facade 角色完成具体的业务。在本案例中,Client
类扮演本角色。
3.2 类图
注意:
- 虽然图中的
SubSystem
之间没有相互调用的关系,但实际上它们是可以相互调用的。 Client
只能直接调用Facade
中的方法,不能与SubSystem
有直接关联。
4 注意事项
- 合理划分访问层次:通过 合理地 使用外观模式,可以更好地划分系统的访问层次。外观类 作为 高层接口,为 客户端 提供了一个 清晰的 访问入口;而 子系统则作为 底层实现,负责 具体的业务逻辑处理。
- 避免过度使用:虽然外观模式有很多优点,但也不能 过度 或 不合理 地使用。如果每个子系统都使用外观模式进行封装,可能会导致 系统结构过于复杂,反而增加了系统的维护难度。
5 在源码中的使用
java.lang.Class
类中的 forName()
方法中使用到了外观模式,它为 加载和初始化类 提供了一个简单的接口,隐藏了类加载和初始化的复杂过程,客户端只需调用该方法并传入类的全限定名,即可获取到该类的Class
对象,而无需关心该类是如何被加载和初始化的。
// java.lang.Class 类中有如下的 forName():
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
// Reflection.getCallerClass() 如下所示:
public static native Class<?> getCallerClass(); // 是一个 native 方法,由其他语言实现
// Class 类的 forName0() 如下所示:
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException; // 是一个 native 方法,由其他语言实现
// ClassLoader.getClassLoader() 如下所示:
static ClassLoader getClassLoader(Class<?> caller) {
if (caller == null) {
return null;
}
return caller.getClassLoader0();
}
// Class 类的 getClassLoader0() 如下所示:
ClassLoader getClassLoader0() { return this.classLoader; }
// Class 类中定义的 classLoader 如下所示
private final ClassLoader classLoader;
6 优缺点
优点:
- 降低系统复杂性:外观模式为 子系统 中的一组接口提供了一个 统一的接口,使得 客户端 与 子系统 之间的交互变得 简单。客户端 只需要与 外观类 交互,而不需要了解 子系统 内部的复杂结构和实现细节,从而降低了系统的 复杂性。
- 提高系统的复用性:通过提供一个简单的接口,外观模式 使得子系统更加易于复用,客户端可以更轻松地达成目的。
- 解耦客户端与子系统:外观模式实现了 客户端 与 子系统 之间的解耦。客户端 不再 直接依赖 于 子系统 的具体实现,而是通过 外观类 与 子系统 交互。
- 提高了系统的 灵活性 和 可维护性:当 子系统 内部发生变化时,只要 外观类 的接口保持不变,客户端 代码就不需要修改。
- 支持系统的分层设计:在分层架构中,外观模式可以 为每一层提供一个清晰的接口,使得层与层之间的交互更加明确和简单。这有助于系统的整体架构设计和维护。
- 简化遗留系统的接口:对于复杂的遗留系统,外观模式可以提供一个简化的接口,使得新系统能够更容易地与遗留系统交互。这有助于在保持遗留系统 稳定性 的同时,提高新系统的 开发效率 和 复用性。
缺点:
- 增加系统的维护成本:当 子系统 中的接口发生变化时,可能需要修改 外观类 的实现。如果 外观类 封装了多个 子系统 的接口,那么这种修改可能会比较 复杂 和 耗时。
- 可能隐藏子系统的重要功能:由于 外观类 封装了 子系统 的多个接口,可能会隐藏一些 子系统 的重要功能。如果 客户端 需要直接访问这些功能,可能需要绕过 外观类,这可能会 破坏封装性。
- 可能不符合开闭原则:在某些情况下,如果 子系统 中的接口频繁发生变化,而 外观类 的设计又没有充分考虑到这种变化,那么 外观类 可能需要频繁地进行修改,这可能会 违反开闭原则(对扩展开放,对修改关闭)。
- 可能导致性能问题:如果 外观类 封装了多个 子系统 的接口,并且这些接口在性能上存在差异,那么 外观类 的实现可能需要仔细考虑性能优化问题,否则,可能会导致 性能瓶颈 或 不必要的性能损失。
7 适用场景
- 复杂系统的简化访问:在设计初期阶段,如果系统预计会变得复杂,可以考虑使用外观模式来简化对系统的访问。例如,在 经典的三层架构 中,可以在 数据访问层 和 业务逻辑层、业务逻辑层 和 表示层 之间建立外观类,以提供简单的接口,降低耦合度。
- 遗留系统的维护与扩展:对于难以维护和扩展的遗留系统,如果它包含重要的功能且新系统的开发必须依赖它,可以使用外观模式来简化其接口。通过 为遗留系统提供一个外观类,新系统可以与外观类交互,而无需直接了解遗留系统的复杂内部结构。
- 客户端 与 子系统 解耦:当 客户端 需要与多个 子系统 交互时,如果 直接依赖 这些 子系统 的具体实现,会导致 客户端 与 子系统 之间的 耦合度过高。使用外观模式可以 解耦 客户端 与 子系统 之间的关系,客户端 只需与 外观类 交互,而无需了解 子系统 的内部实现。
8 总结
外观模式 是一种 结构型 设计模式,它为子系统的复杂调用 提供统一接口,使客户端能够更加方便地达成目的,而无需关心使用子系统的具体细节,从而降低了系统的 耦合性,提高了系统的 可维护性。但是,在使用 外观模式 时,要保证 合理封装 子系统的复杂调用,如果封装不合理,会降低系统的可维护性,违背 开闭原则。