门面模式
,也叫外观模式,英文全称是 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 类交互,并指定要访问的数据库类型,就可以完成对这两个数据库的访问。
门面模式在 框架源码中有很多应用。以下是一些常见的使用场景:
在生产环境中,门面模式常常用于封装复杂的第三方 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 类交互,就可以完成对这两个支付系统的调用。这种方式可以方便地支持新的支付系统的加入,同时也可以提高客户端的调用效率和代码可读性。
心得
接口粒度设计得太大,太小都不好。太大会导致接口不可复用,太小会导致接口不易用。在实际的开发中,接口的可复用性和易用性需要“微妙”的权衡。针对这个问题,我的一个基本的处理原则是,尽量保持接口的可复用性,但针对特殊情况,允许提供冗余的门面接口,来提供更易用的接口。
适配器模式
和门面模式
都可以将不好用的接口适配成好用的接口,那他们之间又有什么区别呢?
(1)目标和目的
(2)应用场景
(3)封装级别和复杂性