《HeadFirst设计模式》笔记(上)
设计模式的目录:
1 设计模式介绍
要不断去学习如何利用其它开发人员的智慧与经验。学习前人的正统思想。
我们认为《Head First》的读者是一位学习者。
一些Head First的学习原则:
- 使其可视化
- 将文字放在相关图形内部或附近,而不是放在底部或另一页上,这样学习者解决问题的能力可提高至两倍。
- 采用对话式和个人化的风格
- 让学习者进行更深入的思考。引起读者的好起,促进、要求并鼓励读者去解决问题、得出结论、产生新的知识。
- 吸引并保持读者的注意力
- 触动他们的情感。如果是读者关心的东西,那么读者就能够记得住。
可以用下面的方法让大脑更好地吸收知识:
- 1、慢一点,理解得越多,需要记的就越少
- 2、勤做练习,自己记笔记
- 3、阅读“There are no Dumb Questions”部分。书中这些部分绝对是核心内容。
- 4、上床睡觉前不要再看别的书,或者至少不再看其他有难度的东西
- 5、多喝水
- 6、大声说出来
- 7、听听大脑怎么说。注意一下你的大脑是不是负荷太重了,如果发现自己开始浮光掠影地翻看,或者刚看的东西就忘了,这说明该休息一会儿了。
- 8、要有点感觉。要真正融入到书中的故事里,为书里照片加上自己的说明。
- 9、设计一些东西。将学来的知识应用到新项目中,甚至重构旧项目。
我们的图表风格类似于UML——尽管我们尽力遵守UML规范,但有时会为了艺术效果而略微打破规则,这往往是出于我们自身的美学考量。
书里的实践活动不是可有可无的。
我们使用“组合”这个词是在广义的面向对象(OO)意义上,这比严格意义上的UML“组合”更加灵活。当我们说‘一个对象是由另一个对象组合而成’时,我们的意思是它们之间存在一种‘包含’(HAS-A)关系。
在面向对象(OO)系统中,类通常表示既有状态(即实例变量)又有行为(即方法)的事物。在这个特定的情况下,事物实际上就是一种行为。然而,即使是行为也可以拥有自己的状态和方法;例如,飞行行为可能包含表示飞行特性的实例变量,如每分钟翼拍次数、最大飞行高度和速度等。
第一个设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
将一些可能需要变化之处委托给别人处理,而不是使用定义在Duck类内的呱呱叫和飞行方法。
委托具体通过动态绑定来实现。
第二个设计原则:针对接口编程,而不是针对实现编程。
第三个设计原则:多用组合,少用继承。
软件开发后相比软件开发前需要更多的时间,因为需要花许多时间在系统的维护和变化上。
定义策略模式
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
策略模式就是前面说的这么多的结合。
设计模式为您提供了一个与其他开发人员共享的词汇表。一旦你有了词汇表,你就可以更容易地与其他开发人员交流,并激励那些不知道模式的人开始学习它们。
设计模式不会直接进入代码中,而是先进入大脑中。一旦先在脑海中装入了许多关于模式的知识,就能够开始在新设计中采用它们,并当旧代码变得十分混乱时,可用它们重做旧代码。
良好的OO设计必须具备可复用、可扩充、可维护三个特性。
2 观察者模式
观察者模式可以让你的对象知悉现况。
观察者模式是JDK中使用最多的模式之一。
定义观察者模式:类图
一对多的关系。一则是提供数据的,多则是观察者。
需要注册观察者和移除观察者可以理解,
NotifyObserver则是用于在状态改变时更新所有当前观察者。
第四个设计原则:为了交互对象之间的松耦合设计而努力。
setChanged()方法用来标记状态已经改变的事实,好让notify0bservers0)知道当它被调用时应该更新观察者。如果调用notify0bserversO之前没有先调用setChangedO,观察者就“不会”被通知。Observable 内部:
通过使用setChanged()方法则可以控制WeatherData对象通知观察者的频率。
import java.util.Observable;
import java.util.Observer;public class ForecastDisplay implements Observer, DisplayElement{private float currentPressure = 29.92f;private float lastPressure;public ForecastDisplay(Observable observable) {WeatherData weatherData = (WeatherData) observable;observable.addObserver(this);}public void update(Observable observable, Object arg) {if (observable instanceof WeatherData) {lastPressure = currentPressure;currentPressure = WeatherData.getPressure();display();}}public void display() {// 在这里显示代码}
}
观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
观察者模式将固定的addObserver、deleteObserver、notifyObservers、setChange放到类里面,而update抽离出来为接口。
针对接口编程则是体现在:主题与观察者都使用接口。观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运行正常,又同时具有松耦合的优点。
多用组合体现在:(GeneralDisplay有一个Observable。)观察者模式利用“组合”将许多观察者组合进主题中。对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的。
3 装饰者模式
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
代码应该如同晚霞中的莲花一样地关闭(免于改变),如同晨曦中的莲花一样地开放(能够扩展)。
第五个设计原则:类应该对扩展开放,对修改关闭。
对扩展开放,对修改关闭的理解:有一些OO技巧,运行系统在不修改代码的情况下进行功能扩展。例如,观察者模式可以在任何时候扩展Subject(主题),而且不需向主题中添加代码。
认识装饰者模式:我们要以饮料为主体,然后在运行时以调料来“装饰”饮料。比方说,如果顾客要摩卡和奶泡深培咖啡,要做的是:
1、拿一个深培咖啡(DarkRoast)对象
2、以摩卡(Mocha)对象装饰它
3、以奶泡(Whip)对象装饰它
4、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去
一些属性:
- 装饰者和被装饰者对象有相同的超类型。
- 可以用一个或多个装饰者包装一个对象。
- 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装)的场合,都可以用装饰过的对象代替它。
- 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地使用你喜欢的装饰者来装饰对象。
画出来的图是这样的(简单画了两个摩卡):
# Soy和Whip的代码:
public class Soy extends CondimentDecorator {Beverage beverage;public Soy(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Soy";}public double cost() {return 0.15 + beverage.cost();}
}public class Whip extends CondimentDecorator {Beverage beverage;public Whip(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Whip";}public double cost() {return 0.15 + beverage.cost();}
}
供应咖啡:
装饰者的缺点:采用装饰者实例化组件时,将增加代码的复杂度。一旦使用了装饰器,不仅需要实例化组件,还需要用不确定数量的装饰器将其包裹。
4 工厂模式
烘烤OO的精华:这句话在比喻性地谈论将面向对象编程的原则和优点应用到某个过程或项目中,就像在烘焙中加入优质的原料一样,来提升最终产品的质量。
把这部分抽离出来,当需要比萨时,就叫比萨工厂做一个。(实际上这就叫做简单工厂模式)
如何利用比萨工厂方法订购比萨:
平行的类层级:
定义工厂方法模式
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法把类实例化推迟到子类。
第六个设计原则:要依赖于抽象,不要依赖于具体类。
(这里的“依赖于”其实就是有一个。)
这个原则提倡软件模块之间的依赖关系应该建立在抽象之上,而不是具体的实现类。这样做可以提高代码的灵活性、可维护性和可扩展性,使得系统更易于管理复杂性,并支持变更而不影响依赖它的其他模块。
应用工厂方法之后,类图看起来就像这样:
避免在OO设计中违反依赖倒置原则的指导方针:
- 变量不可以持有具体类的引用
- 不要让类派生自具体类
- 不要覆盖基类中已实现的方法
定义抽象工厂模式
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
5 单例模式
单例模式:用来创建独一无二对象的门票,这类对象仅有一个实例。
有一些对象只需要一个,比如说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象,充当打印机、显卡等设备的驱动程序的对象。
关键:如何保证一个对象只能被实例化一次。
C++实现单例设计模式:(将构造函数定义为私有成员,然后在静态成员函数进行实例化和返回)
#include <iostream>
#include <mutex>class Singleton {
private:// 私有构造函数,防止外部创建实例Singleton() {// 可以初始化一些资源}// 删除拷贝构造函数和赋值操作符,防止复制Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 静态成员函数,作为全局访问点static Singleton* getInstance() {// 使用局部静态变量来保证线程安全(C++11及以上)static Singleton instance;return &instance;}// 示例方法void showMessage() const {std::cout << "Hello, I'm a Singleton!" << std::endl;}
};// 使用示例
int main() {// 获取单例对象Singleton* singleton = Singleton::getInstance();// 调用单例对象的方法singleton->showMessage();return 0;
}
帮Choc-O-Holic改进CholcolateBoiler类:
public class CholcolateBoiler {private boolean empty;private boolean boiled;public static CholcolateBoiler uniqueInstance;private CholcolateBoiler() {empty = true;boiled = false;}public static CholcolateBoiler getInstance() {if(uniqueInstance == null) {uniqueInstance = new CholcolateBoiler;}return uniqueInstance;}public void fill() {if(isEmpty()) {empty = false;boiled = false;}}
}
定义单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。
能改善多线程吗?
1、如果getInstance()的性能对应用程序不是很关键,就什么都别做
如果可以接受getInstance()造成的额外负担,就不要管了。
2、改为预先创建实例,而不是延迟创建实例。
3、用“双重检查加锁”,在getInstance()中减少使用同步
利用双重检查加锁,首先检查实例是否已经创建了,如果尚未创建,才进行同步。
是否可以创建一个类,把所有的方法和变量都定义为静态的,把类直接当做一个单件?
如果类自给自足,而且不依赖于复杂的初始化,那么可以。但是,因为静态初始化的控制权是在Java手中,这么做有可能导致混乱,特别是当有许多类牵涉其中时。这么做常常会造成一些微妙的、不容易发现的和初始化的次序有关的bug。除非有绝对的必要使用类的单例,否则还是建议使用对象的单例。
6 封装调用
把封装带到一个全新的境界:把方法调用封装起来。
利用命令对象,把请求封装成一个特定对象。如果对每个按钮都存储一个命令对象,那么当按钮被按下的时候,就可以请命令对象做相关的工作。
答案是这个:
GarageDoorOpenCommand类:
public class GarageDoorOpenCommand implements Command {GarageDoor garageDoor;public class GarageDoorOpenCommand(GarageDoor garageDoor) {this.garageDoor = garageDoor;}public void execute() {garageDoor.up();}
}
定义命令模式
命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
写文档:
使用宏命令:
需要关闭按钮的命令,代码如下:
LightOffCommand lightOff = new LightOffCommand(light);
StereoOffCommand stereoOff = new StereoOffCommand(light);
TVOffCommand tvOff = new TVOffCommand(tv);
HottubOffCommand hottubOff = new HottubOffCommand(hottub);
public void undo() {for (int i = 0; i < commands.length; i++) {commands[i].undo();}
}
命令模式的更多用途:队列请求
命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。利用这样的特性衍生一些应用,例如:日程安排、线程池、工作队列等。
你认为Web服务器如何应用这样的队列方式?还能想到任何其他的应用吗?
命令模式用于日志请求:通过新增两个方法(store()、load())来实现。
7 适配器模式与外观模式
客户使用适配器的过程如下:
1、客户通过目标接口调用适配器的方法对适配器发出请求。
2、适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口。
3、客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用。
public class DuckAdapter implements Turkey {Duck duck;public DuckAdapter(Duck duck) {this.duck = duck;}public void gobble() {duck.quack();}public void fly() {duck.fly();}
}
适配器模式的工作是将一个接口转换成另一个。外观模式:让一个适配器包装多个被适配者。
定义适配器模式
适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
类适配器通过多重继承来实现。
对象适配器利用组合的方式将请求传送给被适配器。
外观模式:例如,想要看电影,只要调用一个方法(就是watchMovie())就可以了。灯光、DVD播放器、投影机、功放、屏幕、爆米花,一口气全部搞定。
定义外观模式
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
第七个设计原则:最少知识原则:只和你的密友谈话。
如何不要赢得太多的朋友和影响太多的对象。
就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
- 该对象本身
- 被当做方法的参数而传递进来的对象
- 此方法所创建或实例化的任何对象
- 对象的任何组件
8 封装算法
模板方法模式:在一个方法中定义一个算法的股价,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
第八个设计原则:
好莱坞原则:别调用我们,我们会调用你。
我们允许底层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是”别调用我们,我们会调用你“。
模板方法很常见的原因是因为对创建框架来说,这个模式简直棒极了。由框架控制如何做事情,而由使用这个框架的人指定框架算法中每个步骤的细节。
用模板方法排序
如果要排序鸭子,就需要实现这个compareTo()方法:
模板方法是最常用的方法。因为模板方法提供了一种基本的方法来实现代码复用,使得子类能够指定行为。
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
9 迭代器与组合模式
错了,应该是:
一个可能的迭代器接口:
迭代器:
public class PancakeHouseIterator implements Iterator {ArrayList items;int position = 0;public PancakeHouseIterator(ArrayList items) {this.items = items;}public Object next() {MenuItem menuItem = items.get(position);position = position + 1;return menuItem;}public boolean hasNext() {if(position >= items.size() || items.get(position) == null) {return false;} else {return true;}}
}
菜单:
public class PancakeHouseMenu {ArrayList menuItems;// 构造器在这里//addItem在这里public Iterator createIterator() {rerurn new PancakeHouseIterator(menuItems);}//菜单的其他方法在这里
}
然后就可以快乐地使用迭代器进行打印了:
进一步的,将煎饼和午餐进行合并:
定义迭代器模式
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
当我们运行一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(例如遍历)时,我们就给了这个类两个变化的原因。这违反了下面的设计原则。
第九个设计原则:一个类应该只有一个引起变化的原因。
如果菜单出现了树形结构,则需要使用组合模式:
组合模式允许你将对象组合成树形结构来表现”整体/部分“层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
组合模式以单一责任设计原则换取透明性。透明性:通过允许组件接口包含子管理操作和叶节点操作,客户端可以统一地处理复合对象和叶节点;因此,一个元素是复合对象还是叶节点对于客户端来说变得透明。在该模式下,可以认为叶节点是没有孩子的节点。(事实上,代码实现上也是认为没有孩子的节点是叶节点)
组合迭代器
CompositeIterator是一个不可小觑的迭代器。它的工作是遍历组件内的菜单项,而且确保所有的子菜单都被包括进来。
下面是代码,看不懂。。。特别是hasNext函数中判断iterator.hasNext。
import java.util.*;public class CompositeIterator implements Iterator {Stack stack = new Stack(); // 使用栈来管理迭代器public CompositeIterator(Iterator iterator) {stack.push(iterator); // 将顶层组合的迭代器压入栈中}public Object next() {if (hasNext()) { // 确保还有下一个元素Iterator iterator = (Iterator) stack.peek(); // 获取当前栈顶的迭代器MenuComponent component = (MenuComponent) iterator.next(); // 获取下一个组件if (component instanceof Menu) { // 如果是菜单,继续递归stack.push(component.createIterator()); // 将子菜单的迭代器压入栈中}return component; // 返回当前组件} else {return null; // 没有下一个元素时返回null}}public boolean hasNext() {if (stack.empty()) { // 如果栈为空,表示没有下一个元素return false;} else {Iterator iterator = (Iterator) stack.peek(); // 获取当前栈顶的迭代器if (!iterator.hasNext()) { // 如果当前迭代器没有下一个元素stack.pop(); // 弹出当前迭代器return hasNext(); // 递归检查下一个迭代器} else {return true; // 当前迭代器还有下一个元素}}}public void remove() {throw new UnsupportedOperationException(); // 不支持删除操作}
}
有时候,如果一个组合结构很复杂,或者遍历的代价太高,那么实现组合节点的缓存就很有帮助。
迭代器:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
组合模式:允许你将对象组成树形结构来表现“整体/部分”的层次结构。组合能让客户以一致的方式处理和别对象和对象组合。
10 状态模式
“策略模式和状态模式是双胞胎,在出生时才分开。”
策略模式是围绕可以互换的算法来创建成功业务的。而状态模式走的是更崇高的路,它通过改变对象内部的状态来帮助对象控制自己的行为。
public class SoldOutState implements State {GumballMachine gumballMachine;public SoldOutState(GumballMachine gumballMachine) {this.gumballMachine = gumballMachine;}public void insertQuarter() {System.out.println("You can't insert a quarter, the machine is sold out");}public void ejectQuarter() {System.out.println("You can't eject, you haven't inserted a quarter yet");}public void turnCrank() {System.out.println("You turned, but there are no gumballs");}public void dispense() {System.out.println("No gumball dispensed");}
}
定义状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
状态模式的类图: