欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

门面模式(Facade Pattern)

程序员文章站 2022-05-04 15:34:55
...

近期参与项目研发,调接口时遇一事不爽,比如在调C方法前必须得先调A方法,成功后再调B方法拿到返回结果后,最后才可以调C方法,而且接口调用还会随需求变化而变更,比如可能还得再增加几个步骤......

 

门面模式(亦称外观模式Facade Pattern)的思想很好地解决了这一问题,当然,它的优点也不止如此。

 

场景举例

 

投递信件的过程是:首先写信的内容 -> 其次写信封 -> 把信放到信封里 -> 然后邮递

 

接口类:ILetterProcess

/**
 * @author xuanyin
 * 
 */
public interface ILetterProcess {

	/**
	 * 首先写信的内容
	 * 
	 * @param context
	 */
	void writeContext(String context);

	/**
	 * 其次写信封
	 * 
	 * @param address
	 */
	void fillEnvelope(String address);

	/**
	 * 把信放到信封里
	 */
	void letterIntoEnvelope();

	/**
	 * 然后邮递
	 */
	void sendLetter();
}

 

实现类:LetterProcess

(代码略)

 

场景类:Client

/**
 * @author xuanyin
 * 
 */
public class Client {

	public static void main(String[] args) {

		// 创建一个处理信件的过程
		ILetterProcess letterProcess = new LetterProcess();
		// 开始写信
		letterProcess.writeContext("context");
		// 开始写信封
		letterProcess.fillEnvelope("address");
		// 把信放到信封,并封装好
		letterProcess.letterIntoEnvelope();
		// 跑到邮局把信塞到邮件,投递
		letterProcess.sendLetter();
	}
}

 

上述过程,与高内聚的要求相差甚远,更不要说迪米特法则、接口隔离原则。调用方首先得知道这四个步骤,而且还不能颠倒顺序。这在面向对象的编程中是极度地不适合的,它根本就没有完成一个类所具有的单一职责。更何况,如果信件多了就非常麻烦,每封信都要这样运转一遍。

 

模式演变

 

增加一邮局类,提供信件代发业务,只需把信件的必要信息告之,就代为发送,即提供以上四个步骤的一体化服务。

 

邮局类:PostOffice

/**
 * @author xuanyin
 * 
 */
public class PostOffice {

	/**
	 * 
	 */
	private ILetterProcess letterProcess = new LetterProcess();

	/**
	 * 写信,封装,投递,一体化了
	 * 
	 * @param context
	 * @param address
	 */
	public void sendLetter(String context, String address) {

		// 帮你写信
		letterProcess.writeContext(context);
		// 写好信封
		letterProcess.fillEnvelope(address);
		// 把信放到信封中
		letterProcess.letterIntoEnvelope();
		// 邮递信件
		letterProcess.sendLetter();
	}
}

 

注意,这个新增加的邮局类就是“门面对象”

 

增加邮局类后,在场景类中调用时就简单多了

 

场景类:Client

/**
 * @author xuanyin
 * 
 */
public class Client {

	public static void main(String[] args) {

		// 现代化邮局
		PostOffice postOffice = new PostOffice();
		// 写完内容和地址后邮局一体化代发,不必关心其中过程
		postOffice.sendLetter("context", "address");
	}
}

 

可以看到,在场景类中的调用简化了很多,只需要把信息提交过去就成了,不必再知道投递过程中的所有方法和顺序了。而且这样做的可扩展性还非常好,比如在非常时期,警察需要对信件进行安全检查,就可以按如下实现。

 

警察类:Police

/**
 * @author xuanyin
 * 
 */
public class Police {

	/**
	 * 警察对信件进行检查
	 * 
	 * @param context
	 * @return true检查通过|false检查不通过
	 */
	public boolean checkLetter(ILetterProcess letterProcess) {

		return true; // or false
	}
}

 

再看下当增加检查信件步骤后,邮局类的一些变更。

 

邮局类:PostOffice

/**
 * @author xuanyin
 * 
 */
public class PostOffice {

	/**
	 * 
	 */
	private ILetterProcess letterProcess = new LetterProcess();

	/**
	 * 
	 */
	private Police police = new Police();

	/**
	 * 写信,封装,投递,一体化了
	 * 
	 * @param context
	 * @param address
	 */
	public void sendLetter(String context, String address) {

		// 帮你写信
		letterProcess.writeContext(context);
		// 写好信封
		letterProcess.fillEnvelope(address);
		// 警察检查信件
		if (police.checkLetter(letterProcess)) {
			// 把信放到信封中
			letterProcess.letterIntoEnvelope();
			// 邮递信件
			letterProcess.sendLetter();
		}
	}
}

 

增加警察检查信件这个逻辑后,并没有改动调用端的代码,也没有改变对外暴露的接口和方法,只是改变内部的处理逻辑,所以对调用端是透明的。这是一个非常棒的设计,这就是门面模式。

 

注意事项

 

一、一个接口类可以用多个门面

 

一般情况下,一个接口类只要有一个门面对象就足够了,但下列情况可以有多个:

 

1.门面对象已庞大到一定的程度:比如超过一定行数的代码,虽然都是非常简单的委托操作,也建议拆分成多个门面。拆分时可按功能拆分,比如一个数据库操作,拆分成查询门面对象,删除门面对象,更新门面对象等。

 

2.给接口类提供不同的访问路径:我的理解是,比如模块1通过门面对象A可以完整地访问所有逻辑,但模块2是受限制的,如果仍使用门面对象A,那是无法实现受限的。这时就需要新增门面对象B,提供模块2应得的方法。但在新增门面对象B时,无需重新实现。因在面向对象编程中,应尽量保持相同的代码只写一遍,避免以后修改时到处都要修改的杯具发生。如:

/**
 * 
 * @author xuanyin
 * 
 */
public class Facade2 {

	// 引用原有的门面
	private Facade1 facade1 = new Facade1();

	/**
	 * 因提供的方法在原有门面中已实现,故此处通过引用,无需重复实现
	 */
	public void methodB() {
		this.facade1.methodB();
	}
}

 

二、门面不参与接口类内的业务逻辑

 

门面对象只是提供一个访问接口的路径而已,不应该也不能参与具体的业务逻辑,否则就会产生一个倒依赖的问题:接口必须依赖门面才能被访问。如果在门面对象内有业务逻辑,则应建立一个封装类,封装完毕后提供给门面对象。