门面设计模式

发布时间:2023年12月24日

5. 门面设计模式

5.1 原理与实现

门面模式,也叫外观模式,英文全称是 Facade Design Pattern。门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。

门面模式(Facade Pattern)是一种结构型设计模式,它为一组复杂的子系统提供了一个简单的接口,使得子系统更容易使用和理解。在 Java 中,门面模式通常使用一个门面类(Facade Class)来包装一个或多个复杂的子系统,使得客户端只需要和门面类交互,而不需要直接与子系统交互。

门面模式通常在以下情况下使用:

  • 将复杂的子系统进行抽象和封装:当一个系统变得复杂时,往往会由多个子系统组成。门面模式可以将这些子系统进行抽象和封装,提供一个简单的接口供客户端使用。
  • 隐藏子系统的复杂性:门面模式可以将子系统的复杂性隐藏起来,使得客户端不需要了解子系统的内部实现细节。
  • 提供一个简单的接口:门面模式可以为客户端提供一个简单的接口,使得客户端可以更容易地使用子系统。
  • 降低客户端与子系统的耦合:门面模式可以将客户端和子系统解耦,使得客户端不需要了解子系统的内部实现细节,也不需要直接和子系统交互。

案例:当一个系统需要与多个第三方服务进行交互时,可以使用门面模式来对这些服务进行封装,使得客户端只需要与一个门面类交互就可以完成对多个服务的调用。

首先,定义外部服务接口.

public interface ExternalService {
    void doSomething();
}
// 外部服务接口实现1
public class ExternalServiceImpl implements ExternalService{
    @Override
    public void doSomething() {
        System.out.println("ExternalServiceImpl.doSomething");
    }
}

// 外部服务接口实现2
public class ExternalServiceImpl2 implements ExternalService {
    @Override
    public void doSomething() {
        System.out.println("ExternalServiceImpl2.doSomething");

    }
}

定义门面类

public class Facade {
    private ExternalService externalService = new ExternalServiceImpl();
    private ExternalService externalService2 = new ExternalServiceImpl2();

    public void doSomething1() {
        externalService.doSomething();
    }

    public void doSomething2() {
        externalService2.doSomething();
    }
}

客户端使用门面,来调用外部子系统的实现。

public class FacadePatternTest {
    @Test
    public void testFacade() {
        // 门面实例
        Facade facade = new Facade();
        // 调用外部实现1
        facade.doSomething1();
        // 调用外部实现2
        facade.doSomething2();
    }
}

在这个示例中,ExternalService 是一个外部服务的接口,ExternalServiceImpl 和ExternalServiceImpl2 是这个接口的两个具体实现。Facade 是一个门面类,它将这两个外部服务进行封装,并提供了两个简单的方法 doSomething1 和doSomething2。客户端只需要与 Facade 类交互,就可以完成对这两个服务的调用。

案例二:当一个系统需要访问多个不同的数据库时,可以使用门面模式来对这些数据库进行封装,使得客户端只需要与一个门面类交互就可以完成对多个数据库的访问。

定义数据库接口

// 数据库接口
interface Database {
	void execute(String sql);
}
// MySQL数据库实现类
class MySQLDatabase implements Database {
    @Override
    public void execute(String sql) {
    	System.out.println("Executing " + sql + " in MySQL database");
    }
}

// Oracle数据库实现类
class OracleDatabase implements Database {
    @Override
    public void execute(String sql) {
    	System.out.println("Executing " + sql + " in Oracle database");
     }
}

定义门面类

// 门面类
class DatabaseFacade {
    private Database mysqlDatabase;
    private Database oracleDatabase;
    public DatabaseFacade() {
        mysqlDatabase = new MySQLDatabase();
        oracleDatabase = new OracleDatabase();
    }
    public void executeSQL(String sql, String databaseType) {
        if (databaseType.equals("MySQL")) {
        	mysqlDatabase.execute(sql);
        } else if (databaseType.equals("Oracle")) {
        	oracleDatabase.execute(sql);
        } else {
        	throw new IllegalArgumentException("Unknown database type: " + databaseType);
        }
    }
}

客户端使用门面,访问不同的数据库

public class Client {
    public static void main(String[] args) {
    	DatabaseFacade facade = new DatabaseFacade();
        facade.executeSQL("SELECT * FROM users", "MySQL");
        facade.executeSQL("SELECT * FROM customers", "Oracle");
    }
}

在这个示例中,Database 是一个数据库接口,MySQLDatabase 和OracleDatabase 是这个接口的两个具体实现。DatabaseFacade 是一个门面类,它将这两个数据库进行封装,并提供了一个 executeSQL 方法,用于执行 SQL 语句。客户端只需要与 DatabaseFacade 类交互,并指定要访问的数据库类型,就可以完成对这两个数据库的访问。

5.2 使用场景

5.2.1 源码中的应用

门面模式在 框架源码中有很多应用。以下是一些常见的使用场景:

  • JDBC:在 Java 中使用 JDBC 连接数据库时,通常会使用 DriverManager 来获取连接。DriverManager 就是一个门面类,它将多个数据库驱动进行封装,使得客户端只需要使用一个简单的接口就可以访问不同的数据库。
  • Spring 框架:在 Spring 框架中,ApplicationContext 就是一个门面类,它将Spring 中的各个组件进行封装,使得客户端可以更容易地使用 Spring 中的功能。
  • Servlet API:在 Servlet API 中,HttpServletRequest 和HttpServletResponse 接口就是门面类,它们将底层的网络通信进行封装,使得开发者可以更容易地编写 Web 应用程序。

5.2.2 支付场景

在生产环境中,门面模式常常用于封装复杂的第三方 API 或系统,以提供简单、易用的接口给客户端使用。一个具体的例子是,假设我们的系统需要与多个支付系统进行交互,而每个支付系统的接口和参数都不一样,这时候就可以使用门面模式来对这些支付系统进行封装,使得客户端只需要使用一个简单的接口就可以完成对多个支付系统的调用。

以下是支付系统的示例代码:

// 支付系统接口
interface PaymentSystem {
	void pay(double amount);
}
// 支付宝接口实现类
class AliPay implements PaymentSystem {
    @Override
    public void pay(double amount) {
    	System.out.println("支付宝支付:" + amount + "元");
    }
}
// 微信支付接口实现类
class WeChatPay implements PaymentSystem {
    @Override
    public void pay(double amount) {
    	System.out.println("微信支付:" + amount + "元");
    }
}

创建门面类,对各个支付渠道进行封装

// 门面类
class PaymentFacade {
    private PaymentSystem aliPay;
    private PaymentSystem weChatPay;
    public PaymentFacade() {
        aliPay = new AliPay();
        weChatPay = new WeChatPay();
    }
    public void payByAliPay(double amount) {
    	aliPay.pay(amount);
    }
    public void payByWeChatPay(double amount) {
    	weChatPay.pay(amount);
    }
}

客户端通过使用门面类调用不同的支付系统实现

// 客户端代码
public class Client {
    public static void main(String[] args) {
        PaymentFacade paymentFacade = new PaymentFacade();
        paymentFacade.payByAliPay(100.0);
        paymentFacade.payByWeChatPay(200.0);
    }
}

在这个示例中,PaymentSystem 是支付系统的接口,AliPay 和 WeChatPay 是这个接口的两个具体实现。PaymentFacade 是一个门面类,它将这两个支付系统进行封装,并提供了两个简单的方法 payByAliPay 和 payByWeChatPay。客户端只需要与PaymentFacade 类交互,就可以完成对这两个支付系统的调用。这种方式可以方便地支持新的支付系统的加入,同时也可以提高客户端的调用效率和代码可读性。

心得

接口粒度设计得太大,太小都不好。太大会导致接口不可复用,太小会导致接口不易用。在实际的开发中,接口的可复用性和易用性需要“微妙”的权衡。针对这个问题,我的一个基本的处理原则是,尽量保持接口的可复用性,但针对特殊情况,允许提供冗余的门面接口,来提供更易用的接口。

5.3 适配器模式和门面模式的区别

适配器模式门面模式都可以将不好用的接口适配成好用的接口,那他们之间又有什么区别呢?

(1)目标和目的

  • 适配器模式:适配器模式的目标是为了让现有接口能适配不同的类,让不兼容的接口能够一起工作。这是为了解决接口不兼容性问题,可以在没有修改源码的情况下使用现有的类。
  • 门面模式:门面模式的目标是为了提供一个统一的高级接口,隐藏子系统的复杂性。为了简化客户端对子系统的访问和使用。

(2)应用场景

  • 适配器模式:当我们希望将一个已经存在的类接口转换成另一个接口以供客户程序使用,或者当我们希望创建一个可重用的类,这个类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作时,可以使用适配器模式。
  • 门面模式:当我们希望为一个复杂的子系统提供一个简单的接口,或者客户程序与多个子系统之间存在大量的依赖关系时,可以使用门面模式。

(3)封装级别和复杂性

  • 适配器模式:适配器模式通常只包装一个类或对象,将一个接口转换为另一个接口。
  • 门面模式:门面模式包装了一整个子系统或者一组接口,将一系列复杂操作封装成一个简单的接口。
文章来源:https://blog.csdn.net/u013044713/article/details/135183104
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。