设计模式之外观模式(Facade Pattern)

一.什么是外观模式?

简单的说,外观模式是用来简化接口的。

通常,我们觉得一个子系统不好用,可能是因为它提供的外部接口太接近低层组件,让我们用起来感到很麻烦。

因为我们不需要知道内部细节,我们只想要一个“一键完成”功能,调用子系统的某个方法,它可能替我们完成了图片预处理,而不是靠我们自己来调用灰度化方法、图像增强算法、噪声处理方法等等来一步步实现预处理。

如果子系统提供的接口太接近低层组件,不仅不易用,而且破坏了子系统的封装(想调用子系统就必须了解其各个低层组件,我们被迫知道了太多不应该知道的细节。。)

二.举个例子

假设有一个封装好的老式洗衣机,它提供了这些对外接口:

package FacadePattern;

/**
 * @author ayqy
 * 定义洗衣机接口
 */
public interface Washer {
	/*
	 * 公共部分
	 * */
	//连接电源
	public abstract boolean connectToPower();
	
	/*
	 * 洗涤部分
	 * */
	//打开左侧的洗涤舱
	public abstract void openLeftSide();
	//打开注水口
	public abstract void openWaterHole();
	//开始注水
	public abstract void startWaterInjection();
	//停止注水
	public abstract void stopWaterInjection();
	//开始旋转左侧洗涤舱
	public abstract void startWashing();
	//停止旋转左侧洗涤舱
	public abstract void stopWashing();
	
	/*
	 * 脱水部分
	 * */
	//打开右侧的脱水舱
	public abstract void openRightSide();
	//开始旋转右侧脱水舱
	public abstract void startDewatering();
	//停止旋转右侧脱水舱
	public abstract void stopDewatering();
	
	/*
	 * 排水部分省略。。
	 * */
}

没办法,它实在太老了,不能满足我们的现代生活,但我们又买不起新的自动化洗衣机,所以我们需要把它变成一个半自动的洗衣机,用节省下来的时间去写代码。。先看看我们是如何用老式洗衣机来洗衣服的:

package FacadePattern;

/**
 * @author ayqy
 * 使用老式洗衣机来洗衣服
 */
public class Washing implements Washer{

	public static void main(String[] args) {
		//创建洗衣机对象
		Washer washer = new Washing();
		//连接电源
		if(washer.connectToPower()){
			//打开洗涤舱
			washer.openLeftSide();
			/*装入脏衣服过程省略*/
			//打开注水口
			washer.openWaterHole();
			//开始注水
			washer.startWaterInjection();
			/*等待5分钟*/
			//停止注水
			washer.stopWaterInjection();
			/*添加洗涤剂过程省略*/
			//开始洗涤
			washer.startWashing();
			/*15分钟后停止*/
			washer.stopWashing();
			
			/*
			 * 脱水部分省略
			 * */
		}
	}

	/*
	 * 忽略下面自动生成的这些东西。。
	 * */
	@Override
	public boolean connectToPower() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void openLeftSide() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void openWaterHole() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void startWaterInjection() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void stopWaterInjection() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void startWashing() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void stopWashing() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void openRightSide() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void startDewatering() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void stopDewatering() {
		// TODO Auto-generated method stub
		
	}

}

仅仅演示了一个洗衣服的过程,我们就调用了那么多操作,而且我们必须知道这台洗衣机的内部细节,不然根本无法使用它。。洗衣服可能需要60分钟(注水 + 洗涤 + 脱水 + 排水),在这期间我们几乎什么事情也做不了,只能蹲在洗衣机旁边不停的操作机器

我想,我们可能需要一个遥控器,上面有2个按钮:

  • 洗涤
  • 脱水

然后我们洗衣服的过程会变成这样:

  1. 摁洗涤按钮,自动连接电源,自动打开洗涤舱,自动注水5分钟,自动开始洗涤15分钟
  2. 摁脱水按钮,自动打开脱水舱,自动脱水10分钟,自动排水,自动断开电源

这简直太棒了,我们可以在吃午餐前摁一下洗涤按钮,吃完之后去把衣服拿到右侧,再摁一下脱水按钮,然后去上班,下午回来之后把衣服晾起来就好了(当然,手动把衣服从左侧拿到右侧的过程是避免不了的,毕竟它太老了,想变成全自动洗衣机是不可能的。。)

来看看我们自己做的遥控器:

package FacadePattern;

/**
 * @author ayqy
 * 遥控器(也就是所谓的外观)
 */
public class WasherFacade {
	private Washer washer;

	public WasherFacade(Washer washer){
		this.washer = washer;
	}

	/**
	 * 自动洗涤
	 */
	public void washing(){
		//连接电源
		if(washer.connectToPower()){
			//打开洗涤舱
			washer.openLeftSide();
			/*装入脏衣服过程省略*/
			//打开注水口
			washer.openWaterHole();
			//开始注水
			washer.startWaterInjection();
			/*等待5分钟*/
			//停止注水
			washer.stopWaterInjection();
			/*添加洗涤剂过程省略*/
			//开始洗涤
			washer.startWashing();
			/*15分钟后停止*/
			washer.stopWashing();
		}
	}
	
	/**
	 * 自动脱水
	 */
	public void dewashing(){
		/*判断是否已连接电源过程省略*/
		//打开右侧的脱水舱
		washer.openRightSide();
		//开始旋转右侧脱水舱
		washer.startDewatering();
		//停止旋转右侧脱水舱
		washer.stopDewatering();
		
		/*
		 * 排水过程省略
		 * 切断电源过程省略
		 * */
	}
}

有了遥控器之后,我们是这样洗衣服的:

package FacadePattern;

/**
 * @author ayqy
 * 测试应用了外观模式的半自动洗衣机(利用遥控器)
 */
public class Test implements Washer{
	
	public static void main(String[] args) {
		//创建老式洗衣机
		Washer washer = new Test();
		//创建外观(遥控器)
		WasherFacade facade = new WasherFacade(washer);
		//按下洗涤按钮开始洗衣服
		facade.washing();
		/*把衣服拿到另一侧*/
		//按下脱水按钮开始脱水
		facade.dewashing();
	}

	/*
	 * 忽略下面这些自动生成的东西。。
	 * */
	@Override
	public boolean connectToPower() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void openLeftSide() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void openWaterHole() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void startWaterInjection() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void stopWaterInjection() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void startWashing() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void stopWashing() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void openRightSide() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void startDewatering() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void stopDewatering() {
		// TODO Auto-generated method stub
		
	}

}

简直轻松惬意,不过仔细一看,不就是定义了一个方法来封装方法调用嘛,有什么了不起的?与在我们的新项目代码中建立定义两个方法负责洗涤和脱水有什么区别吗?当然有,不要着急。

三.外观模式的优点

  1. 低耦合

    先看看我们的命名方式,遥控器叫做WasherFacade,如果要划分模块,它应该与Washer放在一起吧。没错,通过定义Facade,我们成功解耦了Washer与我们的代码,意味着一旦Washer发生变更,我们直接修改Facade就好了,而不是在我们冗长的项目代码里寻找某两个方法

  2. 保护了子系统的封装

    我们并没有打开封装好的Washer去修改,而是添加了一些代码来简化Washer的接口,以前调用者对Washer的内部很了解,但现在它对Washer几乎一无所知(除构造方法外)

  3. 适用于含有多个不同对象的子系统

    例子中的Washer只是一个单一对象,好像应该由Washer本身提供这样的简单接口。但如果要实现日常起居的半自动化,我们会面对多个对象,比如门,窗,窗帘,电视,微波炉,洗衣机,电脑等等。我们希望一键准备早餐(自动开灯,自动开启微波炉加热),一键午睡(自动关门,自动拉上窗帘,自动熄灭灯光)等等功能,外观模式同样适用:我们只需要让外观多持有几个具体对象就好了

  4. 有效地简化了子系统的接口

    之前洗衣服需要蹲在洗衣机旁不停的操作,现在我们可以“一键完成”了,这才是我们想要的简单易用的接口

  5. 满足“最少知识原则”

    我们做到了“只和朋友交谈”,我们的新系统只认识Facade,只和它交谈,而不是跑去和子系统中的一大堆低层组件交谈

四.总结

外观模式,用来为复杂的子系统提供简单易用的高层接口。

当你纠结于很多低层组件得不到解脱的时候,不妨去做一个遥控器,我想,你可能确实需要它。。