在软件开发中,随着系统的不断迭代,模块会越来越多,模块之间的依赖关系也会变得错综复杂。这不仅会增加开发难度,还会让系统的维护和扩展变得棘手。而外观模式就像一位 “前台接待员”,为复杂的系统提供一个简洁统一的接口,让外部与系统的交互变得简单高效。。
外观模式是设计模式三大类中的一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易被使用。简单来说,就是在复杂的子系统外面套上一个 “壳”,这个 “壳” 封装了子系统内部的复杂逻辑和交互细节,外部只需与这个 “壳” 进行交互,无需关心子系统内部的具体实现。例如,我们使用电脑时,只需按下开机键,电脑就会完成主板通电、CPU 启动、内存加载系统等一系列复杂操作,而我们无需了解这些内部步骤,这里的 “开机键” 就相当于外观模式中的统一接口。
外观模式的核心原理是封装与委派。它通过引入一个外观类(Facade),将子系统中各个模块的接口进行整合和封装。当外部客户端需要调用子系统的功能时,不需要直接与子系统中的各个模块交互,而是调用外观类提供的接口,外观类再将请求委派给子系统中相应的模块进行处理。其基本结构包含以下几个部分:
(1)外观类:这是外观模式的核心,它知道子系统中各个模块的功能,为客户端提供统一的接口,负责将客户端的请求转发给合适的子系统模块。
(2)子系统模块:实现子系统的具体功能,它们不知道外观类的存在,也不与外观类进行交互,仅在接收到外观类的委派请求时执行相应操作。
(3)客户端:通过外观类与子系统进行交互,无需了解子系统内部的复杂结构。
外观模式在软件开发中有着重要的作用,主要体现在以下几个方面:
(1)简化接口调用:客户端无需记住子系统中众多模块的接口,只需与外观类的统一接口交互,大大降低了客户端使用子系统的难度。
(2)降低耦合度:外观类将客户端与子系统隔离开来,使得客户端与子系统之间的依赖关系变为客户端与外观类之间的依赖,减少了系统的耦合度,便于后续的维护和扩展。
(3)隐藏子系统细节:子系统内部的实现细节对客户端是透明的,客户端不需要知道子系统是如何工作的,只需关注外观类提供的功能是否满足需求。
(4)便于子系统的管理:当子系统内部发生变化时,只要外观类的接口保持不变,客户端就不需要做任何修改,降低了因子系统变化对客户端造成的影响。
作为一种设计模式,外观模式带来了许多优点,例如:
(1)提高易用性:为复杂的子系统提供了简单易用的接口,让客户端能够快速上手使用子系统,减少了学习成本。
(2)降低耦合度:将客户端与子系统的直接交互转变为与外观类的交互,降低了两者之间的耦合,符合 “迪米特法则”(最少知识原则)。
(3)增强系统灵活性:子系统内部的模块可以自由修改和扩展,只要不影响外观类的接口,就不会对客户端产生影响,提高了系统的灵活性。
(4)便于维护:由于客户端与子系统之间通过外观类进行交互,当子系统出现问题时,只需排查外观类与子系统之间的交互,缩小了问题排查的范围,便于系统的维护。
但同时,其也有各种各样的缺点,如:
(1)引入冗余:如果客户端需要使用子系统中一些较为特殊的功能,而外观类没有提供相应的接口,客户端可能还是需要直接与子系统交互,这时候外观模式就显得有些冗余。
(2)外观类可能变得复杂:随着子系统功能的不断增加,外观类需要整合的接口也会越来越多,可能会导致外观类变得庞大而复杂,增加了外观类的维护难度。
(3)限制了客户端的灵活性:外观类提供的是统一的接口,可能无法满足客户端的个性化需求,客户端不能像直接与子系统交互那样灵活地使用子系统的功能。
下面通过一个家庭影院的例子来演示外观模式的实现。
一个家庭影院包含投影仪、音响、播放器等设备,要观看电影需要依次开启这些设备并进行相应设置,操作较为复杂。我们可以使用外观模式,创建一个家庭影院外观类来简化这些操作。
// 子模块类
// 投影仪
class Projector {
public void on() {
System.out.println("投影仪开启");
}
public void off() {
System.out.println("投影仪关闭");
}
public void setMode() {
System.out.println("投影仪设置为电影模式");
}
}
// 音响
class SoundSystem {
public void on() {
System.out.println("音响开启");
}
public void off() {
System.out.println("音响关闭");
}
public void setVolume(int volume) {
System.out.println("音响音量设置为:" + volume);
}
}
// 播放器
class Player {
public void on() {
System.out.println("播放器开启");
}
public void off() {
System.out.println("播放器关闭");
}
public void play() {
System.out.println("播放器开始播放电影");
}
}
// 外观类
// 家庭影院
class HomeTheaterFacade {
private Projector projector;
private SoundSystem soundSystem;
private Player player;
public HomeTheaterFacade(Projector projector, SoundSystem soundSystem, Player player) {
this.projector = projector;
this.soundSystem = soundSystem;
this.player = player;
}
// 观看电影的统一接口
public void watchMovie() {
System.out.println("准备观看电影...");
projector.on();
projector.setMode();
soundSystem.on();
soundSystem.setVolume(8);
player.on();
player.play();
System.out.println("电影开始播放");
}
// 结束观看电影的统一接口
public void endMovie() {
System.out.println("电影结束,关闭设备...");
player.off();
soundSystem.off();
projector.off();
System.out.println("所有设备已关闭");
}
}
// 客户端
public class Client {
public static void main(String[] args) {
// 创建子系统对象
Projector projector = new Projector();
SoundSystem soundSystem = new SoundSystem();
Player player = new Player();
// 创建外观类对象
HomeTheaterFacade homeTheater = new HomeTheaterFacade(projector, soundSystem, player);
// 通过外观类接口观看电影
homeTheater.watchMovie();
// 电影结束,关闭设备
homeTheater.endMovie();
}
}
其运行结果大致为:
准备观看电影...
投影仪开启
投影仪设置为电影模式
音响开启
音响音量设置为:8
播放器开启
播放器开始播放电影
电影开始播放
电影结束,关闭设备...
播放器关闭
音响关闭
投影仪关闭
所有设备已关闭
从代码和运行结果可以看出,客户端只需调用家庭影院外观类的watchMovie和endMovie方法,就可以完成观看电影的一系列复杂操作,无需直接与投影仪、音响、播放器等子系统模块交互,大大简化了客户端的操作。