一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
模式动机
我们进行网上购物时,是不是一般都只和购物平台打交道呢?如:淘宝、京东、拼多多...
当我们下订单并且要求商家发货后,商家会联系仓库打包,银行扣款,物流运输,快递员配送等完成一系列繁杂的工作,这中间所要经历的步骤可能交叉复杂,但你会发现,我们这些客户无需关心它们,我们只关心购物平台这个接口,因为它才是我们真正关心的功能。
看,这就是「外观模式」的基本思维,它被大量运用与实际生活与软件系统中!
在日常的编码工作中,我们都会有意无意地大量使用外观模式。只要是高层模块需要调用多个子系统,我们都会自然而然地创建一个新的类来封装这些子系统,提供更精简的接口,让高层模块能更加容易地间接调用这些子系统的功能。尤其是现在各种第三方 SDK、开源类库,很大概率都会使用到外观模式。
定义
外观模式又称门面模式,它是一种对象结构型模式。
外观模式定义了一个将子系统的一组接口集成在一起的高层接口,并向外提供该高层接口,使得外界能够更轻松地使用交错复杂的子系统。
外观模式的本质就是封装交互,简化调用。
UML 类图
模式结构
外观模式包含如下角色:
Facade:外观角色提供了一种访问特定子系统功能的便捷方式。SubSystem:子系统不会意识到外观角色的存在,它们在系统内运作并且相互之间可直接进行交互。Client:使用外观角色代替对子系统对象的直接调用。
更多实例
文件加密
浅浅演示下代码
/* FileReader.java */
public class FileReader {
public String read(String source) throws IOException {
FileInputStream fis = new FileInputStream(source);
int length;
byte[] bytes = new byte[1024];
StringBuilder builder = new StringBuilder();
while ((length = fis.read(bytes)) != -1) {
builder.append(new String(bytes, 0, length));
}
fis.close();
return builder.toString();
}
}
/* CipherMachine.java */
public class CipherMachine {
public String encrypt(String plainText) {
return new StringBuilder(plainText).reverse().toString();
}
}
/* FileWriter.java */
public class FileWriter {
public void write(String encryptText, String destination) throws IOException {
FileOutputStream fos = new FileOutputStream(destination);
fos.write(encryptText.getBytes());
}
}
/* EncryptFacade.java */
public class EncryptFacade {
private FileReader reader;
private CipherMachine cipher;
private FileWriter writer;
public EncryptFacade() {
reader = new FileReader();
cipher = new CipherMachine();
writer = new FileWriter();
}
public void encrypt(String source, String destination) {
try {
String readText = reader.read(source);
String encryptText = cipher.encrypt(readText);
writer.write(encryptText, destination);
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例代码
Facade.java
public class Facade {
private SubSystemA sub1;
private SubSystemB sub2;
private SubSystemC sub3;
public Facade() {
sub1 = new SubSystemA();
sub2 = new SubSystemB();
sub3 = new SubSystemC();
}
public void wrapOperation() {
sub1.operationA();
sub2.operationB();
sub3.operationC();
}
}
SubSystemA.java
public class SubSystemA {
public void operationA() {
// TODO
}
}
SubSystemB.java
public class SubSystemB {
public void operationB() {
// TODO
}
}
SubSystemC.java
public class SubSystemC {
public void operationC() {
// TODO
}
}
Client.java
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.wrapOperation();
}
}
优缺点
✔降低了子系统与客户之间的耦合程度,使得子系统的变化不会影响到到客户端。
✔对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统易于使用。
✔降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程。
❌不能很好地限制客户端使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
❌在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了 “开闭原则”。
❌外观类可能成为程序中所有类都耦合的上帝对象。
适用场景
在以下情况推荐使用外观模式:
(1)当要为复杂子系统提供一个简单接口时。
(2)客户程序与多个子系统之间存在较大的依赖性。引入外观类可以将子系统与客户端以及其他子系统解耦,可以提高子系统的独立性和可移植性。
(3)如果需要将子系统组织为多层结构。
「外观模式」落地
Spring JDBC 中的外观模式
org.springframework.jdbc.support.JdbcUtils 对原生 JDBC 进行封装,我们在开发过程中直接使用即可,无需封装自己的 JDBC 接口。
Tomcat 中的外观模式
Tomcat 源码中也大量使用了外观模式
模式扩展
一个系统可以有多个外观类
在外观模式中,一般只需要一个外观类,并且为了节约系统资源,都将它设计为单例类。
当然这并不意味着整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责与一些特定的子系统交互,向用户提供相应的业务功能。
不要试图通过外观类为子系统增加新行为
外观模式用于为子系统提供一个集中化、简化的沟通渠道,不应该通过继承外观类以达到在子系统中加入新功能,这种做法是错误的。
外观模式与迪米特法则
外观模式创造出的外观对象,可以将客户端所涉及的协作伙伴的数量降到最低,使得客户端与子系统对象的相互作用被外观对象所取代。所以说,外观类充当了客户类与子系统类之间的 “朋友”(也就是沟通桥梁),外观模式就是 “迪米特法则” 的一个典型应用。
抽象外观类
外观模式最大的缺点就是违背 “开闭原则”,不过引入抽象外观类可以在一定程度上解决该问题,使得客户端针对抽象外观类编程。
对于新的业务需求,不修改原有外观类,而是增加一个新的具体外观类,由它来关联新的子系统对象,同时通过修改配置文件以达到不修改源代码并更改外观类的目的。
最后
❤️ 好的代码无需解释,关注「手撕设计模式」专栏,跟我一起学习设计模式,你的代码也能像诗一样优雅!
❤️ / END / 如果本文对你有帮助,点个「赞」支持下吧,你的支持就是我最大的动力!