门面模式(Facade Pattern)

博客来源:http://wangym.iteye.com/blog/743944

 

近期参与项目研发,调用接口时遇一事不爽,比如在调C方法前必须得先调A方法,且成功后还得再调B方法,最后才能调C方法,而且接口还会有变更,突然说这中间还得再增加几步......

 

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

 

场景举例

 

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

 

接口类:ILetterProcess

Java代码 复制代码
  1. /**  
  2.  * @author xuanyin  
  3.  *   
  4.  */  
  5. public interface ILetterProcess {   
  6.   
  7.     /**  
  8.      * 首先写信的内容  
  9.      *   
  10.      * @param context  
  11.      */  
  12.     void writeContext(String context);   
  13.   
  14.     /**  
  15.      * 其次写信封  
  16.      *   
  17.      * @param address  
  18.      */  
  19.     void fillEnvelope(String address);   
  20.   
  21.     /**  
  22.      * 把信放到信封里  
  23.      */  
  24.     void letterIntoEnvelope();   
  25.   
  26.     /**  
  27.      * 然后邮递  
  28.      */  
  29.     void sendLetter();   
  30. }  
/**
 * @author xuanyin
 * 
 */
public interface ILetterProcess {

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

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

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

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

 

实现类:LetterProcess

(代码略)

 

场景类:Client

Java代码 复制代码
  1. /**  
  2.  * @author xuanyin  
  3.  *   
  4.  */  
  5. public class Client {   
  6.   
  7.     public static void main(String[] args) {   
  8.   
  9.         // 创建一个处理信件的过程   
  10.         ILetterProcess letterProcess = new LetterProcess();   
  11.         // 开始写信   
  12.         letterProcess.writeContext("context");   
  13.         // 开始写信封   
  14.         letterProcess.fillEnvelope("address");   
  15.         // 把信放到信封,并封装好   
  16.         letterProcess.letterIntoEnvelope();   
  17.         // 跑到邮局把信塞到邮件,投递   
  18.         letterProcess.sendLetter();   
  19.     }   
  20. }  
/**
 * @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

Java代码 复制代码
  1. /**  
  2.  *   
  3.  * @author xuanyin  
  4.  *   
  5.  */  
  6. public class PostOffice {   
  7.   
  8.     /**  
  9.      *   
  10.      */  
  11.     private ILetterProcess letterProcess = new LetterProcess();   
  12.   
  13.     /**  
  14.      * 写信,封装,投递,一体化了  
  15.      *   
  16.      * @param context  
  17.      * @param address  
  18.      */  
  19.     public void sendLetter(String context, String address) {   
  20.   
  21.         // 帮你写信   
  22.         letterProcess.writeContext(context);   
  23.         // 写好信封   
  24.         letterProcess.fillEnvelope(address);   
  25.         // 把信放到信封中   
  26.         letterProcess.letterIntoEnvelope();   
  27.         // 邮递信件   
  28.         letterProcess.sendLetter();   
  29.     }   
  30. }  
/**
 * 
 * @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

Java代码 复制代码
  1. /**  
  2.  * @author xuanyin  
  3.  *   
  4.  */  
  5. public class Client {   
  6.   
  7.     public static void main(String[] args) {   
  8.   
  9.         // 现代化邮局   
  10.         PostOffice postOffice = new PostOffice();   
  11.         // 写完内容和地址后邮局一体化代发,不必关心其中过程   
  12.         postOffice.sendLetter("context""address");   
  13.     }   
  14. }  
/**
 * @author xuanyin
 * 
 */
public class Client {

	public static void main(String[] args) {

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

 

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

 

警察类:Police

Java代码 复制代码
  1. /**  
  2.  *   
  3.  * @author xuanyin  
  4.  *   
  5.  */  
  6. public class Police {   
  7.   
  8.     /**  
  9.      * 警察对信件进行检查  
  10.      *   
  11.      * @param context  
  12.      * @return true检查通过|false检查不通过  
  13.      */  
  14.     public boolean checkLetter(ILetterProcess letterProcess) {   
  15.   
  16.         return true// or false   
  17.     }   
  18. }  
/**
 * 
 * @author xuanyin
 * 
 */
public class Police {

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

		return true; // or false
	}
}

 

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

 

邮局类:PostOffice

Java代码 复制代码
  1. /**  
  2.  *   
  3.  * @author xuanyin  
  4.  *   
  5.  */  
  6. public class PostOffice {   
  7.   
  8.     /**  
  9.      *   
  10.      */  
  11.     private ILetterProcess letterProcess = new LetterProcess();   
  12.   
  13.     /**  
  14.      *   
  15.      */  
  16.     private Police police = new Police();   
  17.   
  18.     /**  
  19.      * 写信,封装,投递,一体化了  
  20.      *   
  21.      * @param context  
  22.      * @param address  
  23.      */  
  24.     public void sendLetter(String context, String address) {   
  25.   
  26.         // 帮你写信   
  27.         letterProcess.writeContext(context);   
  28.         // 写好信封   
  29.         letterProcess.fillEnvelope(address);   
  30.   
  31.         // 警察检查信件   
  32.         if (police.checkLetter(letterProcess)) {   
  33.   
  34.             // 把信放到信封中   
  35.             letterProcess.letterIntoEnvelope();   
  36.             // 邮递信件   
  37.             letterProcess.sendLetter();   
  38.         }   
  39.     }   
  40. }  
/**
 * 
 * @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时,无需重新实现。因在面向对象编程中,应尽量保持相同的代码只写一遍,避免以后修改时到处都要修改的杯具发生。如:

 

Java代码 复制代码
  1. /**  
  2.  *   
  3.  * @author xuanyin  
  4.  *   
  5.  */  
  6. public class Facade2 {   
  7.   
  8.     // 引用原有的门面   
  9.     private Facade1 facade1 = new Facade1();   
  10.   
  11.     /**  
  12.      * 因提供的方法在原有门面中已实现,故此处通过引用,无需重复实现  
  13.      */  
  14.     public void methodB() {   
  15.         this.facade1.methodB();   
  16.     }   
  17. }  
/**
 * 
 * @author xuanyin
 * 
 */
public class Facade2 {

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

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

 

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

 

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

你可能感兴趣的:(设计模式,编程,Blog)