引言
外观模式(Facade)其实在开发过程中使用评率十分频繁,或间接或直接使用,尤其是在当前各种第三方SDK 中,相当大的概率使用了外观模式,通过一个外观类使用的整个SDK的接口只有一个统一的高层接口,降低了用户对接成本,也对用户屏蔽了具体实现细节。
一、外观模式概述
外观模式(Facade Pattern)是一种简单的结构型设计模式,也有翻译为门面模式的,也是一种比较常用的封装模式,要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用(Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level
interface that makes the subsystem easier to use.),外观模式核心思想在于“统一的对象”,即是提供一个访问子系统的接口,除了这个接口不允许有任何访问子系统的行为发生(子系统是个概念,是所有类的简称,它可能代表一个类,也可能代表几十个对象的集合),外观对象是外界访问子系统内部的唯一通道,不管子系统内部是多么杂乱无章,所以外观模式的核心参与者有:外观对象和各种子系统角色。
Facade对象——客户端要想使用子系统的功能只能够通过这个对象去间接使用(因为客户端并不知道具体的子对象具体逻辑),此角色知晓子系统的所有功能,通常仅仅是承担着委托的作用,将所有从客户端发来的请求委派到相应的子系统去,即该角色没有实际的业务逻辑和功能实现,仅仅是一个委托类。
Subsystem子系统角色——本质上说这个角色没有任何的特殊的地方,放到其他模式下就是一个普通的子系统,所以该怎么实现还是怎么实现,不受Facade影响或限制。子系统并不知道外观对象的存在。对于子系统而言,外观对象仅仅是另外一个客户端而已
二、外观模式的优点和缺点及可用场景
1、外观模式的优点
减少系统的相互依赖,如果没有使用外观模式,外界访问直接深入到子系统内部,相互之间是是一种强耦合关系,这样的强依赖是系统设计所不能接受的,门面模式的出现就很好地解决了该问题,所有的依赖都是对门面对象的依赖,与子系统无关。
提高了灵活性,依赖减少了,灵活性自然提高了。不管子系统内部如何变化,只要不影响到外观对象,随意扩展
2、外观模式的缺点
外观模式最大的缺点就是不符合开闭原则,对修改关闭,对扩展开放,看看我们那个门面对象吧,它可是重中之重,一旦在系统投产后发现有一个小错误,你怎么解决?完全遵从开闭原则,根本没办法解决。继承?覆写?都顶不上用,唯一能做的一件事就是修改门面角色的代码,这个风险相当大,这就需要大家在设计的时候慎之又慎,多思考几遍才会有好收获。
3、外观模式的使用场景和注意事项
为一套复杂的子系统提供一个统一的简单的接口,子系统可能会因为不断更新升级而变得越来越复杂,使用外观模式可以提供一个简单的统一接口,对外隐蔽子系统的具体实现。
当需要构建一个有层次结构的子系统时,通过外观模式定义子系统中每一层的入口,如果子系统需要互相依赖可以通过Facade接口进行通信。
为一个复杂的模块或子系统提供一个供外界访问的接口
子系统相对独立——外界对子系统的访问只要黑箱操作即可
外观对象不应参与各子系统之间的业务逻辑
三、外观模式的实现
1、定义各子系统
package basis.facade;
public interface IModuleA {
public void doSthA();
}
package basis.facade;
public class ModuleA implements IModuleA {
@Override
public void doSthA() {
System.out.println("使用Module.doSthA()");
}
}
为了节省篇幅ModuleB、ModuleC没有进行抽象,此处实现也和ModuleA类似,实际开发中可以结合具体业务区实现
package basis.facade;
public class ModuleB{
public void doSthB() {
System.out.println("使用ModuleB.doSthB()");
}
}
2、定义Facade对象(高层接口)
package basis.facade;
/**
* 外观对象角色(所谓的高层接口),本例是某子系统由三个类构成,三个类之间互相独立,互不影响逻辑,没有任何访问限制
* @author Crazy.Mo
*/
public class SystemFacade {
//被委托的子系统
private IModuleA a = new ModuleA();
private ModuleB b = new ModuleB();
private ModuleC c = new ModuleC();
//提供给外部访问的方法
public void methodA(){
this.a.doSthA();
}
public void methodB(){
this.b.doSthB();
}
public void methodC(){
this.c.doSthC();
}
}
测试
package basis.facade;
public class Client {
public static void main(String[] args) {
ModuleFacade facade=new ModuleFacade();//对于客户端来说仅仅知道Facade角色,而并不知道具体的子系统中ModuleA等的类存在
facade.methodA();//通过facade角色对外开放出来的方法去使用子系统的功能
facade.methodB();
facade.methodC();
}
}
3、一个子系统也可以拥有多个外观对象(高层接口)
通常一个子系统只要有一个外观对象足够了,但是在某些具体情况下,也可以拥有多个外观对象(比如一个数据库操作的外观对象可以拆分为查询外观对象、删除外观对象、更新外观对象等),比如外观已经十分庞大臃肿或者子系统内部的某些对象的使用有限制,就可以增加一个新的外观角色,实现也很简单:委托给了已经存在的门面对象Facade进行处理,为什么要使用委托而不再编写一个委托到子系统的方法呢?那是因为在面向对象的编程中,尽量复用相同的代码,避免后期到处修改相似代码的尴尬。归根接地来说,外观对象就是提供给客户端使用的高层接口,可以根据情况对于某一类的用户封装对应的一个外观对象,也可以根据不同权限不同类的用户封装各自的外观对象。
package basis.facade;
public class SecFacade {
// 引用原有的门面
private SystemFacade facade = new SystemFacade();
// 对外提供唯一的访问子系统的方法
public void methodB() {
this.facade.methodB();
}
}
4、外观对象不宜参与子系统内部的逻辑交互
假如说在上面的例子中,要使用ModuleB里的功能得先调用ModuleA的功能,原来互相独立的ModuleA和ModuleB产生了依赖关系,此时外观对象该如何设计呢,很简单的一种就是直接在外观对象原来的methodC方法中增加对ModuleA方法的调用,但是这设计是非常不靠谱的,因为你已经让外观对象参与了业务逻辑,门面对象只是提供一个访问子系统的一个路径而已,它不应该也不能参与具体的业务逻辑,否则就会产生一个倒依赖的问题:子系统必须依赖门面才能被访问,这不仅违反了单一职责原则,同时也破坏了系统的封装性。正确的做法应该是再封装一个专门处理ModuleA和ModuleB交互逻辑的类,
/**
*定义一个业务规则complexMethod,并且它的生存环境是在子系统内,仅仅依赖两个相关的对象,外观对象通过对它的访问完成一个复杂的业务逻辑
*/
public class Wrapper {
//委托处理
private ModuleA a = new ModuleA();
private ModuleB b = new ModuleB();
//复杂的计算
public void complexMethod(){
this.a.doSthA();
this.b.doSthB();
}
}
对应的外观对象改为
package basis.facade;
public class WrapperFacade {
//被委托的子系统
private IModuleA a = new ModuleA();
private Wrapper b = new Wrapper();
private ModuleC c = new ModuleC();
//提供给外部访问的方法
public void methodA(){
this.a.doSthA();
}
public void methodB(){
this.b.complexMethod();
}
public void methodC(){
this.c.doSthC();
}
}
这样封装的好处在于外观对象不直接参与业务逻辑了,在外观模式中,外观角色应该是稳定,它不应该经常变化,一个系统一旦投入运行它就不应该被改变,它是一个系统对外的接口,但是业务逻辑是会经常变化的,而我们已经把它的变化封装在子系统内部,不会影响到系统功能的正常使用。
小结
外观模式是一个很好的封装方法,一个子系统比较复杂时,比如算法或者业务比较复杂,就可以封装出一个或多个外观对象出来,项目的结构简单,而且扩展性非常好。还有,对于一个较大项目,为了避免人员带来的风险,也可以使用外观模式,技术水平比较差的成员,尽量安排独立的模块,然后把他写的程序封装到一个外观对象里。