设计模式集锦

提示:本文中利用大量UML图解释,可以参考相关文章熟悉UML表达方式,理解会较快。

设计模式分类


生成模式         结构模式              行为模式          

简单工厂模式                    适配器模式                      模板方法模式         中介者模式

工厂模式                           外观模式                          观察者模式             解释器模式

抽象工厂                           享元模式                          状态模式                 访问者模式

策略模式                           桥接模式                           备忘录模式

单例模式                           装饰模式                           迭代器模式

原型模式                           代理模式                            命令模式

建造者模式                         组合模式                           职责链模式


 

简单工厂模式

通过指定的类型构造出对应的对象,需要向用户呈现工厂类和指定类生产方法。

// OperationFactory
……
Operation  oper;
switch(operation)
{
    case  "+" :
        oper = new OperationAdd() ;break;
    case  "_":
        oper = new OperationSub() ;break;
    case  "." :
        oper = new OperationMul();break;
    case "/":
        oper = new OPertationDiv();break;
}
     return oper;
}
//应用程序中
Operation oper = OperationFactory.createOperate("+") ;
oper.NumberA = 1;
oper.NumberB = 2;
double result = oper.GetResult() ;

策略模式

跟简单工厂看起来很像,核心都用到了面向对象的继承与多态,但是在实现和思路上都有较大的区别。策略模式在于考虑针对不同的策略考虑用不同的算法去实现,而每种算法来自不同的类。这里在设计上是用一个中间类关联实现了算法接口的类,从而在不同的策略下返回对应不同的处理结果。

设计模式集锦_第1张图片

设计模式集锦_第2张图片

装饰模式

设计模式中一个典型的例子:穿衣服。实际每个装饰被当成一个组件放入一个装饰类中,这样,每次调用show方法,实际是调用该装饰类的show方法+组件的show方法。

设计模式集锦_第3张图片

设计模式集锦_第4张图片   设计模式集锦_第5张图片   设计模式集锦_第6张图片

代理模式

设计模式集锦_第7张图片

(1)代理类和被代理类实现相同的接口(也可用继承),这样代理对象可以和被代理对象有共同的方法。
(2)同时代理中包含被代理类对象。
(3)实际当用户在调用代理的方法时,实际是在调用被代理对象的方法。

当然为什么不直接调用被代理类呢?想想,实际生活中,代理是不是还要加额外的处理的(比如代理方除了收取直接费用,是不是还要收取代理费呢)。面向对象编程中的目的是为了不修改原来的被代理类,增加代理类,实现对原来被代理类中方法的增强处理(spring中的面向切面就是这样一个原理)。代码很简单,这里就不给出了。

工厂模式

因为代码篇幅较大,可直接根据UML理清关系。图中描述的是计算器的实现方法。不是很理解UML图的小伙伴可以见相关文章。图中也给出了扩展方案(添加幂运算时)。这也是跟简单工厂间的区别(简单工厂扩展需要修改工厂,添加新的运算符分支)

设计模式集锦_第8张图片

原型模式

 

设计模式集锦_第9张图片

设计模式集锦_第10张图片     设计模式集锦_第11张图片

这里调用默认的MemberwiseClone方法实际是浅复制,也就是对于对象包含的值类型的字段是按位复制,对象类型是引用复制。很多时候对于DataSet拥有copy,clone方法,注意这里分别表示的就是深复制和浅复制的过程。

模板方法模式

设计模式集锦_第12张图片

这里可以参考设计模式中给出的例子:学生考试。试卷相同,答案不同。所以将试卷作为抽象类,且把每道题定义成了一个方法,每个方法中嵌套了给出答案的方法。这个方法在模板中作为protected virtual,必须由子类去实现,且仅需返回该题答案即可。这样的设计模式即模板方法模式。不难发现好处即将大量重复部分逻辑抽象到抽象模板中,实现平台复用。

外观模式

设计模式集锦_第13张图片

简单说,当遇到复杂业务逻辑相关的系列结构,为了给用户呈现一个简单明了的方法,将这些流程(调用系列接口)封装成统一的接口。因此对用户来说,隐藏了复杂的业务流程,这样的模式叫外观模式。

设计模式集锦_第14张图片       设计模式集锦_第15张图片

建造者模式

设计模式集锦_第16张图片

设计模式集锦_第17张图片

 设计模式给出的例子是:画人。对于人基本部件是固定的,所以对于底层的构建方法也是固定的,且每个部分不可或缺的。具体见下图:

设计模式集锦_第18张图片

总之,建造者模式是针对构造具有复杂结构的对象时,其构造算法应该独立于该对象组成部分的方法。在创建对象时则调用统一构造方法

观察者模式

设计模式集锦_第19张图片

设计模式集锦_第20张图片      设计模式集锦_第21张图片

上图取自设计模式中举得例子:前台为同事放风,老板回来则提前通知。典型的观察者模式。应用中的本质在于当一对象状态发生改变需要改变其他对象,且不知道具体改变哪一个,而是笼统改变他记录的所有。

抽象工厂

设计模式集锦_第22张图片

设计模式集锦_第23张图片

和工厂区别在于,现在每个工厂需要负责“产出”多个产品。设计模式中是举了一个:应用程序兼容SQLServer和Acess两种数据库为例展开的讨论。上图可以对应进去,两个工厂便是两种数据库,抽象A,B可以对应数据库中的多张表。

状态模式

设计模式集锦_第24张图片

设计模式集锦_第25张图片     设计模式集锦_第26张图片

 简单说,就是当需要处理的状态很长,并且状态存在前后依赖关系的情况,这时可以用状态模式,切分状态链为多个状态段,每个状态以及每个状态到下一状态的转换都放到一个状态类来处理,然后依次传递下去。这在设计模式中例子是:每个人一天中的工作状态,时间段划分成了上午,中午,下午,晚上。

设计模式集锦_第27张图片   设计模式集锦_第28张图片  设计模式集锦_第29张图片

适配器模式

这个很容易理解,就是有不同方法的类采取适配器转接(可以理解成一个转接器,转换头),这样对于用户使用时,就可以不用关心对方结构如何只用调用同样的方法就好了(java:arrays.aslist(....))。

设计模式集锦_第30张图片

备忘录模式

设计模式集锦_第31张图片

设计模式集锦_第32张图片

图中描述了当一个发起者(假设游戏玩家)在执行某些操作前需要保存当前状态(游戏进度),这时可以将状态封装成一个备忘录类,并且有一个专门负责维护这个备忘录的类(Caretaker),一旦想要恢复状态,从这个状态负责人中(Caretaker)获取状态对象并且将原来的状态重置就好了。

组合模式

设计模式集锦_第33张图片

设计模式集锦_第34张图片

这里也不再附上代码,其实很简单。思想就是组装,就像堆积木或者叫拼图。一个完整的拼图可以是一块一块堆上去,当然也可以先把一些小块拼成一个局部,然后将局部再拼到大图。在面向对象中,显然小块(leaf)和这种局部(composite)对外应该是实现统一接口(component),这样在堆叠成完整的目标时,才能实现无区别(组装的行为无区别)的组装。这种设计模式叫组合模式。用于层次较强的设计中,同时还希望用户忽略这些组件之间的区别,可以统一使用组合对象中的这些组件。

迭代器模式

设计模式集锦_第35张图片

设计模式集锦_第36张图片


迭代器模式,顾名思义。一般为了用同样的方式来遍历不同的集合,需要用到一个对象叫迭代器。这样的好处是,下次换一个集合,迭代部分的逻辑代码不需要做任何改动。(某些语言中语言级别就加入了这种设计模式,比方说foreach in ,iterator等)。

单例模式

设计模式集锦_第37张图片

这个很简单了,就是在程序运行中,全局只允许某些类存在一个该类的实例化对象。很常用,下面说说几种实现单例的方式吧:

第一种:饿汉式

这种方式在类被加载的时候就会创建单例对象,但是创建过后,要很久才会被使用,甚至最后没有被用到。所以也被称为饿汉式。

public class singleton{
    private static single= new singleton();
    private singleton(){}
    public static newInstance(){
        return single;
    }
}

第二种:懒汉式

饿汉式的缺点在于当单例是一个大对象时,会从类加载后就一直占用资源。所以懒汉式实现在需要用到时再创建单例对象。

public class singleton{
    private single= null;
    private singleton(){}
    public static singleton newInstance(){
        if(single==null){
            single= new singleton();
        }
        return single;
    }
}

还有一种静态内部类的方式可以实现懒加载。

public class singleton{  
    private static class Holder{  
        public static Singleton instance = new Singleton();  
    }  
    private singleton(){}  
    public static singleton newInstance(){  
        return Holder.instance;  
    }  
}  

第三种:线程安全式

显然上面的方法在多线程中存在线程安全问题,一般可采用加锁的方式。

public class singleton{
    private single= null;
    private singleton(){}
    public static singleton newInstance(){
        synchronized(singleton.class){
            if(single==null){
                single= new singleton();
            }
        }
        return single;
    }
}

实际,这种加锁方式,无论当前单例有没有被创建都会竞争,线程都会阻塞。想想为什么我不先判断是否需要创建再决定是不是要去竞争锁呢,效率其实还有提升的空间。

public class singleton{
    private single= null;
    private singleton(){}
    public static singleton newInstance(){
        if(single==null){
            synchronized(singleton.class){
                if(single==null){
                    single= new singleton();
                }
            }
        } 
        return single;
    }
}

第四种:枚举式

Effctive Java中推荐的一种方式,但是用的人很少。

public enum singleton{  
    instance;  
    public void whateverMethod(){}      
}

桥接模式

设计模式集锦_第38张图片

设计模式集锦_第39张图片

            设计模式中举了例子是关于不同品牌手机运行各种手机软件。这里可以这样理解,手机品牌作为抽象类(Abstract),然后具体品牌手机(RefineAbstraction)作为子类,且每种手机共同接口(operation)可以运行软件。那么,每种手机可以运行哪种软件是不是安装上就可以用呢,所以这里用了聚合,每种手机都有软件这样一个对象(Implement),具体什么软件(concreteImplementX)需要用户指定。
        实际上,变化比较多的发生在子类和父类之间时,可以考虑抽象成两类,并采用组合/聚合的关系表示两类的联系即桥接。桥接前后对比如下图(优劣在需要扩展时便体现出来了,假设现在需要添加一个音乐播放器?添加一个S牌手机呢?二者模式的实现不言而喻):

                             设计模式集锦_第40张图片      设计模式集锦_第41张图片

命令模式

设计模式集锦_第42张图片

由于将命令的执行独立到了单独的类中(receiver),并将命令(Cammand,ConcreteCommand)抽象出来,采用关联方式来执行,使得这种设计模式优势在于可以很容易设计出命令队列,可以比较容易将消息记录到日志,同时控制命令的执行条件。当需要添加新的一种类型命令时,仅需要添加该命令的执行者(action提供具体实现方案)即可。同时当多条命令需要放到一起执行时,只需要Invoker中去添加一个命令队列即可。(调用顺序即:invoker.set(Cammand)--->invoker.notify()--->cammand.excute()--->reciever.excute())

职责链模式

设计模式集锦_第43张图片

实际就是层层过滤的设计模式,一层履行完自己的职责,传递给下一层把关,直至最终结尾处。图中每层中successor就是下一个职责履行者。可想而知,在一个网站中需要处理一个请求实际可能是需要非常多拦截器去验证处理请求的,这也就是职责链模式。(注意跟状态模式区别,状态模式也是层层传递,但是每一层的执行逻辑是否执行或是否转入下一个状态需要外部给定状态来决定。职责链模式是自动传递至下一层处理,是否传递跟外部状态无关)。优点还是在于需要层层过滤的应用场景下,一旦需要增加过滤层或者需要调整顺序都可以很容易实现。

中介者模式

设计模式集锦_第44张图片

设计模式集锦_第45张图片

中介者模式讲究的就是解耦边缘类,不让其直接发生联系。如果需要联系,是需要通过中介者的。就像设计模式中提到了各国和联合国的关系。

设计模式集锦_第46张图片

不难想到事实上,虽然各国类之间实现了解耦,但是无形中使得中介者类变得很复杂,而且似乎违背了单一职责的原则。这也是实际中应该考虑的问题,如果应用中各类之间有很复杂的关系,这时是可以考虑用中介者模式的,这样的好处是扩展通讯的类很简单,只需要新建一个类,并在中介者中声明就可以完成与个类之间的通讯。当然也并不是多对多的关系就一定要用这种模式,毕竟中介者决定了全局是否能正常联系。

享元模式

设计模式集锦_第47张图片

所谓享元就是当有多个相似对象,他们之间存在些微不同,可以考虑将共有的部分抽象出单一类来共享这部分数据,而他们不同的部分作为外部状态在实例化的时候传进来,从而起到减少对创建重复数据的内存和时间开销。设计模式中提到一个例子:假如新建1000个网站,但是每个网站实际功能都相同,但网站的用户不同。这时候可以考虑用享元:

设计模式集锦_第48张图片

网站工厂可以创建网站,但是保证已经存在该类(暂且字符串区分类别)网站绝不重复创建。同时对于每一个网站除了提供被使用的方法,还具备当前用户(网站所有者)是谁的状态。这样就实现对于不同用户群体要求创建网站时可以大幅降低重复操作(比如:copy n份源代码部署n个具有相同功能的网站,很浪费资源好不?!)实际项目中带来的好处是可以大幅降低实例总数。我是这样理解,Java中,对象.clone(),实际上复制该对象的一般数据类型的字段,引用字段就是原来的那个对象中的引用,这样实际也就是用到了享元的设计模式吧。

解释器模式

设计模式集锦_第49张图片

 

实际上类似像是生活中的翻译软件,由于该解释器中定义了语法映射规则,所以可以对相应语句可以翻译成另外一种语言 。设计模式中提到例子是对乐谱串的解释:

设计模式集锦_第50张图片

                                   设计模式集锦_第51张图片     设计模式集锦_第52张图片

访问者模式

设计模式中举例:对比男人和女人在同一状态下的不同表现。这里对于固定的两种类别:男人和女人,同时每个类都有一个访问者接口用于接待访问者。每个访问者接口实际是调用该访问者在该类人下的表现方法。其中每一种访问者作为一个类,包含男人和女人在该访问者中的表现的方法(相当于提供了访问该状态的两种方法)。

简言之,对于每个访问者应该都具备访问每种类型的方法,这样当该访问者访问不同的类型的人时都可以有不同的表现。适用范围即访问者类型众多,被访问实际很有限,这样便可以实现很好的扩展性。一旦多了一个访问者要做出行为,只需要新建一个访问者并重写访问方法就可以实现,其他类不需要做出任何变动(开闭原则)。

设计模式集锦_第53张图片

设计模式集锦_第54张图片 设计模式集锦_第55张图片 设计模式集锦_第56张图片

至于visitor显然就是对应的每种访问者。每种访问者都应该有GetManConclusion和GetWomanConclusion方法,特定访问者去实现一个统一的访问者接口就可以实现了。这里就不再给出了。

结语:实际上对于这些设计模式,有的看起来可能千差万别,但是最核心的原则还是那6项(单一职责,理氏代换,接口隔离,依赖倒转,迪米特原则,组合复用)。熟悉之后,其实在正常设计问题解决方案的时候,自然而然就用到了某些模式。所谓剑法的最高境界便是“手中无剑,心中也无剑”。

你可能感兴趣的:(设计模式)