设计模式-23种设计模式

学习设计模式不是一蹴而就的事情,需要长时间的积累,在平时写代码的时候多思考,学习设计模式的时候也不要死记硬背,要了解每个设计模式都解决了那些问题,另外就是最好要记住每种设计模式的类图,这样对于实操写代码的时候很有帮助

    设计模式是软件设计中的最佳实践,是由前人的知识和经验总结而来,是针对不同特定场景问题的解决方案,通过学习设计模式,可以让我们知道在什么情况下应该使用什么设计模式,也可以增加我们对于软件设计的理解,设计出更稳定,扩展性更好的系统。
    设计模式总共包括6大设计原则和23种设计模式,23种设计模式又分为创建型模式,结构型模式,行为型模式三种,每种模式解决的问题也不相同,创建型设计模式主要关注与如何创建对象;结构型设计模式主要关注于类和对象之间的关系;而行为型设计模式关注于对象之间的通信;

六大设计原则

    单一职责原则

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。

问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

  说到单一职责原则,很多人都会不屑一顾。因为它太简单了,稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。
  在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法便是遵循单一职责原则。
虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。
  为什么会出现这种现象呢?因为有职责扩散。所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。
比如:类T只负责一个职责P,这样设计是符合单一职责原则的。
后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。
  但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。
  这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责P,在未来可能会扩散为P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。

遵循单一职责原的优点有:
1.可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
2.提高类的可读性,提高系统的可维护性;
3.变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

    里氏替换原则

定义:所有引用基类的地方必须能透明地使用其子类的对象

问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:
1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2.子类中可以增加自己特有的方法。
3.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?后果就是:你写的代码出问题的几率将会大大增加。

    依赖倒置原则

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。(依赖倒置原则的核心思想是面向接口编程)

问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

传递依赖关系有三种方式,接口传递、构造方法传递、setter方法传递。相信用过Spring框架的,对依赖的传递方式一定不会陌生。

在实际编程中,我们一般需要做到如下3点:
1.低层模块尽量都要有抽象类或接口,或者两者都有。
2.变量的声明类型尽量是抽象类或接口。
3.使用继承时遵循里氏替换原则。

依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。

    接口隔离原则

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

问题由来:类A通过接口Interface依赖类B,类C通过接口Interface依赖类D,如果接口Interface对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

解决方案:将臃肿的接口Interface拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。

说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。

采用接口隔离原则对接口进行约束时,要注意以下几点:
1.接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
2.为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
3.提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。

    迪米特原则

定义:一个对象应该对其他对象保持最少的了解。

问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

解决方案:尽量降低类与类之间的耦合。自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。

迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。

    开闭原则

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。以前,如果有人告诉我“你进行设计的时候一定要遵守开闭原则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。

在仔细思考以及仔细阅读很多设计模式的文章后,终于对开闭原则有了一点认识。其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。

其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:
单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合。
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

    如何去遵守这六个原则

对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。我们用一幅图来说明一下。


image.png

图中的每一条维度各代表一项原则,我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守,点将会落在大圆外部。一个良好的设计体现在图中,应该是六个顶点都在同心圆中的六边形。


设计原则

在上图中,设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设计3、设计4设计虽然有些不足,但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了,设计5和设计6都是迫切需要重构的设计。


23种设计模式

模式之间的关系如下图

    创建型设计模式

         单例模式

保证在程序运行期间一个类只有一个实例,并提供一个全局访问点

定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,单例模式目的是保证在程序运行期间一个类只有一个实例,并提供一个全局访问点,无论什么情况下,只会生成一个实例,免去繁琐的创建销毁对象的过程。

模式类图:

单例类图

优点:
1.减少了内存开支,避免频繁地创建、销毁对象
2.避免对资源的多重占用
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
如何设计:
如何设计单例模式其实很简单,只需要考虑一个问题,实例是否可以保证是全局唯一

关于实例是否保证是全局唯一延伸出的问题:
是否线程安全,不安全肯定就不能保证全局只有一个实例
是否支持序列化,支持序列化的类,被反序列化之后肯定就不是全局唯一了
是否支持反射,支持反射肯定也不是全局唯一的
是否可以被克隆,这个也不能保证全局唯一
所以设计一个安全的单例需要考虑的问题还是很多的。

针对上述问题常见的解决办法:
保证线程安全,使用volatile+synchronized实现
防止序列化攻击,重写readResolve方法
防止反射,常用的方案是在单例类里增加一个boolean类型的flag标识,在实例化的时候先判断flag标识
防止克隆,重写clone()方法

实现一个最简单的单例就需要考虑到以上的所有问题,这个时候什么有用的方法还没写那,代码就已经很多了,那有没有简单的办法既满足上述条件,代码又简洁那,那肯定有,使用枚举实现单例。
常见的实现:
1.饿汉模式
延迟加载,首次需要使用的时候在实例化,需要考虑线程安全

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance(){
        return singleton;
    }
}

优点:线程安全,代码简单。

缺点:不是延迟加载,如果你用不到这个类,它也会实例化,还有一个问题就是如果这个实例依赖外部一些配置文件,参数什么的,在实例化之前就要获取到,否则就实例化异常

2.懒汉模式
延迟加载,首次需要使用的时候在实例化,需要考虑线程安全
线程不安全的实现方式

public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance(){
        if(null == singleton){
            singleton = new Singleton();
        }
        return singleton;
    }
}

双重检查(DCL:Double Check Lock)线程安全的实现方式

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance(){
        if(null == singleton){
            synchronized (Singleton.class){
                if(null == singleton){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

常见提问
为什么使用volatile修饰singleton变量?
首先肯定回答volatile的可见性,防止重排序优化,如果不用volatile修饰,多线程的情况下,可能会出现线程A进入synchronized代码块,执行new Singleton();,首先给singleton分配内存,但是还没有初始化变量,这时候线程B进入getInstance方法,进行第一个判断,此时singleton已经不为空,直接返回singleton,然后肯定报错。使用volatile修饰之后禁止jvm重排序优化,所以就不会出现上面的问题

3.静态内部类模式
使用静态内部类实现也是延迟加载,利用静态内部类去实现线程安全,只有在第一次调用getInstance方法的时候才会去加载SingletonHolder,初始化SINGLETON

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance(){
        return SingletonHolder.SINGLETON;
    }

    private static class SingletonHolder{
        private static final Singleton SINGLETON = new Singleton();
    }
}

4.枚举模式
枚举实现代码更简洁,线程安全,并且保证枚举不会被反序列化,反射和克隆

public enum Singleton {

    SINGLETON;

    /**
     * 提供的方法
     */
    public void method(){
        System.out.println("枚举实现");
    }
}

         工厂模式

用工厂方法代替new操作,让子类去决定实例化哪个类,工厂方法将一个类的实例化延迟到子类
定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类,工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但如果随意增加工厂类会增加代码的复杂度,也不易维护
模式类图

工厂模式类图

角色
1.抽象产品:Product抽象产品定义
2.具体产品类:ConcreteProduct实现Product接口
3.抽象工厂:Creator抽象工厂定义
4.具体工厂类:ConcreteCreator实现Creator接口

优点:
1.解耦:调用方不用负责对象的创建,只需要使用,明确各自的职责
2.维护方便:后期如果创建对象时需要修改代码,也只需要去工厂方法中修改,易拓展

模式代码实现
简单工厂

interface Gameable {

    /**
     * 校验账户信息
     * @param nickName
     */
    void validateAccount(String nickName);
}

class ShootGame implements Gameable{

    @Override
    public void validateAccount(String nickName) {
        System.out.println("射击游戏校验昵称:"+nickName);
    }
}

class TowerDefenceGame implements Gameable{

    @Override
    public void validateAccount(String nickName) {
        System.out.println("塔防游戏校验昵称:"+nickName);
    }
}

public class GameFactory {

    /**
     * 根据传入类型生成实例
     * @param gameType
     * @return
     */
    public static Gameable creator(String gameType){
        Gameable gameable = null;
        if(StringUtils.isEmpty(gameType)){
            return gameable;
        }
        if("shoot".equalsIgnoreCase(gameType)){
            gameable = new ShootGame();
        }else if("towerDefence".equalsIgnoreCase(gameType)){
            gameable = new TowerDefenceGame();
        }
        return gameable;
    }

    public static void main(String[] args) {
        Gameable shootGame = GameFactory.creator("shoot");
        shootGame.validateAccount("明羽");
        System.out.println("... 分割线 ...");
        Gameable towerDefenceGame = GameFactory.creator("towerDefence");
        towerDefenceGame.validateAccount("明羽");
    }
}

工厂方法
工厂模式跟简单工厂模式的区别在于简单工厂只有一个工厂类,提供了一个工厂方法,由入参决定生产那个产品,而工厂模式则定义一个工厂接口,不同的产品工厂实现工厂接口,生产的产品由产品工厂决定

interface Gameable {

    /**
     * 校验账户信息
     * @param nickName
     */
    void validateAccount(String nickName);
}

class ShootGame implements Gameable{

    @Override
    public void validateAccount(String nickName) {
        System.out.println("射击游戏校验昵称:"+nickName);
    }
}

class TowerDefenceGame implements Gameable{

    @Override
    public void validateAccount(String nickName) {
        System.out.println("塔防游戏校验昵称:"+nickName);
    }
}


interface GameFactory {

    /**
     * 生成实例
     * @return
     */
    Gameable creator();
}

class ShootGameFactory implements GameFactory{

    @Override
    public Gameable creator() {
        return new ShootGame();
    }
}

class TowerDefenceGameFactory implements GameFactory{

    @Override
    public Gameable creator() {
        return new TowerDefenceGame();
    }
}

public class FactoryTest {

    public static void main(String[] args) {

        GameFactory shootGameFactory = new ShootGameFactory();
        Gameable shootGame = shootGameFactory.creator();
        shootGame.validateAccount("明羽");
        System.out.println("... 分割线 ...");
        GameFactory towerDefenceGameFactory = new TowerDefenceGameFactory();
        Gameable towerDefenceGame = towerDefenceGameFactory.creator();
        towerDefenceGame.validateAccount("明羽");
    }
}

         抽象工厂

定义:为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类,抽象工厂模式是对工厂方法模式的扩展,抽象工厂比工厂模式更为抽象,工厂方法模式针对产品等级结构,而抽象工厂针对产品族。

产品族与产品等级结构的概念:
​ 产品族:是指位于不同产品等级结构中,功能相关联的产品组成的家族,比如游戏工厂生产射击类和塔防类两种产品,任天堂的射击类游戏和塔防类游戏为一个产品族,腾讯的射击类游戏和塔防类游戏为一个产品族
​ 产品等级结构:一个产品族由多个产品等级结构组成,射击类游戏是一个产品等级结构,塔防类游戏也是一个产品等级结构

模式类图
以游戏为例,定义一个抽象工厂,生产射击和塔防两种游戏,有两个具体的生产工厂,任天堂和腾讯,两个工厂生产各自品牌的两类游戏产品

示例类图

角色:
1.抽象工厂:GameFactory,规定了生成射击类和塔防类两种游戏
2.具体工厂:NintendoGameFactory,TencentGameFactory,负责生产各自品牌的射击类和塔防类游戏
3.抽象产品:Gameable,ShootGame和TowerDefenceGame是抽象类,实现Gameable
4.具体产品:NintendoShootGame,NintendoTowerDefenceGame,TencentShootGame,TencentTowerDefenceGame

优点

  1. 接口和实现分离,客户端面向接口编程,不用关心具体实现,从具体的产品实现中解耦
  2. 增加新的具体工厂和产品族方便,切换产品族方便

缺点
不易增加新的产品,如果要增加新的产品需要抽象工厂和所有具体工厂

代码实现

/**
  * @description: 工厂类
 */
public interface GameFactory {

    /**
     * 创建射击游戏
     * @return
     */
    Gameable createShootGame();

    /**
     * 创建塔防游戏
     * @return
     */
    Gameable createTowerDefenceGame();
}

/**
 * @description: 任天堂游戏制造厂
 */
public class NintendoGameFactory implements GameFactory{

    @Override
    public Gameable createShootGame() {
        return new NintendoShootGame();
    }

    @Override
    public Gameable createTowerDefenceGame() {
        return new NintendoTowerDefenceGame();
    }
}

/**
 * @description: 腾讯游戏制造厂
 */
public class TencentGameFactory implements GameFactory {

    @Override
    public Gameable createShootGame() {
        return new TencentShootGame();
    }

    @Override
    public Gameable createTowerDefenceGame() {
        return new TencentTowerDefenceGame();
    }
}


/**
 * @description: 游戏接口
 */
public interface Gameable {

    /**
     * 校验账户信息
     * @param nickName
     */
    void validateAccount(String nickName);

    /**
     * 游戏类型
     */
    void getGameType();
}

/**
 * @description: 射击类游戏
 */
public abstract class ShootGame implements Gameable{

    @Override
    public void validateAccount(String nickName) {
        System.out.println("射击游戏校验昵称:"+nickName);
    }
}

/**
 * @description: 塔防类游戏
 */
public abstract class TowerDefenceGame implements Gameable{
    @Override
    public void validateAccount(String nickName) {
        System.out.println("塔防游戏校验昵称:"+nickName);
    }
}

/**
 * @description: 任天堂射击游戏
 */
public class NintendoShootGame extends ShootGame{
    @Override
    public void getGameType() {
        System.out.println("任天堂射击游戏");
    }
}

/**
 * @description: 任天堂塔防游戏
 */
public class NintendoTowerDefenceGame extends TowerDefenceGame{
    @Override
    public void getGameType() {
        System.out.println("任天堂塔防游戏");
    }
}
/**
 * @description: 腾讯射击游戏
 */
public class TencentShootGame extends ShootGame {
    @Override
    public void getGameType() {
        System.out.println("腾讯射击游戏");
    }
}
/**
 * @description: 腾讯塔防游戏
 */
public class TencentTowerDefenceGame extends TowerDefenceGame{
    @Override
    public void getGameType() {
        System.out.println("腾讯塔防游戏");
    }
}

public static void main(String[] args) throws Exception{

    NintendoGameFactory nintendoGameFactory = new NintendoGameFactory();
    nintendoGameFactory.createShootGame().getGameType();
    nintendoGameFactory.createTowerDefenceGame().getGameType();

    TencentGameFactory tencentGameFactory = new TencentGameFactory();
    tencentGameFactory.createShootGame().getGameType();
    tencentGameFactory.createTowerDefenceGame().getGameType();
}

         建造者模式

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

模式类图

建造者模式类图

角色
1.抽象建造者:Builder,目的是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品
2.具体的建造者: ConcreteBuilder,实现抽象建造者的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品
3.产品类:Product,一般是一个较为复杂的对象
4.管理类:Director,负责调用适当的建造者来组建产品,被用来封装程序中易变的部分

优点
1.客户端不必知道产品内部组成的细节,封装性好
2.建造者独立,容易扩展

代码实现

/**
 * @description: 抽象建造者
 */
public abstract class Builder {

    /**
     * 组装cpu
     */
    public abstract void builderCpu(String cpu);

    /**
     * 组装内存
     */
    public abstract void builderMemory(String memory);

    /**
     * 组装硬盘
     */
    public abstract void builderDisk(String disk);

    /**
     * 获取电脑
     * @return
     */
    public abstract Computer getComputer();

}

/**
 * @description: 具体的建造者
 */
public class ConcreteBuilder extends Builder {

    private Computer computer = new Computer();

    @Override
    public void builderCpu(String cpu) {
        computer.setCpu(cpu);
    }

    @Override
    public void builderMemory(String memory) {
        computer.setMemory(memory);
    }

    @Override
    public void builderDisk(String disk) {
        computer.setDisk(disk);
    }

    @Override
    public Computer getComputer() {
        return computer;
    }
}

/**
 * @description: 电脑产品
 */
@Data
public class Computer {

    private String cpu;

    private String memory;

    private String disk;

}

/**
 * @description: 主管类
 */
public class Director {

    private Builder builder = new ConcreteBuilder();

    /**
     * 组装电脑
     */
    public Computer builderComputer(String cpu, String memory, String disk){
        builder.builderCpu(cpu);
        builder.builderMemory(memory);
        builder.builderDisk(disk);
        return builder.getComputer();
    }
}

public static void main(String[] args) {
    Director director = new Director();
    Computer computer = director.builderComputer("Intel cpu","内存","硬盘");
    System.out.println(computer);
}

         原型模式

通过克隆一个已经存在的对象实例来返回新的实例,而不是通过new去创建对象
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式通过克隆一个已经存在的对象实例来返回新的实例,而不是通过new去创建对象,多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;java中复制对象是通过重写clone()实现的,原型类需要实现Cloneable接口,否则报CloneNotSupportedException异常

模式类图

原型模式类图

角色
1.抽象原型:Prototype,可以为接口或者抽象类,实现了Cloneable接口,重写了clone()方法,子类只需实现或集成即可拥有克隆功能
2.具体原型:PrototypeA,PrototypeB,实现/集成了Prototype接口的类,拥有克隆方法
3.工厂模式:原型模式常和工厂模式一起使用,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者

优点
1.性能优良,比new一个对象性能好很多
2.不受对象构造函数的约束

模式代码实现

/**
 * @description: 抽象原型角色
 */
@Data
public abstract class Pen implements Cloneable{

    private String name;

    public Pen(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

/**
 * @description: 铅笔
 */
public class Pencil extends Pen{

    public Pencil(String name) {
        super(name);
    }
}
/**
 * @description: 碳素笔
 */
public class CarbonPen extends  Pen{

    public CarbonPen(String name) {
        super(name);
    }
}
/**
 * @description: 笔生产工厂
 */
public class PenFactory {

    /**
     * 原型类容器
     */
    private static Map<String, Pen> penMap = new Hashtable<>();

    /**
     * 初始化
     */
    public static void init() {
        Pen carbonPen = new CarbonPen("碳素笔");
        penMap.put(CarbonPen.class.getName(),carbonPen);
        Pen pencil = new Pencil("铅笔");
        penMap.put(Pencil.class.getName(),pencil);
    }

    /**
     * 通过复制获取实例
     * @param className
     * @return
     * @throws CloneNotSupportedException
     */
    public static Pen getPen(Class className) throws CloneNotSupportedException{
        Pen cachedShape = penMap.get(className.getName());
        return (Pen) cachedShape.clone();
    }

}


public static void main(String[] args){
        PenFactory.init();
        IntStream.range(0,2).forEach(i->{
            try {
                System.out.println(PenFactory.getPen(CarbonPen.class).getClass());
                System.out.println(PenFactory.getPen(Pencil.class).getClass());
                System.out.println("  ... ");
            }catch (CloneNotSupportedException e){
                e.printStackTrace();
            }
        });
}

浅拷贝和深拷贝

  • 浅拷贝:将一个对象复制后,基本类型会被重新创建,引用类型的对象会把引用拷贝过去,实际上还是指向的同一个对象
  • 深拷贝:将一个对象复制后,基本类型和引用类型的对象都会被重新创建

    结构型设计模式

关注于类和对象之间的关系

         适配器模式

组合两个不相干类,在两个不兼容的接口之间提供一个混合接口,使其兼容适配
定义:把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。在现有的系统中有新旧两个接口,由于新旧接口不兼容导致客户端调用出现问题,但是现有系统还需要使用旧的接口,所以这个接口不能重构,但是为了能够让客户端正常调用,我们就需要将新的接口转换成旧的接口,这种解决方式就是适配器模式

模式类图

适配器

角色:

  1. 目标接口:Target,该角色把其他类转换为我们期望的接口
  2. 被适配类:Adaptee,被期望改变的接口
  3. 适配器:adapter,将被适配类Adaptee和目标接口Target接口组合到一起

优点

  1. 适配器模式可以让两个没有任何关系的类在一起运行
  2. 增加了类的透明性和复用性
  3. 灵活性非常好

适配器模式是为了在扩展应用的时候减少代码时才使用的,所以最初设计系统时不要考虑使用适配器模式
模式代码实现

/**
 * 目标接口
  Target,我们期望的接口模样,供客户端调用
 * @date: 2019/2/18 18:38
 * @description: 目标接口
 */
public interface Target {

    /**
     * 客户端访问的目标接口
     */
    void request();
}



/**
 * Adaptee的adapterMethod方法是我们期望被适配的方法
 * @date: 2019/2/18 19:20
 * @description: 被适配类
 */
public class Adaptee {

    /**
     * 被适配的接口
     */
    public void adapterMethod(){
        System.out.println("我是要被适配的方法");
    };
}

/**
 * Adapter,将被适配类Adaptee和目标接口Target接口组合到一起
 * @date: 2019/2/18 19:20
 * @description: 适配器
 */
public class Adapter implements Target {

    /**
     * 被适配类
     */
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        adaptee.adapterMethod();
    }
}
/**测试*/
public static void main(String[] args) {
    Adaptee adaptee = new Adaptee();
    Adapter adapter = new Adapter(adaptee);
    //输出"我是要被适配的方法""
    adapter.request();

}

         桥接模式

定义: 将抽象和实现解耦,使得两者可以独立地变化。桥接模式是为了解决继承的缺点而提出的设计模式,将系统各维度抽象出来,各维度独立变化,之后可通过聚合,将各维度组合起来,减少它们之间耦合。主要使用了类间的聚合关系、继承、覆写等常用功能,但是它却提供了一个非常清晰、稳定的架构。

模式类图
以游戏为例,游戏可以按品牌分为任天堂游戏和腾讯游戏两种,显示器可以分为CRT,LCD显示器。​ 将游戏和显示器分为两个维度,当两个维度都可以独立变化时,使用聚合将各维度组合,这时如果要增加一个游戏或者显示器,只需要增加相应子类就可以

桥接模式

优点

  1. 桥接模式主要作用就是解耦,解决多重继承导致的问题,将抽象和实现的分离
  2. 优秀的扩展能力,想扩展就增加实现
  3. 通过抽象层进行聚合完成封装,实现细节对客户透明

缺点

会增加系统的理解与设计难度

模式代码实现

/**
 *抽象游戏类AbstractGame,按游戏分类
 * @date: 2019/3/11 17:13
 * @description: 抽象游戏类
 */
public abstract class AbstractGame {

    protected AbstractDisplayer displayer;

    public void setAbstractDisplayer(AbstractDisplayer displayer) {
        this.displayer = displayer;
    }

    /**
     * 玩游戏
     */
    public abstract void play();
}

/**
 * 具体游戏实现
 * @date: 2019/3/11 17:17
 * @description: 任天堂游戏
 */
public class NintendoGame extends AbstractGame{

    @Override
    public void play() {
        System.out.println("启动任天堂游戏");
        displayer.run();
    }
}

/**
 * 具体游戏实现
 * @date: 2019/3/11 17:21
 * @description:
 */
public class TencentGame extends AbstractGame {

    @Override
    public void play() {
        System.out.println("启动腾讯游戏");
        displayer.run();
    }
}



/**
 * AbstractDisplayer 按显示器分类
 * @date: 2019/3/11 17:25
 * @description: 抽象显示器
 */
public abstract class AbstractDisplayer {

    /**
     * 运行
     */
    public abstract void run();
}

/**
 * @date: 2019/3/11 17:27
 * @description: LCD显示器
 */
public class LCDDisplay extends AbstractDisplayer{

    @Override
    public void run() {
        System.out.println("运行 LCD显示器");
    }
}

/**
 
 * @date: 2019/3/11 17:28
 * @description: CRT显示器
 */
public class CRTDisplayer extends AbstractDisplayer {

    @Override
    public void run() {
        System.out.println("运行 CRT显示器");
    }
}

/**验证:通过聚合实现不同游戏使用不同那个的显示器玩**/
public static void main(String[] args) {

/**输出:
启动任天堂游戏
运行彩色显示器
启动腾讯游戏
运行老式黑白显示器
*/

    AbstractGame nintendoGame = new NintendoGame();
    nintendoGame.setAbstractDisplayer(new LCDDisplay());
    nintendoGame.play();
    AbstractGame tencentGame = new TencentGame();
    tencentGame.setAbstractDisplayer(new CRTDisplayer());
    tencentGame.play();
}

         组合模式

定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。用来描述部分与整体的关系,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,所以组合模式的使用场景就是出现树形结构的地方。比如:树形菜单,文件目录等树形结构数据的操作

优点

  1. 高层模块调用简单
  2. 节点自由增加

缺点

在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则

模式代码实现

/**
 * 每个一级菜单有子菜单,二级子菜单有三级菜单,以此形成一个树形结构
 * @date: 2019/3/12 17:46
 * @description:菜单类
 */
@Data
public class Menu {

    /**
     * 菜单名
     */
    private String name;

    /**
     * 路径
     */
    private String path;

    private List<Menu> menus;

    public Menu(String name, String path) {
        this.name = name;
        this.path = path;
        menus = new ArrayList<>();
    }

    public void add(Menu menu){
        menus.add(menu);
    }

    public void remove(Menu menu){
        menus.remove(menu);
    }
}

/**
*验证
*Menu(name=标签, path=/标签, menus=[Menu(name=子菜单1, path=/子菜单1, menus=[])])
*Menu(name=归档, path=/归档, menus=[Menu(name=子菜单2, path=/子菜单2, menus=[])])
*/
public static void main(String[] args) {
    Menu home = new Menu("首页","/");
    Menu tag = new Menu("标签","/标签");
    Menu archive = new Menu("归档","/归档");
    Menu menu1 = new Menu("子菜单1","/子菜单1");
    Menu menu2 = new Menu("子菜单2","/子菜单2");
    home.add(tag);
    home.add(archive);
    tag.add(menu1);
    archive.add(menu2);
    home.getMenus().forEach(menu ->{
        System.out.println(menu);
    });
}

         装饰器模式

装饰器模式可以为一个现有的类增加新功能,又不改变其结构,要求装饰类和被装饰类实现同一个接口,装饰类持有被装饰类的实例。
定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
当我们要拓展一个类的功能时,可以选择使用集成或者装饰器模式,继承的实现是静态,而装饰器模式实现的是动态的,装饰器模式的缺点是会产生很多功能类似的类

装饰器模式可以为一个现有的类增加新功能,又不改变其结构,要求装饰类和被装饰类实现同一个接口,装饰类持有被装饰类的实例

模式类图

装饰器模式

角色

  1. 抽象接口:able接口,最核心组件,被装饰类和装饰类都实现此接口
  2. 被装饰类:Source,要被装饰的类
  3. 装饰类:Decorator,持有Source类实例,装饰Source类,为Source类动态添加新功能

优点

装饰类和被装饰类相互解耦

缺点

多层的装饰比较复杂,减少装饰类的数量,可以降低系统的复杂度

模式代码实现
以打电话为例,涉及Phoneable(接口),Phone(被装饰类),Decorator(装饰类)

/**
 * 被装饰类和装饰类都实现此接口
 * @date: 2019/3/1 10:20
 * @description: 手机接口
 */
public interface Phoneable {

    /**
     * 打电话
     */
    void call();
}

/**
 * 实现Phoneable接口
 * @date: 2019/3/1 10:19
 * @description: 被装饰类
 */
public class Phone implements Phoneable{

    @Override
    public void call() {
        System.out.println("打电话");
    }
}


/**
 *实现Phoneable接口,持有被装饰类实例
 * @date: 2019/3/1 10:26
 * @description: 装饰类
 */
public class Decorator implements Phoneable{

   private Phone phone;

   public Decorator(Phone phone) {
       this.phone = phone;
   }

    @Override
    public void call() {
        System.out.println("装饰器模式 前置功能:打电话先拨号");
        phone.call();
        System.out.println("装饰器模式 后置功能:挂断电话");
    }
}

public static void main(String[] args) {
    /**
    装饰器模式 前置功能:打电话先拨号
打电话
装饰器模式 后置功能:挂断电话
    **/
    Decorator decorator = new Decorator(new Phone());
    decorator.call();
}

         外观模式

定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。提供一个高层次的接口,使得子系统更易于使用。外观模式是为了解决类与类之间的责任关系和依赖关系的,通过提供一个Facade类来隐藏这些复杂的类之间关系的调用,并提供一个接口,供外部调用,利用这种方式进行类之间的解耦

优点

  1. 减少类之间依赖,只对Facade类进行依赖,对Facade类里的复杂的类没有关联
  2. 提高了灵活性,只对Facade类进行依赖,Facade类内部可自由变化
  3. 提高安全性,只能对Facade类进行访问,无法访问到Facade类里复杂的类

*缺点

不符合开闭原则,如果Facade类设计不好,后期无法通过对Facade类重写覆盖去解决问题,只能改代码

模式代码实现
以汽车启动为例,需要启动引擎,仪表盘,大灯等,有可能需要先启动引擎之后才可以启动仪表盘,或者大灯的启动依赖引擎的启动等,这个时候就需要一个Facade类来隐藏这些复杂的依赖关系。

实例涉及的类:Engine(引擎),Armaturenbrett(仪表盘),Headlight(大灯),Car(汽车类,facade类)
Car类封装了启动引擎,仪表盘,大灯等操作,客户端只需要调用car类的start()方法即可启动上述组件,而不用去对实例化每个类然后去调用其start(),利用这种方式进行解耦。

/**
 * @author: chenmingyu
 * @date: 2019/3/1 14:53
 * @description: 引擎
 */
public class Engine {

    public void start(){
        System.out.println("启动引擎...");
    }
}

/**
 * @author: chenmingyu
 * @date: 2019/3/1 15:02
 * @description: 仪表盘
 */
public class Armaturenbrett {

    public void start(){
        System.out.println("启动仪表盘...");
    }
}

/**
 * @author: chenmingyu
 * @date: 2019/3/1 14:53
 * @description:
 */
public class Headlight {

    public void start(){
        System.out.println("启动大灯...");
    }
}


/**
 * @author: chenmingyu
 * @date: 2019/3/1 14:57
 * @description:
 */
public class Car {

    /**
     * 发动机
     */
    private Engine engine;
    /**
     * 仪表盘
     */
    private Armaturenbrett armaturenbrett;
    /**
     * 大灯
     */
    private Headlight headlight;

    public Car() {
        this.engine = new Engine();
        this.armaturenbrett = new Armaturenbrett();
        this.headlight = new Headlight();
    }

    public void start(){
        System.out.println("启动汽车...");
        engine.start();
        armaturenbrett.start();
        headlight.start();
        System.out.println("汽车以启动...");
    }
}


/**
启动汽车...
启动引擎...
启动仪表盘...
启动大灯...
汽车以启动...

*/
public static void main(String[] args) {
    Car car = new Car();
    car.start();
}

         代理模式

定义:为其他对象提供一种代理以控制对这个对象的访问,代理模式就是在操作原对象的时候,多出来一个代理类,用来对原对象的访问进行控制和替代原对象进行一些操作。与装饰器模式的区别,代理模式主要是对原对象的访问进行控制,而装饰器模式主要是为了对原对象增加新的功能

模式类图

代理模式

角色

  1. 抽象接口:able,定义业务接口
  2. 被代理类:Source
  3. 代理类:Proxy,用来对原对象的访问进行控制和替代原对象进行一些操作
  4. 客户端:Client,负责调用代理类

优点

  1. 职责清晰,被代理类只关心实际的业务逻辑,不关心其他事情
  2. 高扩展性,被代理类可以随意更改,不会影响到代理类对其的访问控制

模式代码实现
以打电话为例

/**
 * 抽象接口,被代理类和代理类都实现此接口
 * @date: 2019/3/1 10:20
 * @description: 手机接口
 */
public interface Phoneable {

    /**
     * 打电话
     */
    void call();
}

/**
 * 实现Phoneable接口
 * @date: 2019/3/1 10:19
 * @description: 被代理类
 */
public class Phone implements Phoneable {

    @Override
    public void call() {
        System.out.println("打电话");
    }
}

/**
 * 实现Phoneable接口,持有被代理类实例,提供validate()对被代理类的访问进行控制
 * @date: 2019/3/1 14:16
 * @description: 代理类
 */
public class Proxy implements Phoneable {

    private Phone phone = new Phone();

    @Override
    public void call() {
        if(validate()){
            phone.call();
        }
        System.out.println("结束通话");
    }

    /**
     * 检查手机号
     * @return
     */
    private Boolean validate(){
        System.out.println("手机号验证通过");
        return Boolean.TRUE;
    }
}

/**
*验证输出
*手机号验证通过
*打电话
*结束通话
*/
public static void main(String[] args) {
    Proxy proxy = new Proxy();
    proxy.call();
}

         享元模式

定义:运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。主要用来减少对象的创建,用来减少内存和提高性能,比较常见的连接池,缓冲池这类的池技术都是享元模式

享元模式的两种状态

  1. 内部状态:在享元对象内部不随外界环境改变而改变的共享部分
  2. 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态

模式类图

享元模式

角色:

  1. 抽象享元角色:Flyweight产品的抽象类,用来定义对象的外部状态和内部状态
  2. 具体享元角色:ConcreteFlyweightunshareConcreteFlyweight
  3. 享元工厂:FlyweightFactory用于构造一个池容器,提供从容器中获取对象的方法

优点

减少对象的创建,降低内存的占用,增强性能

缺点

使用享元模式使程序逻辑变得更复杂

模式代码实现

/**
 * 
 * @date: 2019/4/1 17:32
 * @description: 抽象享元角色
 */
public interface Flyweight {

    /**
     * 学习
     * @param user
     */
    void study(String user);
}

/**
 * 
 * @date: 2019/4/1 17:40
 * @description: 具体享元角色
 */
public class ConcreteFlyweight implements Flyweight {

    /**
     * 内部状态
     */
    private String interiorState;

    public ConcreteFlyweight(String interiorState) {
        this.interiorState = interiorState;
    }

    @Override
    public void study(String user) {
        System.out.println(user+"学习"+interiorState+"课程");
    }
}

/**
 * 
 * @date: 2019/4/1 18:05
 * @description:享元工厂
 */
public class FlyweightFactory {

    private static final Map<String,ConcreteFlyweight> FLYWEIGHTMAP = new HashMap<>(16);

    /**
     * 获取ConcreteFlyweight
     * @param s
     * @return
     */
    public static Flyweight getFlyweight(String s){
        ConcreteFlyweight flyweight = FLYWEIGHTMAP.get(s);
        if(null == flyweight){
            flyweight = new ConcreteFlyweight(s);
            FLYWEIGHTMAP.put(s,flyweight);
        }
        return flyweight;
    }

    /**
     * 获取FLYWEIGHTMAP数量
     * @return
     */
    public static int getFlyweightSize() {
        return FLYWEIGHTMAP.size();
    }
}

/**
输出:
a学习java课程
b学习go课程
c学习java课程
true
Flyweight 总数:2
*/
public static void main(String[] args) {

    Flyweight javaFlyweight = FlyweightFactory.getFlyweight("java");
    javaFlyweight.study("a");

    Flyweight goFlyweight = FlyweightFactory.getFlyweight("go");
    goFlyweight.study("b");

    Flyweight javaFlyweight1 = FlyweightFactory.getFlyweight("java");
    javaFlyweight1.study("c");

    System.out.println(javaFlyweight == javaFlyweight1);
    System.out.println("Flyweight 总数:"+FlyweightFactory.getFlyweightSize());
}

从代码上可以看出java,go就是内部状态,不会随着外界环境改变而改变,是共享部分,而外部状态a,b,c则由环境决定,是共享模式中的不可变部分


    行为型设计模式

关注于对象之间的通信

         模板方法模式

定义:一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
在父类(抽象类)中定义好算法的流程,提供抽象方法,针对不同的实现交由不同的子类去实现,通过这种方式将公共代码提取出来封装在父类中,然后父类去控制行为,子类负责实现,这样当有很多子类的时候,如果要修改算法,只需要在父类中更改算法的行为

优点:

1.封装不变部分,扩展可变部分
2.提取公共部分代码,便于维护
3.行为由父类控制,子类实现

代码实现:
用 支付为例,涉及AbstractPay,BankCardPay,WeChatPay三个类。抽象类封装了一个final类型的方法,方法中调用其他的抽象方法,制定业务流程骨架,抽象方法交由子类去实现。当有多个子类实现逻辑相同时,建议使用模板方法实现

/**
 * 定义了三个抽象方法和一个控制支付流程的方法,也就是模板方法,这个方法使用final修饰,防止被重写
 * @date: 2019/2/13 16:58
 * @description: 抽象类,定义支付流程
 */
public abstract class AbstractPay {

    /**
     * 获取支付信息
     */
    abstract String getPayInfo();

    /**
     * 执行支付
     */
    abstract void executePay();

    /**
     * 支付反馈信息
     */
    abstract void payMessage();

    /**
     * 支付
     */
    public final void pay(){
        String info = getPayInfo();
        System.out.println("支付账户:"+info);
        executePay();
        payMessage();
    }
}



/**
 * 提供银行卡支付,继承AbstractPay类重写三个抽象方法,提供银行卡支付的实现
 * @date: 2019/2/13 17:18
 * @description: 银行卡支付
 */
public class BankCardPay extends AbstractPay{

    @Override
    String getPayInfo() {
        return "我的银行卡信息";
    }

    @Override
    void executePay() {
        System.out.println("银行卡支付了");
    }

    @Override
    void payMessage() {
        System.out.println("银行卡方式支付,发短息");
    }
}

/**
 * 提供微信支付,继承AbstractPay类重写三个抽象方法,提供微信支付的实现
 * @date: 2019/2/13 17:20
 * @description: 微信支付
 */
public class WeChatPay extends AbstractPay{

    @Override
    String getPayInfo() {
        return "我的微信支付信息";
    }

    @Override
    void executePay() {
        System.out.println("微信支付了");
    }

    @Override
    void payMessage() {
        System.out.println("微信方式支付,微信内发服务通知");
    }
}

/**
支付账户:我的银行卡信息
银行卡支付了
银行卡方式支付,发短息
... 分割线 ...
支付账户:我的微信支付信息
微信支付了
微信方式支付,微信内发服务通知
*/
public static void main(String[] args) {
    BankCardPay bankCardPay = new BankCardPay();
    bankCardPay.pay();
    System.out.println("... 分割线 ...");
    WeChatPay weChatPay = new WeChatPay();
    weChatPay.pay();
}

         策略模式

定义:定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。在系统中提供一组策略,并将每个策略封装成类,使他们可以相互转换,具体策略的选择由客户端决定,这就是策略模式,当系统中有很多if…else的时候可以考虑使用策略模式,策略模式可以灵活的增加策略类,进行扩展,但是可能会由于策略过多导致,策略类太多。如果系统中的一个策略家族的具体策略数量超过4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题,否则日后的系统维护就会非常困难

模式类图

策略模式

角色

  1. 策略接口:TripStrategy
  2. 具体策略类:BicycleTripStrategyCarTripStraregy实现了策略接口的具体策略类
  3. 封装策略类:TripContext,使用某种策略的类

优点

  1. 算法可以自由切换
  2. 避免使用多重条件判断
  3. 扩展性良好,增加一种策略只需要新增一个策略类

缺点

  1. 策略类数量增多,每新增一种策略,就需要增加一个策略类
  2. 所有的策略类都需要对外暴露,上层模块必须知道有哪些策略,然后才能决定使用哪一个策略,违法迪米特法则

代码实现

/**
 * 策略接口
 * @date: 2019/2/21 18:15
 * @description: 出行策略
 */
public interface TripStrategy {

    /**
     * 出行方式
     */
    void tripMode();
}

/**
 * 
 * @date: 2019/2/21 18:19
 * @description: 自行车出行策略类
 */
public class BicycleTripStrategy implements TripStrategy{

    @Override
    public void tripMode() {
        System.out.println("选择骑自行车出行");
    }
}

/**
 * 
 * @date: 2019/2/21 18:21
 * @description: 开车出行策略类
 */
public class CarTripStraregy implements TripStrategy {

    @Override
    public void tripMode() {
        System.out.println("选择开车出行");
    }
}

/**
 * 
 * @date: 2019/2/21 18:22
 * @description: 策略context
 */
public class TripContext {

    /**
     * 出行策略
     */
    private TripStrategy tripStrategy;

    public TripContext(TripStrategy tripStrategy) {
        this.tripStrategy = tripStrategy;
    }

    /**
     * 选择出行策略
     */
    public void chooseTripMode(){
        this.tripStrategy.tripMode();
    }
}

/**
选择骑自行车出行
换一种出行方案
选择开车出行
*/
public static void main(String[] args) {

    TripContext tripContext = new TripContext(new BicycleTripStrategy());
    tripContext.chooseTripMode();
    System.out.println("换一种出行方案");
    tripContext = new TripContext(new CarTripStraregy());
    tripContext.chooseTripMode();
}

         观察者模式

定义:对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新

模式类图

观察者模式

角色:

  1. 被观察者:Subject定义一个被观察者必须实现职责,包括动态增加,删除,通知观察者
  2. 观察者:Observer接受到观察者修改消息,执行自身逻辑
  3. 具体观察者:ConctreteSubject继承Subject,拥有自己的业务逻辑,具有被观察者基本功能,对某些事件进行通知
  4. 具体的观察者:ConcreteObserver具体观察者,在接受到被观察者变更消息后,进行各自业务处理

优点

  1. 观察者和被观察者之间是抽象耦合,容易拓展
  2. 通过触发机制可以创建成一种链式触发机制,形成多级触发

缺点

  1. 执行效率
    当通知观察者是顺序执行时,需要考虑整个观察者列表的数量,对整个通知事件执行效率的影响,可以考虑使用异步通知,同时尽量避免多级触发事件
  2. 循环依赖
    当观察者和被观察之间形成循环依赖,会导致循环调用,比如A改变通知B,B改变通知C,C改变通知A,注意避免循环依赖的发生

模式代码实现
以用户修改密码为例,当用户密码改变后,使用短信和邮件对用户进行消息提醒

SmsObserverEmailObserver观察到UserInfo用户麻辣香锅更改密码为mlxg

/**
 * 定义被观察者基本功能
 * @date: 2019/3/15 13:44
 * @description: 被观察者接口
 */
public interface Subjectable {

    /**
     * 增加观察者
     */
    void addObserver(Observerable observer);

    /**
     * 移除观察者
     */
    void removeObserver(Observerable observer);

    /**
     * 通知观察者
     */
    void notifyObserver(Object object);
}


/**
 * 被观察者类的公共父类,实现了被观察者基本功能的实现,使用Vector为观察者列表
 * @date: 2019/3/15 11:24
 * @description: 被观察者抽象类
 */
public abstract class AbstractSubject implements Subjectable{

    /**
     * 观察者列表
     */
    private Vector<Observerable> observers = new Vector();

    @Override
    public void addObserver(Observerable observer){
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observerable observer){
        observers.add(observer);
    }

    @Override
    public void notifyObserver(Object object){
        observers.forEach(observer->{
            observer.update(object);
        });
    }
}

/**
 * 被观察者类
 * @date: 2019/3/15 13:50
 * @description: 被观察者
 */
@Data
public class UserInfo extends AbstractSubject{

    /**
     * 昵称
     */
    private String nickName;
    /**
     * 密码
     */
    private String password;

    /**
     * 修改密码
     * @param password
     */
    public void updatePassword(String password){
        this.password = password;
        this.notifyObserver(password);
    }

    public UserInfo(String nickName, String password) {
        this.nickName = nickName;
        this.password = password;
    }
}


/**
 * 观察者接口
 * @date: 2019/3/15 13:40
 * @description: 观察者
 */
public interface Observerable {

    /**
     * 被观察者变化触发事件
     */
    void update(Object object);
}

/**
 * 具体观察者:接收到被观察者密码变更,发送短信提醒
 * @date: 2019/3/15 13:56
 * @description: 短信观察者
 */
public class SmsObserver implements Observerable {

    @Override
    public void update(Object object) {
        if(null == object){
            return;
        }
        System.out.println("短信观察者");
        System.out.println("短信发送提醒:密码更改为:"+object.toString());
    }
}
/**
 * 具体观察者: 接收到被观察者密码变更,发送邮件提醒
 * @date: 2019/3/15 13:54
 * @description: 邮件观察者
 */
public class EmailObserver implements Observerable{

    @Override
    public void update(Object object) {
        if(null == object){
            return;
        }
        System.out.println("邮件观察者");
        System.out.println("邮件发送提醒:密码更改为:"+object.toString());
    }
}

/**
校验输出
短信观察者
短信发送提醒:密码更改为:mlxg
邮件观察者
邮件发送提醒:密码更改为:mlxg
*/
public static void main(String[] args) {

    UserInfo userInfo = new UserInfo("麻辣香锅","malaxiangguo");

    SmsObserver smsObserver = new SmsObserver();
    EmailObserver emailObserver = new EmailObserver();

    userInfo.addObserver(smsObserver);
    userInfo.addObserver(emailObserver);

    userInfo.updatePassword("mlxg");
}

         迭代器模式

定义: 它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式就是为解决遍历元素而诞生的,java而言,使用java提供的iterator就可以了,不用需要手动去写迭代器

模式类图

迭代器模式

角色:

  1. 抽象迭代器:Iterator,负责定义访问和遍历元素的接口
  2. 具体迭代器:ConcreteIterator,实现Iterator接口
  3. 抽象容器:Aggregate,负责提供创建具体迭代器角色的接口
  4. 具体容器: ConcreteAggregate,实现Aggregate接口

模式代码实现

/**
 * 提供两个方法,返回下一个元素,是否还有下一个元素
 * @date: 2019/3/17 19:07
 * @description: 抽象迭代器
 */
public interface Iterator {

    /**
     * 返回下一个元素
     * @return
     */
    public Object next();

    /**
     * 是否还有下一个元素
     * @return
     */
    public boolean hasNext();

}


/**
 * cursor为容器遍历元素时当前元素的容器下标
 * @date: 2019/3/18 09:50
 * @description: 具体迭代器实现
 */
public class ConcreteIterator implements Iterator {

    /**
     * 当前位置
     */
    public int cursor = 0;

    /**
     * 容器
     */
    private List<String> list;

    public ConcreteIterator(List<String> list) {
        this.list = list;
    }

    @Override
    public Object next() {
        if(this.hasNext()){
            return this.list.get(this.cursor++);
        }
        return null;
    }

    @Override
    public boolean hasNext() {
        if(this.cursor == this.list.size()){
            return false;
        }else{
            return true;
        }
    }
}



/**
 * 提供三个方法,增加容器元素,删除容器元素,获取当前容器的迭代器
 * @date: 2019/3/18 09:51
 * @description: 抽象容器
 */
public interface Aggregate {

    /**
     * 增加元素
     * @param s
     */
    public void add(String s);

    /**
     * 删除元素
     * @param s
     */
    public void remove(String s);

    /**
     * 获取当前容器的迭代器
     * @return
     */
    public Iterator createIterator();
}


/**
 * @date: 2019/3/18 11:16
 * @description: 具体容器实现
 */
public class ConcreteAggregate implements  Aggregate {

    private List<String> list = new ArrayList<>();

    @Override
    public void add(String s) {
        list.add(s);
    }

    @Override
    public void remove(String s) {
        list.remove(s);
    }

    @Override
    public Iterator createIterator() {
        return new ConcreteIterator(list);
    }
}

/**
验证输出
first title
second title
third title
*/
public static void main(String[] args) {
    Aggregate aggregate = new ConcreteAggregate();
    aggregate.add("first title");
    aggregate.add("second title");
    aggregate.add("third title");
    Iterator iterator = aggregate.createIterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
}

         责任链模式

定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。每个对象持有对下一个对象的引用,形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求.

模式类图

责任链模式

角色

  1. 抽象处理接口:Handler
  2. 具体处理类:ConcreteHandler

优点

将请求与处理分开,请求者可以不用知道到底是需要谁来处理的,两者解耦,提高系统灵活性,增加新的请求处理类很方便

缺点

当调用链比较长的时候,调试不方便,写代码时要注意避免循环调用

模式代码实现
以用户注册为例,注册方式分为普通用户注册和Vip用户注册,使用责任链模式将两种注册方式链起来

/**
 * 抽象接口定义了两个方法:用户注册方法,责任链调用方法
 * @date: 2019/3/22 20:32
 * @description:
 */
public interface Handler {

    /**
     * 注册操作
     * @param userName 用户名
     */
    String operator(String userName);

    /**
     * 责任链调用处理逻辑
     *      如果可以自己处理则自己处理,如果处理不了,交由下一个处理类处理
     * @param level
     * @param userName
     * @return
     */
    String handleMessage(Integer level,String userName);
}

/**
 * 继承Handler,getUserLevel()交由子类实现,handleMessage(Integer level,String userName)用final修饰,子类无法重写责任链调用的处理逻辑
 * @date: 2019/3/23 10:42
 * @description: 抽象类
 */
@Data
public abstract class AbstractHandle implements Handler {

    /**
     * 下一个处理器
     */
    public Handler nextHandler;

    /**
     * 获取用户等级
     * @return
     */
    protected abstract Integer getUserLevel();

    @Override
    public final String handleMessage(Integer level,String userName){
        String result = "";
        if(this.getUserLevel().equals(level)){
            result = this.operator(userName);
        }else{
            result = this.nextHandler.handleMessage(level,userName);
        }
        return result;
    }
}


/**
 * 
 * @date: 2019/3/23 10:46
 * @description: 普通用户注册
 */
@Data
public class UserRegisterHandle extends AbstractHandle {

    /**
     * 普通会员
     */
    public Integer ordinaryLevel = 1;

    @Override
    public String operator(String userName) {
        System.out.println("普通会员注册:"+userName);
        return userName;
    }

    @Override
    protected Integer getUserLevel() {
        return ordinaryLevel;
    }
}


/**
 * 
 * @date: 2019/3/23 11:02
 * @description: vip会员注册
 */
public class VIPUserRegisterHandle extends AbstractHandle {

    /**
     * VIP会员
     */
    public Integer VIPLevel = 2;

    @Override
    public String operator(String userName) {
        System.out.println("VIP会员注册:"+userName);
        return userName;
    }

    @Override
    protected Integer getUserLevel() {
        return VIPLevel;
    }
}

/**
输出结果
VIP会员注册:mingyu
普通会员注册:mingyu
*/
public static void main(String[] args) {

    UserRegisterHandle userRegisterHandle = new UserRegisterHandle();
    VIPUserRegisterHandle vipUserRegisterHandle = new VIPUserRegisterHandle();
    userRegisterHandle.setNextHandler(vipUserRegisterHandle);

    userRegisterHandle.handleMessage(2,"mingyu");
    userRegisterHandle.handleMessage(1,"mingyu");
}

         命令模式

定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能

模式类图*

命令模式

角色:

  1. 接受者:Receiver最后的执行者,具体的业务逻辑
  2. 命令接口:Command需要执行的所有命令都在这里声明
  3. 具体命令:ConcreteCommand实现Command接口
  4. 调用者:Invoker接收到命令,并执行命令,命令中调用接受者

优点

  1. 降低类之间解耦,调用者与接受者之间没有任何依赖关系
  2. 扩展性高,非常容易地扩展Command类的子类

缺点

如果命令比较多,Command类的子类就会变得非常的多

模式代码实现
接受者有两个方法,打游戏和学习,通过实现命令接口写了两个具体的命令实现类,分别代表玩游戏命令和学习命令,通过调用者去调用不同的命令

/**
 * 
 * @date: 2019/3/25 10:48
 * @description: 接受者角色
 */
public class Receive {

    public void play(){
        System.out.println("打游戏吧");
    }

    public void study(){
        System.out.println("要学习了");
    }
}

/**
 * 
 * @date: 2019/3/25 10:51
 * @description: 命令接口
 */
public interface Commandable {

    /**
     * 命令执行接口
     */
    void execute();
}


/**
 * 玩游戏命令,持有receive实例
 * @date: 2019/3/25 10:52
 * @description: 玩游戏命令
 */
public class GameCommand implements Commandable{

    private Receive receive;

    public GameCommand(Receive receive) {
        this.receive = receive;
    }

    @Override
    public void execute() {
        this.receive.play();
    }
}


/**
 * 学习命令持有receive实例
 * @date: 2019/3/25 10:54
 * @description: 学习命令
 */
public class StudyCommand implements Commandable {

    private Receive receive;

    public StudyCommand(Receive receive) {
        this.receive = receive;
    }

    @Override
    public void execute() {
        this.receive.study();
    }
}


/**
 * 调用者持有Commandable的实现类的实例
 * @date: 2019/3/25 10:51
 * @description: 调用者
 */
@Data
public class Invoker {

    /**
     * 持有Commandable的实现类的实例
     */
    private Commandable commandable;

    /**
     * 执行命令
     */
    public void action(){
        this.commandable.execute();
    }
}
/**
验证输出
打游戏吧
要学习了
**/
public static void main(String[] args) {

    Invoker invoker = new Invoker();
    Receive receive = new Receive();

    Commandable gameCommand = new GameCommand(receive);
    invoker.setCommandable(gameCommand);
    invoker.action();

    Commandable studyCommand = new StudyCommand(receive);
    invoker.setCommandable(studyCommand);
    invoker.action();
}

         备忘录模式

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态,其实就是在某个时刻备份了对象的状态,在更改对象状态后,可以通过备份将对象还原成备份时刻的状态

模式类图

备忘录模式

角色

  1. 原始对象:Originator 原对象,提供创建备份,和恢复备份数据功能
  2. 备忘录:Memento负责存储Originator对象的内部状态,在需要的时候提供发起人需要的内部状态
  3. 备忘录管理员:Caretaker对备忘录进行管理、保存和提供备忘录

优点

  1. 提供一种可以备份恢复的机制,客户端可以方便的恢复某个历史状态
  2. 封装性好,客户端不需要关心状态的保存细节

缺点

如果状态太多,会导致类膨胀

代码实现

/**
 * 提供创建备份,和恢复备份数据功能
 * @date: 2019/3/25 20:42
 * @description: 原始对象
 */
@Data
public class Originator{

    private String name;

    /**
     * 创建备忘录
     * @return
     * @throws CloneNotSupportedException
     */
    public Memento createMemento(){
        return new Memento(this.getName());
    }

    /**
     * 恢复一个备忘录
     * @param memento
     */
    public void restoreMemento(Memento memento){
        this.setName(memento.getName());
    }

    public Originator(String name) {
        this.name = name;
    }
}


/**
 * 备忘录负责存储Originator的内在状态
 * @date: 2019/3/25 20:47
 * @description: 备忘录
 */
@Data
public class Memento {

    private String name;

    public Memento(String name) {
        this.name = name;
    }
}


/**
 * 备忘录管理员持有Mementod的实例
 * @date: 2019/3/25 20:57
 * @description: 备忘录管理员
 */
@Data
public class Caretaker {

    private Memento memento;

    public Caretaker(Memento memento) {
        this.memento = memento;
    }
}

/**
验证输出

mingyu
……
wuwu
……
mingyu
*/
public static void main(String[] args) {
    Originator originator = new Originator("mingyu");
    Caretaker caretaker = new Caretaker(originator.createMemento());
    System.out.println(originator.getName());
    System.out.println("……");

    originator.setName("wuwu");
    System.out.println(originator.getName());

    System.out.println("……");
    originator.restoreMemento(caretaker.getMemento());
    System.out.println(originator.getName());
}

         状态模式

定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为

模式类图

状态模式

角色:

  1. 抽象状态接口:State定义抽象状态接口
  2. 具体状态实现:ConcreteState实现State接口,不同状态实现逻辑不同
  3. 环境类:Context持有一个状态实例

看类图就知道这个模式很简单了

优点

  1. 结构清晰,遵循设计原则,每种状态都对应一个实现类,同一扩展
  2. 封装性非常好,状态变换放置到环境类的内部来实现

缺点

如果类的成员变量过多,多次保存,会消耗一定的内存

模式代码实现

/**
 * 抽象状态接口
 * @date: 2019/3/26 19:42
 * @description: 状态接口
 */
public interface Stateable {

    /**
     * 搞事情
     */
    void doSometing();
}


/**
 * 具体状态实现学习状态
 * @date: 2019/3/26 19:44
 * @description:
 */
public class StudyState implements Stateable{

    @Override
    public void doSometing() {
        System.out.println("学习状态,就要学习");
    }
}


/**
 * 游戏状态
 * @date: 2019/3/26 19:43
 * @description: 游戏状态
 */
public class GameState implements Stateable {

    @Override
    public void doSometing() {
        System.out.println("游戏状态,就打游戏");
    }
}

/**
 * 环境类
 * @date: 2019/3/26 19:45
 * @description: 环境类
 */
@Data
public class Context {
    /**
     * 持有具体状态实例
     */
    private Stateable stateable;
}
/**
验证输出

学习状态,就要学习
游戏状态,就打游戏
*/
public static void main(String[] args) {
    Context context = new Context();

    Stateable studyState = new StudyState();
    context.setStateable(studyState);
    context.getStateable().doSometing();

    Stateable gameState = new GameState();
    context.setStateable(gameState);
    context.getStateable().doSometing();
}

         访问者模式

定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作,访问者模式就是将数据结构与数据操作相分离

模式类图

访问者模式

角色:

  1. 抽象访问者:Visitor
  2. 具体访问者:ConcreteVisitor
  3. 抽象元素:Element
  4. 具体元素:ConcreteElement

优点

  1. 符合单一职责原则,具体元素负责数据的加载,具体访问者负责操作数据
  2. 扩展性好,如果需要新增操作方式,只需要在具体访问者增加方法,灵活性高

缺点

  1. 违背了迪米特法则,访问者类需要了解元素类的内部实现
  2. 违背了依赖倒置转原则,应该面向接口编程而不是类
  3. 元素类增加元素复杂,对应的访问者类也要根据需求看看是否需要更改

模式代码实现


/**
 *  抽象访问者
 * @date: 2019/3/26 20:52
 * @description: 抽象访问者
 */
public interface Visitorable {

    /**
     * 接收元素
     * @param sub
     */
    void visit(Element sub);
}

/**
 *  具体访问者
 * @date: 2019/3/26 20:54
 * @description: 具体访问者
 */
public class ConcreteVisitor implements Visitorable {

    @Override
    public void visit(Element sub) {
        sub.doSomething();
        System.out.println("访问者学习设计模式");
    }
}

/**
 *  抽象元素
 * @date: 2019/3/26 20:52
 * @description:
 */
public interface Element {

    /**
     * 接受访问者
     * @param visitor
     */
    void accept(Visitorable visitor);

    /**
     * 自定义方法
     */
    void doSomething();
}

/**
 *  具体元素
 * @date: 2019/3/26 20:56
 * @description: 具体元素
 */
public class ConcreteElement implements Element {

    @Override
    public void accept(Visitorable visitor) {
        visitor.visit(this);
    }

    @Override
    public void doSomething() {
        System.out.println("要不要学一会儿设计模式");
    }
}
/**
验证输出

要不要学一会儿设计模式
访问者学习设计模式
**/
public static void main(String[] args) {
    Visitorable visitor = new ConcreteVisitor();
    Element subject = new ConcreteElement();
    subject.accept(visitor);
}

         中介者模式

定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互,中介者模式主要用来降低类类之间的耦合

模式类图

中介者模式

角色

  1. 抽象中介者:Mediator
  2. 具体中介者:ConcreteMediator
  3. 对象类:Colleague中介者持有该类实例

优点

减少类之间的耦合,对象类只依赖中介者类,将原本可能会出现的一对多的依赖,变成一对一的依赖,降低类之间的耦合

缺点

中介者类膨胀,对象类越多,中介者类处理的依赖就越多,中介者类越膨胀,处理逻辑越复杂

模式代码实现

/**
 * 
 * @date: 2019/3/31 13:46
 * @description: 抽象中介者
 */
public interface Mediatorable {

    /**
     * 创建中介者
     */
    void createMediator();

    /**
     * 打游戏
     */
    void playGame();
}


/**
 * 具体中介者持有对象类依赖
 * @date: 2019/3/31 13:47
 * @description: 具体中介者
 */
public class ConcreteMediator implements Mediatorable {

    /**
     * 对象类1
     */
    private Colleague1 colleague1;
    /**
     * 对象类2
     */
    private Colleague2 colleague2;

    @Override
    public void createMediator() {
        colleague1 = new Colleague1(this);
        colleague2 = new Colleague2(this);
    }

    @Override
    public void playGame() {
        colleague1.playGame();
        colleague2.playGame();
    }
}


/**
 * 对象抽象类依赖中介者类
 * @date: 2019/3/31 13:51
 * @description: 抽象对象类
 */
public abstract class Colleague {

    /**
     * 依赖中介者类
     */
    protected Mediatorable mediatorable;

    public Colleague(Mediatorable mediatorable) {
        this.mediatorable = mediatorable;
    }

    /**
     * 玩游戏
     */
    public abstract void playGame();
}



/**
 * 具体对象类对象类1
 * @date: 2019/3/31 13:54
 * @description: 对象类1
 */
public class Colleague1 extends Colleague {

    public Colleague1(Mediatorable mediatorable) {
        super(mediatorable);
    }

    @Override
    public void playGame() {
        System.out.println("Colleague1 玩游戏");
    }
}


/**
 * 具体对象类对象类2
 * @date: 2019/3/31 13:56
 * @description:
 */
public class Colleague2 extends Colleague {

    public Colleague2(Mediatorable mediatorable) {
        super(mediatorable);
    }

    @Override
    public void playGame() {
        System.out.println("Colleague2 玩游戏");
    }
}
/**
验证输出

Colleague1 玩游戏
Colleague2 玩游戏
**/
public static void main(String[] args) {
    Mediatorable mediatorable = new ConcreteMediator();
    mediatorable.createMediator();
    mediatorable.playGame();
}

         解释器模式

定义:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子

模式类图

解释器模式

角色:

  1. 抽象解释器: AbstractExpression 定义解释器规则
  2. 具体解释器:TerminalExpressionNonterminalExpression解释器规则具体实现

优点

易扩展,增加新的解释器规则是只需要增加一个具体解释器就好

缺点

解释器规则太多时,会导致类膨胀

模式代码实现
以验证输入的链接是否已http开头为例

/**
 * 
 * @date: 2019/3/31 15:00
 * @description: 抽象解释器
 */
public interface Expression {

    /**
     * 定义解释器规则
     * @param context
     * @return
     */
    boolean interpret(String context);
}

/**
 * 具体解释器
 * @date: 2019/3/31 15:03
 * @description:
 */
public class TerminalExpression implements Expression{

    private String data;

    public TerminalExpression(String data) {
        this.data = data;
    }

    @Override
    public boolean interpret(String context) {
        if(context.startsWith(data)){
            return true;
        }
        return false;
    }
}
/**
验证输出

http://chenmingyu.top 是以http开头:true
www.chenmingyu.top 是以http开头:false
**/
public static void main(String[] args) {
    Expression expression = new TerminalExpression("http");
    System.out.println("http://chenmingyu.top 是以http开头:"+expression.interpret("http://chenmingyu.top"));
    System.out.println("www.chenmingyu.top 是以http开头:"+expression.interpret("www.chenmingyu.top"));
}


参考资料

设计模式六大原则
陈明羽设计模式

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容