「补课」进行时:设计模式(16)——简单又实用的门面模式

1. 前文汇总

「补课」进行时:设计模式系列java

2. 从银行转帐提及

当咱们在银行进行转帐操做的时候,整个操做流程咱们能够简化为帐户 A 扣费,而后帐户 B 增长余额,最后转帐操做成功。编程

这两个操做缺一不可,同时又不能颠倒顺序。设计模式

简单定义一个转帐的接口 ITransfer安全

public interface ITransfer {
    // 首先发起转帐
    void start(String amount);
    // 帐户 A 进行扣费
    void subtractionA();
    // 帐户 B 增长金额
    void addB();
    // 转帐完成
    void end();
}

而后增长一个接口实现类:架构

public class TransferImpl implements ITransfer {
    @Override
    public void start(String amount) {
        System.out.println(String.format("帐户 A 开始向帐户 B 进行转帐: %s 元。", amount));
    }

    @Override
    public void subtractionA() {
        System.out.println("帐户 A 扣费成功");
    }

    @Override
    public void addB() {
        System.out.println("帐户 B 余额增长成功");
    }

    @Override
    public void end() {
        System.out.println("转帐完成");
    }
}

来一个测试类:ide

public class Test {
    public static void main(String[] args) {
        ITransfer transfer = new TransferImpl();
        transfer.start("1000");
        transfer.subtractionA();
        transfer.addB();
        transfer.end();
    }
}

最后运行的结果以下:测试

帐户 A 开始向帐户 B 进行转帐: 1000 元。
帐户 A 扣费成功
帐户 B 余额增长成功
转帐完成

咱们回过头来看看这个过程,它与高内聚的要求相差甚远,更不要说迪米特法则、接口隔离原则了。this

若是咱们要进行转帐操做,那么咱们必需要知道这几个步骤,并且还要知道它们的顺序,一旦出错,转帐操做就没法完成,这在面向对象的编程中是极度地不适合,它根本就没有完成一个类所具备的单一职责。设计

那怎么办呢?这时候银行柜台出现了,咱们只须要把需求告诉银行柜台,柜台会直接帮咱们完成转帐操做。3d

银行柜台:

public class BankCounter {
    private ITransfer transfer = new TransferImpl();
    // 转帐操做一体化
    public void transferAmount(String amount) {
        transfer.start(amount);
        transfer.subtractionA();
        transfer.addB();
        transfer.end();
    }
}

接下来修改一下测试类:

public class Test1 {
    public static void main(String[] args) {
        BankCounter counter = new BankCounter();
        counter.transferAmount("1000");
    }
}

和刚才的执行结果同样,可是整个测试类却简化了不少,只要关心和银行柜台进行交互就行,彻底不用本身操心以前的帐户 A 扣费,再给帐户 B 加余额,可是,每次转帐就这么直接转帐有点不大安全,假如帐户 A 的余额根本不足转帐的费用,那么就不该该转帐成功。

增长一个余额校验类 Balance 对帐户余额进行校验:

public class Balance {
    Boolean checkBalance() {
        System.out.println("帐户余额校验成功");
        return true;
    }
}

这时候,测试类无需改动,只需修改银行柜台类就能够:

public class BankCounter {
    private ITransfer transfer = new TransferImpl();
    private Balance balance = new Balance();
    // 转帐操做一体化
    public void transferAmount(String amount) {
        transfer.start(amount);
        transfer.subtractionA();
        // 增长余额校验
        if (balance.checkBalance()) {
            transfer.addB();
            transfer.end();
        }
    }
}

这里只增长了一个余额校验类,而且对转帐的过程进行了修改,这个过程对于咱们来说是彻底透明的,咱们彻底不须要关心转帐的过程,这个过程由银行柜台所有帮咱们办好了。

高层模块没有任何改动,可是帐户的余额已经被检查过了,不改变子系统对外暴露的接口、方法,只改变内部的处理逻辑,其余兄弟模块的调用产生了不一样的结果。

是否是很是简单,没错,这就是门面模式或者说外观模式。

3. 门面模式

3.1 定义

门面模式(Facade Pattern)也叫作外观模式,是一种比较经常使用的封装模式,其定义以下:

Provide a unified interface to a set of interfaces in a subsystem.Facadedefines a higher-level interface that makes the subsystem easier to use.(要求一个子系统的外部与其内部的通讯必须经过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。)

3.2 通用类图

门面模式注重「统一的对象」,也就是提供一个访问子系统的接口,除了这个接口不容许有任何访问子系统的行为发生,其通用类图:

是的,类图就这么简单,可是它表明的意义但是异常复杂,Subsystem Classes是子系统全部类的简称,它可能表明一个类,也可能表明几十个对象的集合。甭管多少对象,咱们把这些对象所有圈入子系统的范畴:

再简单地说,门面对象是外界访问子系统内部的惟一通道,无论子系统内部是多么杂乱无章,只要有门面对象在,就能够作到「金玉其外,败絮其中」。咱们先明确一下门面模式的角色。

  • Facade 门面角色:此角色知晓子系统的全部功能和责任。通常状况下,本角色会将全部从客户端发来的请求委派到相应的子系统去,也就说该角色没有实际的业务逻辑,只是一个委托类。
  • subsystem 子系统角色:能够同时有一个或者多个子系统。每个子系统都不是一个单独的类,而是一个类的集合。子系统并不知道门面的存在。对于子系统而言,门面仅仅是另一个客户端而已。

3.3 通用代码

子系统:

// 
public class ClassA {
    public void doSomethingA() {
        // 执行逻辑 A
    }
}

public class ClassB {
    public void doSomethingB() {
        // 执行逻辑 A
    }
}

public class ClassC {
    public void doSomethingC() {
        // 执行逻辑 A
    }
}

门面类:

public class Facade {
    private ClassA classA = new ClassA();
    private ClassB classB = new ClassB();
    private ClassC classC = new ClassC();
    public void methodA() {
        this.classA.doSomethingA();
    }
    public void methodB() {
        this.classB.doSomethingB();
    }
    public void methodC() {
        this.classC.doSomethingC();
    }
}

4. 注意

有一点须要注意的是:门面不参与子系统内的业务逻辑。

这句话怎么理解?举一个简单的例子:

把上面的通用代码稍微改一下,在 methodC() 方法上先调用 ClassAdoSomethingA() 方法,而后再调用 ClassCdoSomethingC() 方法,修改后的门面类以下:

public class Facade {
    private ClassA classA = new ClassA();
    private ClassB classB = new ClassB();
    private ClassC classC = new ClassC();
    public void methodA() {
        this.classA.doSomethingA();
    }
    public void methodB() {
        this.classB.doSomethingB();
    }
    public void methodC() {
        this.classA.doSomethingA();
        this.classC.doSomethingC();
    }
}

很是简单,只是在 methodC() 方法中增长了 doSomethingA() 方法的调用,能够这样作吗?

我相信在大多数的平常开发中,咱们不少时候都是直接这么写了,这么写有什么问题么?

固然有,由于这种作法让门面对象参与了业务逻辑,门面对象只是提供一个访问子系统的一个路径而已,它不该该也不能参与具体的业务逻辑,不然就会产生一个倒依赖的问题:子系统必须依赖门面才能被访问。

那么在这种状况下能够怎么处理呢?

也很简单,建立一个封装类,封装完毕后提供给门面对象:

public class Context {
    private ClassA classA = new ClassA();
    private ClassC classC = new ClassC();
    // 复杂的业务操做
    public void complexMethod() {
        this.classA.doSomethingA();
        this.classC.doSomethingC();
    }
}

这个封装类存在的价值就是产生一个复杂的业务规则 complexMethod() ,而且它的生存环境是在子系统内,仅仅依赖两个相关的对象,门面对象经过对它的访问完成一个复杂的业务逻辑,最后咱们经过门面模式进行调用的时候直接调用封装类:

public class Facade1 {
    private ClassA classA = new ClassA();
    private ClassB classB = new ClassB();
    private Context context = new Context();
    public void methodA() {
        this.classA.doSomethingA();
    }
    public void methodB() {
        this.classB.doSomethingB();
    }
    public void methodC() {
        this.context.complexMethod();
    }
}

经过这样一次封装后,门面对象又不参与业务逻辑了,在门面模式中,门面角色应该是稳定,它不该该常常变化,一个系统一旦投入运行它就不该该被改变,它是一个系统对外的接口,你变来变去还怎么保证其余模块的稳定运行呢?可是,业务逻辑是会常常变化的,咱们已经把它的变化封装在子系统内部,不管你如何变化,对外界的访问者来讲,都仍是同一个门面,一样的方法——这才是架构师最但愿看到的结构。

相关文章
相关标签/搜索