1 创建型模式

把对象的创建和使用分离。

1.1 单例模式

言简意赅,单例只允许new一个实例,所以一般放在静态方法里自己new自己,且只能new一次。
单例模式一般不用于计算,适合用于工具类,无状态。

class Singleton{
    // volatile 可以避免指令重排,当未初始化完成,地址没指好又来个线程,这是已经有初始化数据,以为有,实际无。
    private volatile static Singleton singleton;   //如果这里初始化就叫饿汉
    private Singleton(){}
    
    public static Singleton getInstance(){  // 加锁防并发
        if(singleton == null){
            // 双重检查锁,如果写在方法上,每次都要拿锁,那我们只有在第一次不为null再拿锁。
            synchronized (Singleton.class) { 
                if(singleton == null)
                     singleton = new Singleton();    //懒汉
            }
        }  
        return singleton;
    }
}        

1.2 工厂方法模式

简单工厂就是策略工厂那样通过名称拿取示例,而工厂方法要比他再高一个层级。先来探讨一下简单工厂的弊端,假设我们用策略类的名称去策略工厂拿取实例,这也就意味着我们这个工厂就只传“名称”拿实例,如果那天有些策略需要复杂一点的业务逻辑,我们需要传两个参数才知道我们要拿那个策略。我们就不得不修改工厂代码,这显然是违反开闭原则的。

于是工厂方法应运而生,当我们去一个父工厂里先拿的子工厂,在用这个子工厂去拿取对应的实例。当我们有新的工厂需求时,再继承父工厂实现一个新的工厂即可,而不用入侵父工厂代码。

子工厂虽然名字叫工厂,其实其定位更像一个方法。我们将工厂想象成对象,各种子工厂就是他的方法,依据各种子工厂去做具体的操作,类似与策略模式的解耦。

1.3 抽象工厂模式

工厂方法做到了什么呢?工厂-产品-产品的具体业务 的三段解耦。如果需要的是一堆虽然不相关,产品逻辑不同,但是却总是同时用到的产品呢?工厂方法虽然让产品可以有不同的业务逻辑了,但是还是同一个产品。而抽象工厂则是可以制作多个产品,且各自有其业务逻辑。

比如豆腐脑工厂,可以造豆腐脑和调料,当选择甜豆腐脑子工厂的时候,不仅有豆腐,并且调料给出糖。(不要想象成甜豆腐脑工厂可以拿到咸调料!你可以改变的是加红糖还是白糖!)

实际在架构层面,抽象工厂和工厂方法代码实现相似。工厂方法是一种特殊的抽象工厂,其只有一个方法。而抽象工厂可以有更多。

1.4 原型模式

当我们希望获得一个对象的信息时,如果没有对象的参数的get方法,或者对象参数是私有的,无疑是难以操作的。在原型类实现一个clone方法即可。

1.5 建造者模式

一种方法链创建对象的方式,可以直接用@builder注解一键注释。这样在生成时使用方法链new复杂对象时十分方便。

2 结构型模式

关注结构组合,让类 / 对象协作更灵活。

2.1 适配器模式

当我们需要调用一个服务B,但其和我们的现在的服务A不兼容,这时就需要适配器模式。通过适配器将B转换为我们所需的格式。适配器模式是代理模式的一种,但是适配器强调的是B服务仅是和A不兼容,不强调增强,而只是转换。
这就意味着适配器引入B即可,A调用适配器。而代理模式的代理类一般要直接引入被代理类。

2.2 桥接模式

主要用于解耦出现笛卡尔积的情况,例:有一个产品类集合{1,2,3};一个产品售卖渠道集合{A,B,C}。假设我们单独实现,显然会出现一个笛卡尔积的情况,我们要写9个类。桥接模式就提供了这种情况的解决思路。

很简单实现产品类,在产品类里实现引入售卖渠道,或者在售卖渠道里引入产品类都可以被称为桥接模式。主要看哪个类少。

2.3 装饰器模式

递归的模式。为了解决复杂流程的自定义装配问题。当我们组装电脑时,显卡,cpu,内存,硬盘都是diy的。但是落到程序中如何解决这种类似问题呢?毕竟型号这么多,难道要预先new出所有型号组成的电脑吗?显然是不可能的。想想现实我们是怎么做的,当我们在组装电脑时,装上cpu我们就把带着cpu的电脑看成一个整体a,然后拿电脑a装散热塔变成电脑b,,,然而实际上只存在一个电脑!

假设我们有个抽象类 Compent,然后装饰器Decorator 也作为一个抽象类继承了Compent,但是同时他又注入了Compent,以此递归拿到了对“自己”修改的权利。也就是说装饰器可以在不改动原类的基础上做功能的增强。比如包装有特殊规则的Set等,装饰器模式是一个常用模式。

2.4 外观(门面)模式

当我们有较多子系统时,需要有复杂的调用流程,可以建立有一个更高级的抽象类去控制调用子系统,只提供调用一些方法供使用者调用。最简单的例子就是业务开发中暴露给用户的选项是很简单,当他触发请求后,实际上有复杂的业务处理。
但是外观模式是不符合开闭原则的,比如,子系统的接口业务逻辑更改,也就意味着外观也要跟着调整。因为外观模式就是承接了业务处理逻辑的顺序。

2.5 代理模式

这是一个最能显现设计模式是某种理念的模式。代理模式就是本来将某个功能封装成一个代理,关于这个功能的一切只能走这个代理!
你会发现很多其他的设计模式不就是在做这个吗?命名模式把命令抽出来,装饰器也在外面包了一层代理式的功能。实际上代理模式是最基础的一种设计理念。代理模式侧重于不改变原来的实现,做到调用方无感知。

代理模式是一种比装饰器模式等更底层的模式,装饰器承诺了不改源代码将功能增强,且可新建方法,但是代理模式要求是在原来方法上做增强。比如动态代理技术,你做效验,去根据具体的业务DIY,影响原来的方法,但不提供新方法。

2.6 组合(复合)模式

假设面对的对象是一种复合模式,即父和子节点都具有相同的信息,方法。比如省份关系,部门关系,省有总人口,省下的市有自己的总人口,市下的区也有自己的总人口。我们就可以去利用类之间的继承关系,去实现一个类之间的递归。具体是做法是继承实现一个Composite类,实现递归方法即可。调用伪代码如下

Composite 类实现递归调用法
    @override public int count() {
    for(Counter counter : counterList) sum += counter.couni();}
// 调用
Composite china = new Composite(); //主节点
china.add(new City(sum:1000)); //直接添加
Composite ShanXi = new Composite(); // 创建子节点,尚未挂到主节点上。
ShanXi.add(new City(sum:3000));
china.add(ShanXi); // 将子节点添加至主节点
china.count();

在uml和类之间的实质关系上,组合模式和装饰器模式十分相似,甚至你可以将“Composite类看成一种装饰器“,给其他类统一增加了功能。在装饰器模式中,你也可以类-装饰器A-装饰器B一直装饰弄出一个类似的树形结构出来。比如上面例子装饰器可以用一个最基础的行政单元作为父节点,用装饰器实现为市,市再装饰为省。最后统一走一个装饰器去计数(但是实际上并不会这么做,装饰器一般只能装饰一个组件,而且这个所谓的“装饰器”实际上已经是组合模式的Composite了)。组合模式更擅长在层次结构的方向做,而装饰器更倾向于链表那种传递强化。

2.7 享元模式

当一个对象是被共享使用的,那么这个对象就需要维护两个东西:外部调用者传入的信息,内部自己的具体功能。其实就是池技术,不同的对象复用同一个/批资源,所以享元模式常和工厂模式配合。
享元模式要维护自己参数,哪个资源被占用了,哪些资源被释放了,对象传入后的动作是什么。

3 行为型模式

关注对象之间的交互、职责、行为流程

3.1 策略模式

Bird类,各种不同的fly动作,那就实现一个IFly接口,这个接口去实现各种具体的fly。

然后在Bird类本身里只需要,用IFly假装自己会飞就行了,实际上他还没拿到具体的策略,等new的时候再去拿具体的fly。并且你可以实现setFly方法,以此提供动态修改fly策略。

问题拓展1
假设策略很多呢?且策略是按照一定规律的,比如通过传金额,来得知去获得哪种服务。这时可以使用枚举的写法,来去实现UserType userType = UserType.typeOf(recharge) 直接获得对标服务的标签。这样就不用自己new了。

public enum UserType{
    NORMAL(recharge -> recharge > 0 && recharge100)
    SMALL(recharge -> recharge > 100 && recharge1000)
    BIG(recharge -> recharge > 1000 && recharge10000)

    private final IntPredicate support;
    
    UserType(IntPredicate support) {this.support = support;}
    
    public static UserType typeOf(int recharge){
        for (UserType value : values()) {
            if (value.support.test(recharge)) {
                return value;
            }
        }
        return null;    
    }
}    

问题拓展2
问题拓展1解决了传参判别策略标签的问题,但是实际上具体的策略还是静静的躺着等你用这个标签for循环找到它。
如何省去这个for循环呢。很简单用Map去存所有策略,然后用标签get了一下。恭喜你发明了策略工厂!其实单个模式都是很简单的,甚至在某些场景下虽然感觉优化了,但是不多。只有配合其他设计模式或者优化方式才真正显得“优雅”。

3.2 观察者模式

假设你(观察者)需要知道一个东西的状态,知道他啥时候发生变化,难道一直轮询吗?太浪费资源了,可以让它(被观察者)自身来提醒你。
被观察者要实现一个数组,来记录有谁在观察它。并且要实现notify来提醒所有观察者。
而观察者则需实现update,即收到通知后我干啥,去拉取最新状态之类的动作。一般为push-pull
这里就存在观察者变体,假设被观察者不是提醒,直接将消息推过来,变成了广播,变成了只有push。
问题拓展
其实可以发现,代码间虽然经过抽象可拓展性大大提高,但是观察者和被观察者之间耦合度还很高。比如当观察者被提示要去拉取最新消息时,还是得new 个被观察者出来,然后去拿数据。有没有彻底的解耦的方法呢?有的兄弟有的,发布订阅模式,其实就是增加了一个中间层,所有消息由它中介-总线。但是发布订阅模式更适用于架构层,而不是功能层,实现某一功能往往用的还是观察者模式。

3.3 命令模式

将命令看作一个对象,实现其接口与具体实现。这样调度者Invoker只用调命令,命令逻辑业务写在命令对象中,接收者Receiver写具体业务。好处显而易见,我们将命令的业务单独抽出来,就可以对其有自定义操作:撤销,排队优先级,延迟执行,日志,回滚等。很多高级的Http请求封装类就用到命令模式的设计。

3.4 模版方法模式

假设你需要一个类的流程是固定的,但是具体方法是可以DIY的。毫无疑问,用个抽象模版类固定模版,子类继承,修改那些父类可以允许重写的类或者必须实现的抽象类。

3.5 状态模式

当我们希望在某某条件(也可以视为某种状态)下想调用某种特定方法时,很容易让人想到策略模式,并且很多人以为自己在用状态模式,实际上会写成策略模式。但实际上两者的区别在于,策略模式先判断条件,在去调用对应的策略方法,且难以修改;而状态模式是,状态作为一个类,被注入到程序中,直接调用这个状态的方法,这个状态的改变也就意味着对应方法的改变。
也就是说我们的各种状态要实现自己的方法。状态模式不想其他模式那样会让你少写东西,他的功能更倾向于理清类之间的关系。

3.6 空对象模式

判空让人头大,于是可以多实现一个空对象,只实现方法但无实际内容。在创建这个对象的时候进行判空,如果为空则返回一个空对象,空对象去调用方法的时候不用判空,因为就算调用方法实际上也不会发生任何事情。而调用正常对象则正常执行即可。
这个可以省去代码编写中大量方法执行前的null判断,省去如果一个没写就报debug的情况。

3.7 迭代器模式

数据结构类往往需要遍历方法来返回元素,但将每一个迭代方法写在数据结构类是不合适的,这违反了类单一职责,并且迭代器显然可以服用的。比如森林列表和池塘列表,用一种迭代器即可。
UML特点十分明显,不仅被迭代的iterable需要引入iterator。迭代器也要引入iterable,因为迭代器需要操作被迭代的对象。

3.8 责任链模式

比如审批,先老师审,后院长审,这就是责任链。可以将责任链传递规则写在责任类Handler里面统一编排,也可以依赖注入的方式写在具体责任节点上。更加高级的方式是采用路由法,具体责任节点实现一个get方法,返回下一节点的对象Bean,可路由责任链更适合工作流责任上,比如agent。

3.9 备忘录模式

当我们希望有回退功能时,可以将原型类拓展为备忘录模式,和原型模式的clone一样,备份功能也是写在原型类里,拓展他的备份功能。无非是实现一个保存功能和栈储存功能,回退功能,但实际上实现是备忘录栈stack,备忘录内容memento,原型类实现备份功能是分开的,为了类的封装性保护和单一职责,memento一般作为原型类的内部类实现。

3.10 中介者模式

当类之间需要互相通信时以确定下一步动作,一般采用中介者类,将自己的信息和动作委托给中介者,由中介者来判断业务逻辑。中介者类需要维护一个被中介者信息的数组或者列表,并实现注册方法和业务方法。

3.11 访问者模式

最具争议的一个模式,当我们希望给类增加功能时,可以创建一个访问者visitor并实现增加的方法;在原型类里实现accept方法,传入visitor并调用对应方法。不禁要问,这是不是有点脱裤子放屁。
虽然大部分情况是,但并不全部是,当我们希望的原型类的是不能被改的,或者说原型类是一种固定结构。我们只希望修改操作,往往采用访问者模式。

3.12 解释器模式

当我们需要解析某种语言,可以利用解析器,实现context与具体的解析器类expression。解析器之间可以利用责任链传递顺序。