外观设计模式
外观设计模式(Facade Design Pattern)是一种结构型设计模式 ,旨在为复杂系统提供简单的接口。该模式通过为子系统提供一个高级接口,使得客户端与子系统之间的交互更加简单。外观设计模式通常被用来隐藏系统的复杂性,并且提供一个简化的接口,以便客户端能够更容易地使用系统。
概述
有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
定义:
外观模式又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是"迪米特法则 "的典型应用
结构
以下是外观设计模式的主要组成部分和工作原理:
-
外观(Facade):外观是客户端与系统之间的接口,它向客户端提供了一个简单的接口,隐藏了系统的复杂性。外观通常是一个单一的类或接口。
-
子系统(Subsystems):子系统是系统中的各个模块或组件,它们实现了系统的各个功能。这些子系统可能是相互独立的,但它们在外观的统一接口下协同工作。
-
客户端(Client):客户端是使用外观模式的应用程序的组成部分。它通过外观接口与系统进行交互,而不需要了解系统的内部实现细节。
外观设计模式的工作原理如下:
- 客户端通过外观接口与系统交互。
- 外观将客户端的请求委派给相应的子系统。
- 子系统执行请求,并将结果返回给外观。
- 外观将结果返回给客户端。
具体案例:
案例一:
让我们以一个简单的家庭影院系统 为例,来演示外观设计模式的使用。假设这个家庭影院系统包括DVD播放器 、投影仪 和音响系统。
首先,我们定义子系统的类:
java
// DVD播放器
class DVDPlayer {
public void on() {
System.out.println("DVD Player is on");
}
public void play(String movie) {
System.out.println("Playing movie: " + movie);
}
public void off() {
System.out.println("DVD Player is off");
}
}
// 投影仪
class Projector {
public void on() {
System.out.println("Projector is on");
}
public void setInput(DVDPlayer dvdPlayer) {
System.out.println("Setting DVD Player input to Projector");
}
public void off() {
System.out.println("Projector is off");
}
}
// 音响系统
class SoundSystem {
public void on() {
System.out.println("Sound System is on");
}
public void setVolume(int volume) {
System.out.println("Setting volume to " + volume);
}
public void off() {
System.out.println("Sound System is off");
}
}
然后,我们定义外观类来封装这些子系统,提供一个简单的接口给客户端:
java
// 外观类
class HomeTheaterFacade {
private DVDPlayer dvdPlayer;
private Projector projector;
private SoundSystem soundSystem;
public HomeTheaterFacade(DVDPlayer dvdPlayer, Projector projector, SoundSystem soundSystem) {
this.dvdPlayer = dvdPlayer;
this.projector = projector;
this.soundSystem = soundSystem;
}
public void watchMovie(String movie, int volume) {
dvdPlayer.on();
projector.on();
projector.setInput(dvdPlayer);
soundSystem.on();
soundSystem.setVolume(volume);
dvdPlayer.play(movie);
}
public void endMovie() {
dvdPlayer.off();
projector.off();
soundSystem.off();
}
}
最后,我们使用客户端来调用外观接口,而不需要了解子系统的复杂性:
java
public class Client{
public static void main(String[] args) {
// 创建子系统对象
DVDPlayer dvdPlayer = new DVDPlayer();
Projector projector = new Projector();
SoundSystem soundSystem = new SoundSystem();
// 创建外观对象
HomeTheaterFacade homeTheater = new HomeTheaterFacade(dvdPlayer, projector, soundSystem);
// 调用外观接口观看电影
homeTheater.watchMovie("Avatar", 10);
// 结束电影
homeTheater.endMovie();
}
}
这样,客户端只需调用外观的 watchMovie
方法来观看电影,而无需直接与DVD播放器、投影仪和音响系统进行交互。外观类 HomeTheaterFacade
封装了系统的复杂性,提供了一个简单的接口给客户端使用。
案例二:
我们定义一个智能家电控制系统,小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。
子系统类:
java
/**
* @author OldGj 2024/02/27
* @version v1.0
* @apiNote 子系统 - 空调类
*/
public class AirCondition {
public void on(){
System.out.println("打开空调...");
}
public void off(){
System.out.println("关闭空调...");
}
}
java
/**
* @author OldGj 2024/02/27
* @version v1.0
* @apiNote 子系统角色 - 电灯类
*/
public class Light {
public void on(){
System.out.println("打开电灯...");
}
public void off(){
System.out.println("关闭电灯...");
}
}
java
/**
* @author OldGj 2024/02/27
* @version v1.0
* @apiNote 子系统 - 电视类
*/
public class TV {
public void on(){
System.out.println("打开电视...");
}
public void off(){
System.out.println("关闭电视...");
}
}
外观类:
java
/**
* @author OldGj 2024/02/27
* @version v1.0
* @apiNote 外观角色 - 智能语言助手类
*/
public class SmartAppliancesFacade {
private final TV tv;
private final Light light;
private final AirCondition airCondition;
public SmartAppliancesFacade() {
tv = new TV();
airCondition = new AirCondition();
light = new Light();
}
public void say(String message) {
if(message.contains("打开")) {
on();
} else if(message.contains("关闭")) {
off();
} else {
System.out.println("我还听不懂你说的!!!");
}
}
// 一键开启家电
private void on(){
tv.on();
airCondition.on();
light.on();
}
// 一键关闭家电
private void off(){
tv.off();
airCondition.off();
light.off();
}
}
通过以上外观类对家电的封装,客户端只需要直接调用外观类的方法,即可操作子系统中的功能。
外观设计模式的优点包括:
- 简化接口 :外观提供了一个简单的接口,隐藏了系统的复杂性,使得客户端更容易使用系统。
- 解耦 :外观将客户端与子系统之间解耦 ,使得子系统的变化不会影响到客户端。
- 提高可维护性:外观将系统的实现细节封装起来,使得系统更容易维护和修改。
然而,外观设计模式也有一些缺点:
- 限制灵活性:外观定义了一个固定的接口,可能会限制系统的灵活性。
- 增加了类和接口的数量:引入外观可能会增加系统中的类和接口的数量,使得系统更加复杂。
- 不符合开闭原则,修改很麻烦
使用场景
外观设计模式通常在以下情况下使用:
-
简化复杂系统:当系统包含多个复杂的子系统,并且客户端需要与这些子系统进行交互时,可以使用外观设计模式来简化客户端的操作。外观类封装了子系统的复杂性,提供了一个简单的接口给客户端。
-
隐藏实现细节:当客户端不需要了解系统的内部实现细节,只需要一个简单的接口来与系统交互时,可以使用外观设计模式。外观类隐藏了系统的实现细节,使得客户端不需要直接与子系统进行交互。
-
解耦客户端和子系统:当客户端与多个子系统之间存在耦合关系时,可以使用外观设计模式来解耦客户端和子系统。外观类充当了客户端与子系统之间的中介,使得客户端与子系统之间的耦合度降低。
-
提供简化的接口:当系统的接口过于复杂或冗长时,可以使用外观设计模式来提供一个简化的接口给客户端使用。外观类将系统的复杂性隐藏起来,提供了一个简单的接口给客户端。
-
封装变化:当系统中的子系统可能发生变化时,可以使用外观设计模式来封装这些变化。客户端不需要知道系统内部的变化,只需要通过外观类来与系统交互。
总的来说,外观设计模式适用于需要简化复杂系统接口、隐藏实现细节、解耦客户端和子系统以及提供简化接口等情况。通过使用外观设计模式,可以提高系统的易用性和可维护性,同时降低客户端与子系统之间的耦合度。
源码中的外观模式:
在Tomcat中,RequestFacade对象使用了外观设计模式。在Servlet容器中,RequestFacade是一个包装类,它封装了HttpServletRequest对象,并提供了简化的接口给Servlet类使用。RequestFacade隐藏了HttpServletRequest对象的复杂性,使得Servlet类不需要直接与HttpServletRequest对象进行交互,而是通过RequestFacade对象来访问请求的信息。
通过使用外观设计模式,RequestFacade对象提供了一个简化的接口给Servlet类使用,同时封装了HttpServletRequest对象的实现细节,使得Servlet类不需要关心HttpServletRequest对象的具体实现。这样,RequestFacade对象简化了Servlet类的开发,提高了系统的可维护性和可扩展性。
在现实的源代码中,外观模式可能存在于各种形式中,具体取决于应用程序的结构和需求。以下是一些常见的源码示例,展示了外观模式在不同上下文中的应用:
-
Java类库中的网络请求发送 :
在Java类库中,像Apache HttpClient或者HttpURLConnection等网络请求库的使用中,可能会使用外观模式。这些库封装了底层的网络请求细节,提供了一个简单的接口给开发者来发送HTTP请求,隐藏了网络请求的复杂性。
-
Spring框架中的JdbcTemplate :
在Spring框架中,JdbcTemplate是一个用于简化JDBC编程的类,它封装了底层的JDBC细节,提供了一组简单的方法来执行SQL查询和更新操作。开发者可以通过JdbcTemplate来与数据库进行交互,而不需要直接使用JDBC API。
-
Android开发中的ContentResolver :
在Android开发中,ContentResolver是用于访问ContentProvider提供的数据的类。ContentResolver封装了底层的ContentProvider细节,提供了一组简单的方法来查询、插入、更新和删除数据,隐藏了ContentProvider的复杂性。
-
日志记录框架中的Logger :
在日志记录框架(如Log4j、Logback等)中,Logger封装了底层的日志记录细节,提供了一组简单的方法来记录日志信息。开发者可以通过Logger来记录日志,而不需要直接操作日志记录器、格式化器等组件。
-
文件操作库中的FileUtils :
在文件操作库(如Apache Commons IO)中,FileUtils封装了底层的文件操作细节,提供了一组简单的方法来操作文件和目录。开发者可以通过FileUtils来复制、移动、删除文件等,而不需要直接使用File类和流操作。
在这些情况下,外观模式封装了底层的复杂性,提供了一个简单的接口给开发者使用,从而简化了开发过程,提高了代码的可维护性和可读性。