外观模式(Facade) - 为系统分层次


程序面试时经常会碰到系统分层的问题.  要你描述怎样分层, 分层的好处等.


而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个层次并不简单, 需要很扎实的功力啊.































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值