程序面试时经常会碰到系统分层的问题. 要你描述怎样分层, 分层的好处等.
而Java 有个外观模式(facade) 正能帮组我们对系统分层次.
一, 外观模式(Facade) 的定义
所谓外观模式, 子系统中的一组接口提供1个一致的界面, 此模式定义了1个高层接口, 这个接口使得这1个子系统更加容易使用.
注意, 这里的接口并不是java的interface的意思, 毕竟我那本教材不是for Java的.
名词解释:
子系统: 我们可以认为是因为是一组类, 这些类具有类似的功能(方法).
一组接口: 子系统中, 每个类都有1个功能类似的方法, 这些方法的组合就是一些接口. 注意这些方法很可能不是继承自同1个interface, 甚至这些类没有任何联系, 是不同公司发布的产品.
一致的界面: 1个在子系统之上的类, 这个类对子系统的类以及"一组接口", 进行归纳.
1个高层接口: 上面那个界面的1个方法(), 它能根据条件调用子系统的其中1个类的方法.
其实稍稍有些经验的程序猿就知道客户端只需访问那个"一致的界面" 就ok了.
二, 1个快递系统例子.
我们知道快递的运输方式有很多种.
比如下面的3个类
空运, 火车运输, 和船运.
public class ShipEx {
public void sExpress(){
System.out.println("Expressed by ship!");
}
}
public class AirEx {
public void aExpress(){
System.out.println("Expressed by plane!");
}
}
public class TrainEx {
public void tExpress(){
System.out.println("Expressed by train!");
}
}
可见, 三个类, 它们之间毫无关联, 但是它们都有1个功能类似的方法.
我们就可以把这个三个类看成1个子系统了.
好了, 假如这时有1个人(Client)想把1件物品快递出去了. 那么它应该找那种快递呢?
假如他要求3天内运到, 那么他只能找空运.
但是如果物品大于100公斤, 他就不会找空运, 因为空运很贵.
也就是说, 每种快递方法都有些条件限制的.
有时我们会把条件写在客户端里.
例如:
int period = 3;
int weight = 100;
if (period < 5 & weight <= 100){
new AirEx().aExpress();
}
//....
我们来列出这种写法的缺点:
缺点1:
实际上运输的条件限制很复杂, 包括价格, 时间, 距离, 物品重量, 物品类型(例如电池不能上飞机)等等.
把规则写在客户端每1个客户都必须清楚这些规则? 这是不可能的.
除非那个客户是做淘宝的..
缺点2(迪米特法则):
因为客户能直接找各种运输工具, 则代表这些方法必须对所有人开放.
违反了Law of Demeter(具体可以参考我上一篇博文).
现实上估计每人会直接找航空公司去空运吧? 航空公司也不会对个人开放运输业务.
缺点3(紧耦合):
就上面那个实现了, 一旦某间公司规则修改, 例如国庆期间, 空运费用半价..
那么就必须通知所有客户了..
程序猿就必须所有客户端类(客户端也有很多种)的代码... 否则会判断错误.
无层次的UML:
对于上面的例子, 我们假设有3个客户端类.那么每个客户端对有可能访问3种运输方式, 那么UML图看起来就很混乱了:
是不是有一种感觉,
对运输类的改动都需要很大的成本....
对了, 有人问, 把规则写在每1个运输方式类里面?
则代表客户什么也不懂, 必须一个一个去问, 比如现实上你去先去航空公司问这个炸弹能不能寄, 然后再去铁路部去问..
这也是不科学的.
三, 对快递系统使用外观模式(Facade)
那么对上面的例子如何修改呢.
很简单, 加上1个界面类就ok了, 这个类就是快递公司.
例如现实上, 你要寄一样物品, 只需找发快递公司就ok了, 它会清楚所有的快递规则.
你只需要列出你的要求(参数), 快递公司根据你的参数以及规则自然会帮你选择空运, 陆运还是船运.
例如我添加1个类
ExCompany:
public class ExCompany {
public void Express(int period, int weight){
if (weight <= 100 && period <= 3){
new AirEx().aExpress();
return;
}
if (weight <= 1000){
new TrainEx().tExpress();
return;
}
new ShipEx().sExpress();
}
}
这个基面类对, 子系统的一组接口(各种Express方法)进行了整理.
那么客户端的代码十分简介了:
int period = 5;
int weight = 100;
new ExCompany().Express(period, weight);
我们在看看这种设计模式的优点:
优点1:
客户无需关心运输规则, 有需要的话去问快递公司就ok了.或者你直接把你的需求告诉快递公司, 公司会根据规则帮你选择运输方式.
IT点来讲就是客户端代码很简洁.
优点2(迪米特法则):
子系统的方法可以对外封闭, 也就是说只让界面类去访问就ok了.客户端甚至没必要知道子系统的存在.
现实上你也很可能不关心你的商品是如何运过来的, 一定时间内能运到就ok了.
优点3(松耦合):
无论运输方式的算法如何修改, 只需要修改界面类的接口(对应方法)就ok了, 对客户端类毫无影响.也就是说这个系统很方便地进行维护和扩展.
有层次的UML:

看起来是不是对系统分了层次, 不同层次之间的类必须通过接口来互相访问, 它们的关系好像也没有那么复杂了.
四, 面试层次的问题.
其实上面的UML图只有两个层次, 接口是不算的.
实际项目中, 我们一般会将程序分成3层:
1. 数据访问层 -> 这些包括了Connection, DB 读写的东西
2. 业务逻辑层 -> 各种业务类
3. 界面表示层 -> 用户见到的界面
它们3个层之间会有各种接口来通讯.
至于优缺点, 大家可以按照上面的回答就可以了.
当然, 实际编程中, 如果能完美地实现3个层次并不简单, 需要很扎实的功力啊.