图片均来自《Head First 设计模式》和网络,侵删。

策略模式

例子:不同鸭子的行为。 image.png

观察者模式

例子:气象站更新布告板。 image.png

推拉模型

  • 推模型实现前提被观察者对象(主题对象)知道观察者需要什么数据,所以只传递数据;拉模型可能是主题对象不知道观察者需要什么样的数据,所以只能把自身传递过去,观察者根据自身需要到主题对象中拉取数据;
  • 推模型可能使观察者对象无法复用,因为推模型传递给观察者的是已经定义好的数据,一旦新的观察者要求不同格式数据,就需要重新定义  update()  方法;而拉模型就不会造成这样的情况,因为拉模型下,update()  方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
  • JDK  中的观察者模式采用的推模型,但是在  update(Observable o, Object arg)  方法中定义了两个参数,不仅传递了数据内容,也将被观察者对象(主题对象)本身传递给了观察者。

发布-订阅模式

ChangeManager:中介者模式的实例。如果一个操作涉及到对几个相互依赖的目标进行改动,就必须保证仅在所有的目标都已更改完毕后,才一次性地通知它们的观察者,而不是每个目标都通知观察者。总结其作用有三:

  1. 它将一个目标映射到它的观察者并提供一个接口来维护这个映射。这就不需要由目标来维护对其观察者的引用,反之亦然。
  2. 它定义一个特定的更新策略。
  3. 根据一个目标的请求,它更新所有依赖于这个目标的观察者。 image.png

装饰者模式

例子:咖啡添加配料,实际不太好,可以直接使用组合模式,更加灵活透明,装饰者模式更主要的还是动态修改或增加功能,因此最好的案例还是 JDK 中 I/O 相关的实现。 开闭原则。 image.png 更加灵活的封装。 image.png

工厂模式

例子:披萨制作,简单工厂不是设计模式,只是单独将制作披萨的条件判断函数独立。工厂方法模式是通过继承,主要是创建单个对象,由子类来具体确定对象创建类型。抽象工厂是创建对象簇,需要先实例化对应的工厂,更多使用的是对象的组合。

工厂方法

image.png

抽象工厂

image.png

单例模式

例子:巧克力工厂,只能有一个锅炉,还需要解决多线程加入原料的问题。 image.png

双重校验锁实现单例:

public class SingletonDoubleCheck {​
    // 类引用
    private static volatile SingletonDoubleCheck singletonDoubleCheck;​
    // 构造函数私有化
    private SingletonDoubleCheck() {​}​
    // 双重校验 + 锁实现单例
    public static SingletonDoubleCheck getInstance() {
        // 第一次校验是否为null
        if (singletonDoubleCheck == null) {
            // 不为空则加锁
            synchronized (SingletonDoubleCheck.class) {
                // 第二次校验是否为null
                if (singletonDoubleCheck == null) {
                    singletonDoubleCheck = new SingletonDoubleCheck();
                }
            }
        }
        return singletonDoubleCheck;
    }
}

每个类加载器都有自己的命名空间。这意味着如果有两个类加载器,它们可能会加载同一个类,但在虚拟机中被视为两个不同的类,因为它们处于不同的命名空间。为了避免单例模式的实例化问题,建议在使用单例模式的同时,明确指定类加载器,并确保在整个程序中只使用这一个类加载器。 单例模式相比全局变量:延迟初始化,减少大量小对象对命名空间的污染。

命令模式

例子:女招待(invoker)接收命令(setCommand),厨师(receiver)完成命令(execute),以及遥控器 undo 命令设置。 image.png 空插槽没有预先设置对应的命令:预先将所有插槽均设置一个空命令(空对象),空命令被调用 execute() 将无事发生。 宏命令:支持一组命令,undo 时候需要逆向逐个撤销。 队列和日志管理。

适配器模式

例子:充电器,鸭子和火鸡的行为,新的迭代器适配枚举类(迭代器有 remove 方法而枚举类没有,迭代器接口的 remove 被设计为抛出 UnsupportOperationException 异常)。

对象适配器

不需要引入继承,但是需要全部实现被适配器的方法,代码重复度可能较高。 image.png

类适配器

可以根据被适配器的需要选择性地重写适配器的方法,但是使用了继承,相对不灵活。 image.png

image.png

双向适配器

image.png

外观模式

例子:家庭影院。 外观没有“封装”子系统的类,外观只提供简化的接口。所以客户如果觉得有必要,依然可以直接使用子系统的类。这是外观模式一个很好的特征:提供简化的接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。 最小知识原则。 image.png

模板方法模式

例子:冲泡咖啡和冲泡茶。 钩子方法:通常是空的缺省实现,子类自行决定是否要覆盖它。也就是说,算法的必须步骤是抽象方法,而可选的则是钩子。钩子的另一个用法,是让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤作出反应。比方说,名为 justReOrderedList()的钩子方法允许子类在内部列表重新组织后执行某些动作(例如在屏幕上重新显示数据)正如你刚刚看到的,钩子也可以让子类有能力为其抽象类作一些决定。 排序方法中 compareTo 由子类实现,除此之外,java.io 的 InputStream 类有一个 read() 方法,是由子类实现的,而这个方法又会被 read(byte b[],int off,int len) 模板方法使用,Android 的生命周期函数也都是典型例子。 模板方法和策略模式都封装算法,前者用继承,后者组合。 防止子类更改算法可以在模板类定义该方法为 final。 工厂方法是模板方法的一种特殊情况。因为在工厂方法模式中,工厂接口的创建方法可以被认为是一个模板方法。这个模板方法定义了创建对象的框架,而具体的创建过程则由具体的工厂子类实现。 好莱坞原则:高层告诉低层,别管我们,我们会调用你。 image.png

迭代器模式

例子:两家菜单不同的餐厅合并,服务员需要统一接口。 单一职责模式。 image.png

image.png

组合模式

例子:菜单不同的餐厅合并后要在某个菜单下添加子菜单。 有时候,如果这个组合结构很复杂,或者遍历的代价太高,那么实现组合节点的缓存就很有帮助。比方说,如果你要不断地遍历一个组合,而且它的每一个子节点都需要进行某些计算,那你就应该使用缓存来临时保存结果,省去遍历的开支。 image.png

状态模式

例子:糖果机的不同状态,有硬币、无硬币、糖果售罄等。 一般来讲,当状态转换是固定的时候,就适合放在 Context 中:然而,当转换是更动态的时候,通常就会放在状态类中(例如,在 GumballMachine 中,由运行时糖果的数目来决定状态要转换到 NoQuarter 还是 SoldOut)。将状态转换放在状态类中的缺点是:状态类之间产生了依赖。在我们的 GumballMachine 实现中,我们试图通过使用 Context 上的 getter 方法把依赖减到最小,而不是显式硬编码具体状态类。 请注意,在做这个决策的同时,也等于是在为另一件事情做决策:当系统进化时,究竟哪个类是对修改封闭(Context 还是状态类)的。 如果你有一个应用,它有很多状态,但是你决定不将这些状态封装在不同的对象中,那么你就会得到巨大的、整块的条件语句。这会让你的代码不容易维护和理解。通过使用许多对象,你可以让状态变得很干净,在以后理解和维护它们时,就可以省下很多的工夫。 状态类可以被多个 Context 实例共享。 image.png

代理模式

例子:糖果机监测状态,网站图像加载代理(未下载好时显示加载中)。 增加“下载中”消息可以算是增加新功能,但是更重要的是将客户端和图像服务解耦,代理对象控制对图像服务的访问,因此更符合代理模式而不是装饰者模式。 一个常用的技巧是提供一个工厂,实例化并返回主题。因为这是在工厂方法内发生的,我们可以用代理包装主题再返回,而客户不知道也不在乎他使用的是代理还是真东西。 防火墙代理、智能引用代理、缓存代理、同步代理、复杂隐藏代理、写入时复制代理。 image.png

动态代理类图。 image.png

MVC 使用的设计模式。 image.png