案例展示——Facade怎么用?
我们来模拟一下写信寄信的场景:首先要在纸上写上内容、然后在信封上写好收件人和地址、其次封好信封、最后将信件寄出去。在信息技术不发达的年代,上面的所有操作都需要个人自己去完成,而到了现在,我们需要做的已经很少。我们只需要将信的内容和收件人地址发给一个门面(相当于一个窗口:邮局),这个窗口将会封装诸如:写信、封信、送信等操作,而不需要我们亲自去执行。下面是该场景的类图设计:
类图元素分析:
-
ILetterProcess接口:信件处理的接口,里面定义了一些操作信件的基本方法:写信、写信封、封装信件、寄信等
-
LetterProcess:实现了ILetterProcess接口,实现了接口中的方法
-
PostOffice门面类:聚合了ILetterProcess中的操作,它将会代替高层模块去执行细节操作,高层模块只需要调用这个类的对象即可
-
Police附加操作类(可以不加):为了增强子系统的扩展性和复杂性而添加的,该类的功能主要是为了审核信件是否合法(在投递信件之前检查)
代码实现如下:
// 写信过程接口
public interface ILetterProcess {
// 信的内容
public void writeContext(String context);
// 写信封上的内容
public void fillEnvelope(String address);
// 把信放到信封里
public void letterIntoEnvelope();
// 邮递
public void sendLetter();
}
// 写信的过程
public class LetterProcess implements ILetterProcess{
// 写信
public void writeContext(String context) {
System.out.println("信的内容:" + context);
}
// 在信封上填写必要信息
public void fillEnvelope(String address) {
System.out.println("填写收件人地址及姓名:" + address);
}
// 把信放到信封中并放回去
public void letterIntoEnvelope() {
System.out.println("把信到放到信封中。。。");
}
// 塞到邮箱中邮递
public void sendLetter() {
System.out.println("邮递信件。。。");
}
}
// 检查信件是否有问题
public class Police {
// 检查信件有没有问题
public void checkLetter(ILetterProcess letterProcess) {
System.out.println(letterProcess + " 已经检查过了,没有任何问题!");
}
}
// 门面类:邮局
public class PostOffice {
// 持有 ILetterProcess 的对象
private ILetterProcess letterProcess = new LetterProcess();
// 检查信件是否有问题
private Police letterPolice = new Police();
// 写信、封装、投递一体化
public void sendLetter(String context, String address) {
// 帮你写信
letterProcess.writeContext(context);
// 写好信封
letterProcess.fillEnvelope(address);
// 检查信件是否有问题
letterPolice.checkLetter(letterProcess);
// 把信放到信封中
letterProcess.letterIntoEnvelope();
// 邮递信件
letterProcess.sendLetter();
}
}
// 在一个场景中运行代码
public class Client {
public static void main(String[] args) {
// 构造门面类:帮你写信、发邮件
PostOffice postOffice = new PostOffice();
// 定义地址和信的内容,传给邮局类;邮局类会帮你自动完成系列操作
// 收件人地址
String address = "-The Sky City,888,queen street; -Davi";
// 信的内容
String context = "my lover, how are you? I am missing you so much!";
// 发送信件
postOffice.sendLetter(context, address);
}
}
// 结果如下:
信的内容:my lover, how are you? I am missing you so much!
填写收件人地址及姓名:-The Sky City,888,queen street; -Davi
com.yuangh.designmodel.facade.LetterProcess@6d6f6e28 已经检查过了,没有任何问题!
把信到放到信封中。。。
邮递信件。。。
通过结果我们发现,对于一些附加操作,如:检查信件等。这些都可以通过门面类来进行整合处理,而高层模块不需要有任何改动,而且并没有改变子系统对外暴露的接口、方法,只改变了内部的处理逻辑,这就是门面设计模式。
深入分析——Facade是什么?
Facade的定义
门面模式也叫外观模式,是一种很常用的设计模式
定义: 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
门面模式注重统一的对象,也就是门面类,该类提供给其他调用者一个访问子系统的接口,除了这个接口不允许有任何访问子系统的行为发生。其关系图如下:
-
Facade门面类:客户端通过该类提供的接口去访问子系统
-
subsystem子系统:可以是一个或多个子系统。每个子系统不是一个单独的类,而是类的集合。门面类相当于子系统的客户端。
代码示例如下:
public class classA {
public void doSomethingA() {
// 业务逻辑
}
}
public class classB {
public void doSomethingB() {
// 业务逻辑
}
}
public class classC {
public void doSomethingC() {
// 业务逻辑
}
}
public class Facade {
// 被委托的对象
private ClassA a = new ClassA();
private ClassB b = new ClassB();
private ClassC c = new ClassC();
// 提供给外部访问的方法
public void methodA() {
this.a.doSomethingA();
}
public void methodB() {
this.b.doSomethingB();
}
public void methodC() {
this.c.doSomethingC();
}
}
Facade类不参与子系统的业务逻辑
例如下面的例子:在methodC()中,我们想要先调用ClassA的方法,再调用ClassC的方法
public class Facade {
// 被委托的对象
private ClassA a = new ClassA();
private ClassB b = new ClassB();
private ClassC c = new ClassC();
// 提供给外部访问的方法
public void methodA() {
this.a.doSomethingA();
}
public void methodB() {
this.b.doSomethingB();
}
public void methodC() {
this.a.doSomethingA();
this.c.doSomethingC();
}
}
这样设计结果固然没有问题,但是却让门面类参与到了子系统的业务逻辑中,这与门面类的封装性是相违背的。门面对象只是提供一个访问子系统的接口而已,它不应该也不能参与具体的业务逻辑,否则会产生一个倒依赖的问题:子系统必须依赖门面才能被访问。这严重违反了单一职责原则,同时也破坏了系统的封装性。下面进行修改:
public class Context {
// 委托处理
private ClassA a = new ClassA();
private ClassB c = new ClassC();
// 整合
public void complexMethod() {
this.a.doSomethingA();
this.c.doSomethingC();
}
}
public class Facade {
// 被委托的对象
private ClassA a = new ClassA();
private ClassB b = new ClassB();
private Context context = new Context();
// 提供给外部访问的方法
public void methodA() {
this.a.doSomethingA();
}
public void methodB() {
this.b.doSomethingB();
}
public void methodC() {
this.context.complexMethod();
}
}
将业务逻辑重新抽象出一个类,这样门面类将不会参与到子系统的逻辑中去,它只负责自己的工作,这才是良好的设计。
Facade的优点
-
减少系统间的相互依赖:所有的依赖都只是针对于门面类,与子系统无关
-
提高了灵活性
-
提高了安全性:访问控制权在门面类,要想访问子系统,必须通过门面类提供的接口,否则无法访问
Facade的缺点
- 不符合开闭原则:假如门面类出现问题,只能对其进行修改
Facade的使用场景
-
为一个复杂的模块或子系统提供一个可供外部访问的接口
-
子系统相对独立——外界对子系统的访问只要黑箱操作即可
参考
《设计模式之禅》