要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,门面模式提供一个高层次的接口,使得子系统更易于使用。
门面对象只是提供一个访问子系统的一个路径而已,它不应该不能参与具体的业务逻辑,把多变的业务逻辑的变化封闭在子系统内部,无论你如何变化,对外界的访问者来说,都还是同一个门面(只提供路径)。
一、我要投递信件
1. 写信过程接口
public interface ILetterProcess {
//首先要写信的内容
public void writeContext(String context);
//其次写信封
public void fillEnvelope(String address);
//把信放到信封中
public void letterInotoEnvelope();
//然后邮递
public void sendLetter();
}
2. 写信过程的实现
public class LetterProcessImpl implements ILetterProcess{
//写信
public void writeContext(String context) {
System.out.println("填写信的内容..."+context);
}
//在信封上填写必要的信息
public void fillEnvelope(String address) {
System.out.println("填写收件人地址及姓名..."+address);
}
//把信放到信封中,并封好
public void letterInotoEnvelope() {
System.out.println("把信放到信封中...");
}
//塞到邮箱中,邮递
public void sendLetter() {
System.out.println("邮递信件");
}
}
3. 现代化邮局
public class ModenPostOffice {
private ILetterProcess letterProcess = new LetterProcessImpl();
private Police police = new Police();
//写信、封装、投递,一体化
public void sendLetter(String context,String address){
//帮你写信
letterProcess.writeContext(context);
//写好信封
letterProcess.fillEnvelope(address);
//警察要检查信件了
police.checkLetter(letterProcess);
//把信放到信封中
letterProcess.letterInotoEnvelope();
//邮递信件
letterProcess.sendLetter();
}
}
4. 信件检查类
public class Police {
//检查信件,检查完毕后警察在信封上盖个戳,此信无病毒
public void checkLetter(ILetterProcess letterProcess){
System.out.println(letterProcess+" 信件已经检查过了...");
}
}
5. 场景类
public class Client {
public static void main(String[] args) {
//现代化的邮局,有这项服务,邮局名称叫Hell Road
ModenPostOffice helloRoadPostOffice = new ModenPostOffice();
//你只要把信的内容和收集人地址给他,他会帮你完成一系列的工作
//定义一个地址
String address = "Happy Road No.666";
//信的内容
String context = "Hello,It's me,do you know who I am?";
//你给我送吧
helloRoadPostOffice.sendLetter(context, address);
}
}
这正是我们设计所需要的模式,不改变子系统对外暴露的接口、方法,只改变内部的处理逻辑,其他兄弟模块的调用产生了不同的结果,这就是门面模式。
二、门面模式的定义
定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,门面模式提供一个高层次的接口,使得子系统更易于使用。
Facade门面角色
客户端可以调用这个角色的方法,此角色知晓子系统的所有功能和责任。
subsystem子系统角色
subsystem是子系统所有类的简称,它可能代表一个类,也可能 代表几十个对象的集合。
可以同时有一个或者多个子系统,每一个子系统都不是一个单独的类,而是一个类的集合,子系统并不知道门面的存在。对于子系统而言,门面仅仅是另外一个客户端而已。
1. 子系统
public class ClassA {
public void doSomethingA(){
//业务逻辑
}
}
&
public class ClassB {
public void doSomethingB(){
//业务逻辑
}
}
&
public class ClassC {
public void doSomethingC(){
//业务逻辑
}
}
2. 门面对象
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();
}
}
三、门面模式的应用
1.优点
减少系统的相互依赖:如果不使用门面模式,外界访问直接深入到子系统内部,相互之间是一种强耦合关系,这是系统设计不能接受的,门面模式的出现将所有的依赖都是对门面对象的依赖,与子系统无关。
提高了灵活性:不管子系统内部如何变化,只要不影响到门面对象,任你自由活动。
提高了安全性:想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,你休想访问到。
2. 缺点
不符合开闭,对修改关闭,对扩展开放,一旦在系统投产后发现有一个小错误,如果完全遵从开闭原则,没办法解决,继承、重写都不行,唯一能做的一件事是修改门面角色的代码,这个风险比较大。
3. 使用场景
为一个复杂的模块或子系统提供一个供外界访问的接口
子系统相对独立——外界对子系统的访问只要黑箱操作即可,比如:利息计算
预防低水平人员带来的风险扩散,只能在指定的子系统 中开发,然后再提供门面接口进行访问操作
四、门面模式的注意事项
1. 一个子系统可以有多个门面
一般一个子系统只要有一个门面就足够了,在什么情况下可以有多个门面呢?
1)门面已经庞大到不能忍受的程度,比如一个数据库操作的门面可以拆分为查询门面、删除门面、更新门面。
2)子系统可以提供不同的访问路径,比如:ClassA/ClassB/ClassC中,有的模块可以访问ABC,有的只能访问AC。
public class Facade2 {
//引用原有的门面
private Facade facade = new Facade();
//对外提供唯一的访问子系统的方法
public void methodB(){
this.facade.methodB();
}
}
将新门面委托给了已经存在的门面对象Facade进行处理,为什么要使用委托而不再编写一个委托到子系统的方法呢?那是因为在面向对象的编程中,尽量保持相同的代码只编写一遍,避免以后到处修改相似代码的悲剧。
2. 门面不参与子系统内的业务逻辑
如下面这段代码是错误的,(当我要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 ClassC 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 ClassC c = new ClassC();
private Context context = new Context();
//提供给外部访问的方法
public void methodA(){
this.a.doSomethingA();
}
public void methodB(){
this.b.doSomethingB();
}
public void methodC(){
this.context.complexMethod();
}
}
通过这样的封装,门面对象又不参与业务逻辑了。在门面模式中,门面角色应该是稳定,它不应该经常变化,一个系统 一旦投入运行它就不应该被改变,它是一个系统对外的接口。但是业务逻辑是会经常变化的,我们已经
把它的变化封闭在子系统内部,无论你如何变化,对外界的访问者来说,都还是同一个门面。
五、最佳实践
一个子系统比较复杂,比如算法或者业务比较复杂,就可以封装出一个或多个门面出来 。