Head First 设计模式

第一章 策略模式

策略模式:定义算法族,分别封装起来,让它们之间可以相互替换,让算法的变化独立于使用算法的客户。
将应用中所有可能需要变化的地方独立出来,取出封装,使这部分以后可以更轻易地改动或扩充,更有弹性。
针对接口编程,而不是针对实现编程。这里的“接口”指超类型,通常是抽象类或者接口。
多用组合,少用继承。区分HAS-AIS-A。使用组合建立的系统将具有很大的弹性。

第二章 观察者模式

观察者模式:定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
主题(subject)+观察者(observer)。主题对象管理某些数据,当主题内的数据改变,就会通知注册了的观察者。同时需要维护观察者的注册与取消注册。
观察者模式使得两个对象之间松耦合,可以彼此交互但不清楚彼此的细节。当某个类需要注册为观察者时,只需要实现对应的观察者接口,再注册为观察者即可。

Java 内置的观察者模式
使用Observable类追踪所有的观察者并进行通知,使用Observer接口实现观察者
应当注意,Observable是一个类而不是接口。
由于内部实现顺序,观察者被通知的次序可能与手动实现的不一样。这要求我们不能依赖观察者被通知的次序。

在传输数据时,既可以让主题将数据推送(push)给观察者,也可以由观察者主动拉(pull)数据

第三章 装饰者模式

装饰者模式:动态地将责任附加到对象上,提供了比继承更有弹性的扩展功能。
类应该对扩展开放,对修改关闭。其目标是允许类容易扩展,这样在不修改现有代码的情况下,就可以搭配新的行为。
装饰者与被装饰者拥有共同的超类(或接口),使得相应功能可以一层层附加。
与之对应的,装饰模式的缺点就是在设计中可能会加入大量的小类,提高理解难度。

第四章 工厂模式

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的是哪一个,工厂方法让类把实例化推迟到子类,即封装具体类型的实例化。
简单工厂:设立独立的工厂类负责具体的对象生成,使需要创建对象的类不需要关心创建的细节,并能方便地控制生成不同的类。
让子类决定该创建的对象是什么,通常包括创建者类和产品类。创建者超类定义抽象的工厂方法,包含依赖子类的代码,让子类实现该方法以制造产品。产品类则包括一个产品超类和继承的具体子产品类。
参数化工厂方法:根据传入的参数创建不同的对象。
依赖倒置原则:要依赖抽象,不要依赖具体类。低层组件依赖高层的抽象。

变量不可以持有具体类的引用
不要让类派生自具体类
不要覆盖基类中已经实现的方法

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。抽象工厂为产品家族提供接口,允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么。

工厂方法与抽象工厂
工厂方法通过子类来创建对象,客户只需要知道所使用的抽象类型,而由子类来负责决定具体类型。
抽象工厂提供一个用来创建产品家族的抽象类型。

第五章 单件模式

单件模式:确保一个类只有一个实例,并提供一个全局访问点。
通过定义手段保证某个类只生成唯一的实例。具体方式为,将构造器设置为私有,当其它类想要取得实例时,要先进行请求,一般时设定一个getInstance()的静态方法来调用。这样外部程序就不能自行实例化得到一个实例,而必须调用静态类来获得实例。
getInstance()中设计,可以使得只有使用了该类才创建这一对象,减少不必要的程序开销。

public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (uniqueInstance == null) uniqueInstance = new Singleton;
        return uniqueInstance;
    }
    //...
}

线程安全问题
上述代码会带来线程安全问题,例如当两个线程同时调用getInstance()且未进行第一次实例化时。有下列解决办法:
1.增加synchronized关键字,迫使每个线程进入方法前都要先等待其它线程离开该方法。但这样会使得多线程性能下降。另外值得注意的是,实际上只有第一次执行该方法才需要同步,当uniqueInstance已经被实例化后,就不需要同步这个方法了。
2.在静态初始化器中创建单件,即提前实例化,这样在getInstance()时就已经实例化了。
3.使用双重检查加锁。首先检查实例是否已经创建了,如果尚未创建才进行同步。

第六章 命令模式

命令模式:将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也可以支持撤销的操作。
命令对象通过在特定的接收者上绑定一组动作来封装一个对象,而命令对象只暴露execute()方法用于调用。这样一来,当此方法被调用时,外面的其他对象不知道也不需关心具体是对哪个(些)接收者进行了操作。

空对象(null object)
对于抽象数据类型,有时会实现一个空对象来表示一个无意义的对象。通常用来初始化接口,防止出现调用时因未检查 null 导致的错误。

宏命令(party mode):创建一种新的命令对象,用来执行其他一堆命令。当宏命令被调用时,会一次性调用其中保存的其他命令对象。注意到宏命令中存放的是命令对象组而不是具体的方法组,这样设计也是为了解耦,便于扩展。

命令对象的更多用途:
队列请求:将运算打包成一个接收者和一个对象,这样在命令对象创建很久之后仍可以调用运算。这一设计甚至可以在多线程中使用。不妨设想有一个任务队列,在队列的尾部不断添加命令,而线程在头部不断取出命令执行。这样设计的好处是,线程进行工作与对象的计算之间是完全解耦的,它不关心对象具体在做什么,只知道取出命令对象然后执行execute()方法。这样可以有效地把运算限制在固定数目地线程中。
日志请求:扩展命令对象,增加两个方法store()save()使得在执行命令的同时,将历史记录存储在磁盘上。当服务出现问题重启的时候,可以从上一个检查点开始,重新执行这些操作恢复状态。