23种设计模式(1)-Facade设计模式
前记
曾经我遇见的一个需求是这样的,接口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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
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("额,原来你是这个意思!!");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
测试类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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
在我耳朵和大脑的配合下,我终于明白了别人的话语,不容易啊,运行结果如下:
分析一下:
再来看一下这个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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
测试类TestFacade:
package com.facade.pattern;
/**
* 利用设计模式,用代码来模拟这个我们和别人交流接听和理解别人话的过程
* @author DC
*
*/
public class TestFacade {
public static void main(String[] args) {
//头引用,已经把具体的各个子系统连同其实现细节都封装在内部
Head head=Head.getInstance();
//别人说了,我就明白了,并不需要直接和Ear和Brain交互
head.explainWords("你在干吗??");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
运行情况和不采用设计模式一样。
分析一下:
再来看看这次发生了什么,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设计模式==古代婚礼。
周礼中规定的婚礼主要分六步,首先男方请媒人带上礼物去女方家里提亲,然后等女方家里回答,如果答应了,再让媒人带上男女双方的生辰去卜吉凶,如果可以结婚再选择一个日子,到了时间让男方和媒人带上礼物去迎娶。这下方便了,什么事都交给媒婆办了,连对方是不是女的我们自己都不能确定了。我们有什么事就找媒婆,媒婆从女方那里得到什么消息再反馈给我们。
自己领悟吧,由于技术和理解有限就写这些了吧。望指教,谢谢!!
23种设计模式(1)-Facade设计模式的更多相关文章
- 23种设计模式 - 接口隔离(Facade - Proxy - Mediator - Adapter)
其他设计模式 23种设计模式(C++) 每一种都有对应理解的相关代码示例 → Git原码 ⌨ 接口隔离 在组件构建过程中,某些接口之间直接的依赖常常会带来很多问题.甚至根本无法实现.采用添加一层间接( ...
- Java开发中的23种设计模式详解
[放弃了原文访问者模式的Demo,自己写了一个新使用场景的Demo,加上了自己的理解] [源码地址:https://github.com/leon66666/DesignPattern] 一.设计模式 ...
- Java开发中的23种设计模式详解(转)
设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...
- Java开发中的23种设计模式(转)
设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...
- c#中的23种设计模式
C# 23种设计模式汇总 创建型模式 工厂方法(Factory Method) 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节.工厂方法模式的核 ...
- java 23种设计模式及具体例子 收藏有时间慢慢看
设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代 码可靠性. 毫无疑问,设计模式 ...
- JAVA 23种设计模式(转)
1.FACTORY?追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了.麦当劳和肯德基就是生产鸡翅 ...
- 三类,23种设计模式,速记理解法!PHP
一,创建型设计模式 1.FACTORY—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了.麦当劳 ...
- php 23种设计模式的趣味解释
http://wenku.baidu.com/link?url=GwvuvSOdJneZQc-DSKoGmPcxTtzn3cdtIp3fRaCNbkg1zJDZZZTx2NwEK5IsqU996fG3 ...
随机推荐
- vue 环境搭建
目前我们的项目前端都采用的是vue js为了方便开发过程中前后端同事进行功能对接,建议每个同事都准备好前后端环境(前端的同事参考文档第二部分,后端同事请参考第一部分),只要保持前后端代码是最新的就可以 ...
- ES6 Reflect的认识
首先我们要了解一下,为什么会新添加这么一个全局对象?如果你看过Reflect的一些函数,你就会发现,这个对象上的方法基本上都可以从Object上面找到,找不到的那些,也是可以通过对对象命令式的操作去实 ...
- Junit4知识梳理
一.junit官网 junit4:http://junit.org/junit4/ junit5:http://junit.org/junit5/ 二.github junit4: https://g ...
- eigen Matrix详解
Eigen Matrix 详解 在Eigen中,所有的matrices 和vectors 都是模板类Matrix 的对象,Vectors 只是一种特殊的矩阵,行或者列为1. Matrix的前三个模板参 ...
- linux执行系统命令时挂起
现象:使用mock构建时出现挂起现象 1.排除内存不足和构建工作空间所在磁盘分区不足情形: 2.执行任何系统命令异常卡顿 原因: 1.系统根分区空间严重不足: 解决办法: 清理根分区无用文件 1> ...
- Luogu2149 [SDOI2009]Elaxia的路线-最短路+拓扑排序
Solution 另外$ m <=5e5$. 两条最短路的 最长公共路径 一定是若干条连续的边, 并且满足拓扑序. 于是我们分别 正向 和反向走第二条路径,若该条边同时是两条最短路径上的边, 则 ...
- JSP脚本元素(声明 %! 表达式 %= 脚本 %)
JSP脚本元素包括声明.表达式.脚本 声明(declaration):用于在JSP页面中声明合法的变量和方法.以“<%!”开始,以“%>”结束. 在JSP页面中,一个声明可以出现在任何地方 ...
- powershell上传证书
https://www.cnblogs.com/threestone/p/4001632.html powershell上传证书
- mongoDB(Window)
1.启动命令 mongod --dbpath F:\mongo\data 注:dbpath路径不能有空格,我开始用F:\Program Files,就因为有一个空格,失败了. ...
- spring converter-message 规则
spring 判断返回值使用哪个 converter 时,会执行两次converter 循环.. 第一次会根据 返回类型(converter的support方法) 和 mediaType 遍历所有co ...