前记
曾经我遇见的一个需求是这样的,接口A有个方法void methodA(),类B需要实现接口A的methodA()方法,并且在类B中需要把methodA()方法内部处理逻辑获得的结果利用C类实例的某个方法进行持久化操作。由于技术功力尚浅,开始我左思右想就是不能实现这个需求。开始纠结于两个难题:1,methodA()方法返回值为void,我无法获得methodA()内部逻辑获得的数据,无法获得这些数据,也就无法利用持久化类C进行处理;2,methodA()方法入参又为空,我的持久化类C也无法注入。当时我就懵逼了。还好,突然脑海想起了曾学spring时遇见的模板类设计模式,于是浅显学了下的模板类设计模式轻松把这个难题搞定。解决方法为,B定义为抽象类,内部再另外定义一个抽象方法absMethod(C c),其入参为持久类C类型,在B类的methodA()方法中调用这个抽象方法absMethod(C c),这样持久化类则注入到了methodA()方法中,则可以对其中的数据进行持久化操作了。然后只需要用D类继承这个B类,并且实现这个B的absMethod(C c)方法,这样就可以把C实例间接传入methodA()方法。
本博文并不是讲解模板类设计模式,只是借助遇见的这个问题的解决过程来强调设计模式对实现需求或学习技术源码是多么重要。不再废话,开始正文。
设计模式原则
以下为6种设计原则,直接摘录于别的博文或百度,这些内容的最终来源基本都是设计模式经典书籍《设计模式—可复用面向对象软件的基础》
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
问题描述
交流对于程序员这个职位来说是严重缺乏的,我们需要多锻炼啊。就举这个例子,当别人对我们说一句话,我们要领会别人的意思,粗略的讲这个过程主要是耳朵和大脑配合的结果。耳朵首先要“听”别人说的话,也就是获取别人对我们说的话。然后交给大脑去分析,我们才得以明白,用代码来模拟就如下。
不采用设计模式
Ear类
package com.facade.tradition;
/**
*
* @author DC
*
*/
public class Ear {
/**
* 别人说的话
*/
private String words;
/**
* 听-获取别人说的话
*/
public String getWords(String words){
System.out.println("别人对我说的话:"+words);
return words;
}
/**
* 耳朵把话传给大脑
*/
public boolean sendWordsToBrain(Brain brain,String words){
return brain.sendWordsInBrain(words);
}
}
Brain类
package com.facade.tradition;
/**
*
* @author DC
*
*/
public class Brain {
/**
* 话语
*/
private String words;
/**
* 把话语记录大脑,别人说了话就设置true,反之false
*/
public boolean sendWordsInBrain(String words){
this.words=words;
if(words==null){
return false;
}else{
return true;
}
}
/**
* 分析话语
*/
public void explainWords(){
//模拟分析过程
try {
System.out.println("正在分析对方说的话是什么意思......");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("额,原来你是这个意思!!");
}
}
测试类TestTradition
我们来测试一下,当别人对我们说了一句话, 如果若想理解别人,在不采用门面模式是怎样的。如图:
package com.facade.tradition;
/**
* 不利用设计模式,用代码来模拟这个我们和别人交流接听和理解别人话的过程
* @author DC
*
*/
public class TestTradition {
public static void main(String[] args) {
//这是我的耳朵和大脑
Ear ear=new Ear();
Brain brain=new Brain();
//别人对我说了句话,我耳朵“听”到了
String words=ear.getWords("你在干吗?");
//但是我的耳朵并不理解别人说的什么意思,于是交给我的大脑
ear.sendWordsToBrain(brain, words);
//我的大脑分析话语
brain.explainWords();
}
}
在我耳朵和大脑的配合下,我终于明白了别人的话语,不容易啊,运行结果如下:
分析一下:
再来看一下这个TestTradition代码,你是不是已经发现什么逻辑代码都得我们自己去完成,耳朵的逻辑处理我们得做,大脑的逻辑处理我们也得做。这导致了几个明显的问题:
1. 测试类与Ear类代表的子系统和Brain类代表的子系统严重的耦合在了一起。
2. 我们的测试类不仅与各个类所代表的子系统进行了交互,而且还必须了解两个子系统的类的内部实现情况。
3. 我们系统中类的所有方法都暴露给了测试类,不论是需要暴露和不需要暴露的。
再来看看上面提到的软件开发的设计原则,是不是这几个问题严重违背了一些设计原则。另外,我们并不需要了解和操作Ear或者Brain内部的一些方法。为解决这些问题,可以采用门面设计模式,也就是本博文的Facade设计模式。
采用Facade设计模式
Ear和Brain类同上
Head类:在此我们添加一个门面类,也就是本设计模式的核心类。其实,作为门面,你不觉得没有比Head更合适的类了吗,Head封装了Ear和Brain。那就定义Head为门面类,代码如下:
package com.facade.pattern;
import javax.swing.plaf.synth.SynthSpinnerUI;
/**
* 模拟门面类,门面类一般是单例的,本例设计一个简单的单例
*
* @author DC
*
*/
public class Head {
/**
* 耳朵引用变量
*/
public Ear ear=null;
/**
* 大脑引用变量
*/
public Brain brain=null;
/**
* 头引用变量
*/
public static Head head=null;
private Head(){
ear=new Ear();
brain=new Brain();
}
/**
* 获得Head头单例
* @return
*/
public static Head getInstance(){
synchronized(Head.class){
if(head==null){
return new Head();
}
return head;
}
}
/**
* "听"后明白了话语
*/
public void explainWords(String words){
//别人对我说了句话,我耳朵“听”到了
String yourWords=ear.getWords(words);
//但是我的耳朵并不理解别人说的什么意思,于是交给我的大脑
ear.sendWordsToBrain(brain, yourWords);
//我的大脑分析话语
brain.explainWords();
}
}
测试类TestFacade:
package com.facade.pattern;
/**
* 利用设计模式,用代码来模拟这个我们和别人交流接听和理解别人话的过程
* @author DC
*
*/
public class TestFacade {
public static void main(String[] args) {
//头引用,已经把具体的各个子系统连同其实现细节都封装在内部
Head head=Head.getInstance();
//别人说了,我就明白了,并不需要直接和Ear和Brain交互
head.explainWords("你在干吗??");
}
}
运行情况和不采用设计模式一样。
分析一下:
再来看看这次发生了什么,Head门面类把Ear和Brain封装到了内部,并且把很多逻辑处理也封装到了内部,比如Ear实例获得话语,Ear实例传递话语,Brain实例记录话语这些都逻辑处理方法。通过这个门面类,只把和用户直接交互的方法explainWords()暴露给用户,其他的几个逻辑方法是属于系统内部方法,因此不需要交互也就不需要暴露出用户。这样的话,就解决了不采用Facade设计模式的几个问题。是不是感觉这个门面设计模式很简单了?
Facade设计模式
facade门面角色:客户端和facade进行交互。此角色知道相关的(一个或者多个)子系统的功能和责任。当客户端想与众多的子系统进行交互时,facade角色会将所有从客户端发来的请求委派到相应的子系统去。
subsystem子系统角色:可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
Facade(外观)模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。 Facade所面对的往往是多个类或其它程序单元,通过重新组合各类及程序单元,对外提供统一的接口/界面。门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行。
Facade模式的几个使用场景:
1、当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。
2、客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
3、当你需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点,如果子系统之间是相互依赖的,你可以让它们仅通过Facade进行通讯,从而简化了它们之间的依赖关系。
特别注意:
1, 【GOF】的书中指出:在门面模式中,通常只需要一个门面类,并且此门面类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只能有一个门面类,而仅仅是说对每一个子系统只有一个门面类。或者说,如果一个系统有好几个子系统的话,每一个子系统有一个门面类,整个系统可以有数个门面类。
2,初学者往往以为通过继承一个门面类便可在子系统中加入新的行为,这是错误的。门面模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统加入新的行为。
以上比较专业的描述参考于书籍与别的博文。通俗一点理解,不采用facade设计模式就像现在的婚礼,采用facade设计模式和古代婚礼的整个过程是一样。
不采用facade设计模式==现代婚礼。
现在你遇见了一个心仪的女孩,恋爱了,打算和她结婚,上门提亲送彩礼什么的一切过程都得你亲力亲为。这个过程细节繁多。毕竟别人家的女儿,丈夫娘怎么会那么轻易让你得逞啦,已哭晕。
采用facade设计模式==古代婚礼。
周礼中规定的婚礼主要分六步,首先男方请媒人带上礼物去女方家里提亲,然后等女方家里回答,如果答应了,再让媒人带上男女双方的生辰去卜吉凶,如果可以结婚再选择一个日子,到了时间让男方和媒人带上礼物去迎娶。这下方便了,什么事都交给媒婆办了,连对方是不是女的我们自己都不能确定了。我们有什么事就找媒婆,媒婆从女方那里得到什么消息再反馈给我们。
自己领悟吧,由于技术和理解有限就写这些了吧。望指教,谢谢!!
参考书籍或博文: