「设计模式」🏰外观模式(Facade)

633 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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 源码中也大量使用了外观模式

模式扩展

一个系统可以有多个外观类

在外观模式中,一般只需要一个外观类,并且为了节约系统资源,都将它设计为单例类

当然这并不意味着整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责与一些特定的子系统交互,向用户提供相应的业务功能。

不要试图通过外观类为子系统增加新行为

外观模式用于为子系统提供一个集中化、简化的沟通渠道,不应该通过继承外观类以达到在子系统中加入新功能,这种做法是错误的。

外观模式与迪米特法则

外观模式创造出的外观对象,可以将客户端所涉及的协作伙伴的数量降到最低,使得客户端与子系统对象的相互作用被外观对象所取代。所以说,外观类充当了客户类与子系统类之间的 “朋友”(也就是沟通桥梁),外观模式就是 “迪米特法则” 的一个典型应用。

抽象外观类

外观模式最大的缺点就是违背 “开闭原则”,不过引入抽象外观类可以在一定程度上解决该问题,使得客户端针对抽象外观类编程。

对于新的业务需求,不修改原有外观类,而是增加一个新的具体外观类,由它来关联新的子系统对象,同时通过修改配置文件以达到不修改源代码并更改外观类的目的。

最后

👆上一篇:「设计模式」🎀装饰模式(Decorator)

👇下一篇:「设计模式」🧩享元模式(Flyweight)

❤️ 好的代码无需解释,关注「手撕设计模式」专栏,跟我一起学习设计模式,你的代码也能像诗一样优雅!

❤️ / END / 如果本文对你有帮助,点个「赞」支持下吧,你的支持就是我最大的动力!