天天看点

设计模式(6)—— 结构型 ——外观(Facade)

简介

  • 定义:又叫门面模式,提供一个统一的接口,用来访问子系统中的一群接口。
  • 解释:外观模式定义了一个高层接口,让子系统更容易被使用。
  • 类型:结构型
  • 适用场景:
    • 子系统越来越复杂,外观模式能够提供简单的调用接口。
    • 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用。
  • 优点:
    • 简化调用过程,无需了解深入子系统,防止带来风险
    • 减少系统依赖,松散耦合
    • 更好地划分访问层次
    • 符合迪米特法则,即最少知道原则
  • 缺点:
    • 增加,扩展子系统的行为容易引入风险
    • 不符合开闭原则
  • 相关设计模式
    • 外观模式和中介模式
    • 外观模式和单例模式
    • 外观模式和抽象工厂模式

代码实现

业务场景:在网上书店,我们假定购买一本书系统需要经过三个流程:
  • 身份校验(CheckUser)。检测用户是否登陆,没有登陆则强制要求用户先登录
  • 支付校验(Payment)。检查金额数量,余额等,并进行在线支付。
  • 物流系统(Logistics)。支付成功后,交由物流系统处理,并实时显示物流信息等操作。

下面是相关的业务流程示意图:

设计模式(6)—— 结构型 ——外观(Facade)

从上面示意图,很容易理解外观模式中所谓的 “又称为门面模式,它提供一个统一的接口,用来访问子系统中的一群接口” 。各个service就是底层子系统的一群接口。这个”门面“既对外保持访问子系统的统一接口,对内又能访问子系统中的一群接口。

下面查看具体代码实现:

首先是最简单的两个实体类User,Book,为了展现外观设计模式,对具体的实现作了一定的简化。

用户类: 很简单的实现,仅仅定义两个简单字段isLogin,remainingMoney分别表示用户登录状态和账户余额。

/**
 * 用户类。
 * 这里为演示方便,做简单处理:定义两个字段。
 * isLogin:标识用户是否登陆
 * remainingMoney:标识用户的余额
 */
public class User {

    // 用户登录标志。这里默认用户是登陆的
    public static boolean isLogin = true;

    // 简单初始化用户的余额为 46 元
    private static double remainingMoney = 46;

    public static double getRemainingMoney() {
        return remainingMoney;
    }

    public static void consume(double price){

        if( price > remainingMoney ){
            throw new RuntimeException("消费金额不能大于余额");
        }

        remainingMoney -= price;
    }

}
           

定义简单的商品类,因为我们的业务场景是网上书店。定义一个Book类。同样简单起见我们只使用了两本书作为例子演示。一本书大于用户的默认余额,另外一本书小于用户的默认余额。

/**
 * 定义一个商品类。
 * 简单地定义两个字段:商品名称,价格
 */
public class Book {

    private String name;
    private double price;

    /*
    getter 和 setter 方法
     */

    public Book(String name){

        this.name = name;

        //为简单起见,如下方式定义书的价格

        if( "设计模式".equals(name) ) {
            this.price = 35;
        } else if( "算法设计".equals(name) ) {
            this.price = 49;
        } else {
            //默认书的价格46
            this.price = 46;
        }

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}
           

下面直接查看测试类Test。从整体上查看这个外观的接口:

public class Test {
    public static void main(String[] args) {
        // 业务场景:现在我买了一本书,它叫《设计模式》(它35元)

        // 1. 买一本书:
        Book book = new Book("设计模式");
        //Book book1 = new Book("算法设计");

        // 2. 提交系统。系统进行购物逻辑处理
        int resCode = ShoppingService.shopping(book);
        //int resCode2 = ShoppingService.shopping(book1);

        // 下面是对购物中系统内部出现的错误,进行相应的处理。
        // 这里就不具体实现了

    }
}
           

可以看到,这跟现实生活中的例子一样:我们只需要接触我们所买的书——

new Book("设计模式")

,然后直接提交给购物系统就行了——

ShoppingService.shopping(book)

下面查看外观模式中的对客户端的外观。也就是ShoppingService类。

/**
 * 定义子系统(Shopping购物系统)的外观模式类。
 * 外面通过这个类的接口就可以实现系统的功能了。
 *
 * 外界通过shopping的返回值能够确定shopping流程的一些错误
 */
public class ShoppingService {

    public static final int LOGIN_FAiLURE = 0;     // 登陆失败
    public static final int SUCCESS = 1;           // 购买成功
    public static final int PAYMENT_FAILURE = 2;   // 支付失败
    public static final int LOGISTICS_FAITURE = 3;     // 物流处理故障

    // 验证用户身份接口服务
    private static CheckUserService checkUserService = new CheckUserService();
    // 支付接口服务
    private static PaymentService paymentService = new PaymentService();
    // 物流接口服务
    private static LogisticsService logisticsService = new LogisticsService();

    // 对外的唯一接口
    public static int shopping(Book shopItem){

        // 1. 检查用户身份(登陆状态)
        if( checkUserService.isLogin() ){

            System.out.println("用户是登陆的.");

            // 2. 支付
            if( paymentService.pay( shopItem ) ) {

                // 3. 物流
                if( logisticsService.handleLogistics() ){
                    System.out.println("购物成功");
                    //物流处理顺利,这里我们直接返回正确代码:ShoppingService.SUCCESS
                    return ShoppingService.SUCCESS;

                } else { // 物流有错,返回错误代码

                    System.out.println("物流出错啦.");
                    return ShoppingService.LOGISTICS_FAITURE;

                }

            } else {  // 支付失败,返回错误代码

                System.out.println("支付失败, 余额不足.");
                return ShoppingService.PAYMENT_FAILURE;

            }
        } else {

            // 用户没登陆,返回错误代码
            System.out.println("用户登录失败.");
            return ShoppingService.LOGIN_FAiLURE;

        }
    }

}
           

暂时不讨论各个service成员的具体实现。我们这个外观类中保存了子系统中的所有接口,把它们作为类的成员变量。 然后外部调用此外观类的接口(这里是shopping函数),ShoppingService 外观类在处理外部请求任务的逻辑中,充分的利用了已经保存的子系统的一系列接口,实现相关逻辑。。

值得说明的是,shopping函数返回的代码:0,1,2,3与外观模式关系不大,不过对于很好的理解此类业务逻辑有帮助。

下面是子系统内部的三个接口的实现逻辑:

CheckUserService类检查用户是否登陆。

public class CheckUserService {

    /**
     * 简单验证用户身份:用户是否登陆
     * @return true/false 登陆与否
     */
    public boolean isLogin(){
        return User.isLogin;
    }

}
           

PaymentService类实现支付逻辑

/**
 * 支付服务,PaymentService
 */
public class PaymentService {

    public boolean pay(Book shopItem){

        // 如果余额还够的话,就能够进行支付
        if( shopItem.getPrice() <= User.getRemainingMoney() ){

            System.out.println("支付中...");
            User.consume( shopItem.getPrice() );
            System.out.println("支付成功...");

            // 支付成功,返回true;
            return true;
        }

        // 余额不足,支付失败,返回false
        return false;

    }

}

           

LogisticsService类实现物流相关的逻辑,这里仅仅简单的表示一下。

/**
 * 物流服务类。
 * 完成支付后,处理物流相关逻辑。
 */
public class LogisticsService {

    //处理物流,为演示方面直接返回true
    public boolean handleLogistics(){
        System.out.println("处理物流...");
        return true;
    }
}
           

UML图

下面是这个代码工程项目的UML图:

设计模式(6)—— 结构型 ——外观(Facade)

总结

值得注意的是,上面的代码分析是自顶向下的方式(自客户端代码实现到底层代码实现,自Test类到外观类再到子系统的接口群)。