《Head First设计模式》中文版 读书笔记
Posted 山河已无恙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Head First设计模式》中文版 读书笔记相关的知识,希望对你有一定的参考价值。
写在前面:
-
嗯,参加软考,考设计模式,之前都是学一半就丢掉了,《图解设计模式》看了 1 4 \\frac{1}{4} 41后没在看,所以现在刷一下。嘻嘻,如获至宝,感觉不错,没有早一点读这本书,记得以前是打开来着,看着花花绿绿的就没细看…
-
嗯,本书的电子档资源我放到评论里了,博客主要是以书里内容为主,关于博客里的UML图,小伙伴有需要的可以联系我,生活加油 😦
-
笔记内容
:这本书的设计模式
不全,所以结合菜鸟教程
上的笔记,还有那本著名的《设计模式_可复用面向对象软件的基础》的部分,《设计模式_可复用面向对象软件的基础》
这本以后有时间刷,感觉非常不错,就是有些重,有些还是读不懂,水平有限,不适合快速学习,如果时间多,强烈建议小伙伴真的要刷一下,超级棒,需要资源可以联系我 😃
我只是怕某天死了,我的生命却一无所有。----《奇幻之旅》
一、软件设计七大原则
嗯,看书学习之前,先了解下面向对象设计原则吧
名称 | 描述 |
---|---|
单一职责原则(SRP:Single responsibility principle) | 单一职责原则:一个类应该只有一个发生变化的原因,应该只有一个职责 |
开闭原则(Open Close Principle) | 开闭原则的意思是:对扩展开放,对修改关闭。 |
里氏代换原则(Liskov Substitution Principle) | 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。 |
依赖倒转原则(Dependence Inversion Principle) | 这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。 |
接口隔离原则(Interface Segregation Principle) | 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。 |
迪米特法则,又称最少知道原则(Demeter Principle) | 最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。 |
合成复用原则(Composite Reuse Principle) | 合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。 |
里氏代换原则
是对开闭原则
的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
嗯,学设计模式,多态的概念是必须要掌握的,我们在看下多态吧:
多态分为两种通用的多态
和特定的多态
。
特定的多态
分为过载多态(overloading)
和强制多态(coercion)
.
多态类型 | 描述 | 实例 |
---|---|---|
强制多态 | 编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求。程序设计语言中基本类型的大多数操作符,在发生不同类型的数据进行混合运算时,编译程序一般都会进行强制多态。程序员也可以显示地进行强制多态的操作(Casting) 。 | 三元运算符返回类型总是最大的那个,强制类型转化符 |
过载(overloading)多态 | 同一个名(操作符、函数名)在不同的上下文中有不同的类型`。程序设计语言中基本类型的大多数操作符都是过载多态的。 | 通过不同的方法签名实现方法重载 |
通用的多态
又分为参数多态(parametric)
和包含多态(inclusion)
;
多态类型 | 描述 | 实例 |
---|---|---|
参数多态 | 采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型 | 对应中java中的泛型的概念public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable |
包含多态 | 同样的操作可用于一个类型及其子类型。(注意是子类型,不是子类。)包含多态一般需要进行运行时的类型检查。 | 定义接口引用,运行时动态绑定实现类实例 |
两者的区别是:
- 前者对工作的类型不加限制,允许对不同类型的值执行相同的代码;
- 后者只对有限数量的类型有效,而且对不同类型的值可能要执行不同的代码。
二,设计模式概述
嗯,学设计模式之前,先大概整体了解下吧,如果想在项目中使用,这些应该都要熟悉。
创建型
设计模式名称 | 简要说明 | 速记关键字 |
---|---|---|
Abstract Factory 抽象工厂模式 | 提供一个接口,可以创建一系列相关或相互依赖的对象而无需指定它们具体的类 | 生产成系列对象 |
Builder 构建器模式 | 将一个复杂类的表示与其构造相分离,使得相同的构建过程能够得出不同的表示 | 复杂对象构造 |
Factory Method 工厂方法模式 | 定义一个创建对象的接口,但由子类决定需要实例化哪一个类。工厂方法使得子类实例化的过程推迟 | 动态生产对象 |
Prototype 原型模式 | 用原型实例指定创建对象的类型,并且通过接贝这个原型来创建新的对象 | 克隆对象 |
singleton 单例模式 | 保证一个类只有一个实例,并提供一个访问它的全局访问点 | 单实例 |
结构型
设计模式名称 | 简要说明 | 速记关键字 |
---|---|---|
Adapter 适配器模式 | 将一个类的接口转换成用户希望得到的另一种接口。它使原本不相容的接口得以协同工作 | 转换接口 |
Bridge 桥接模式 | 将类的抽象部分和它的实现部分分离开来,使它们可以独立地变化 | 继承树拆分 |
Composite组合模式 | 将对象组合成树型结构以表示“整体-部分”的层次结构,使得用户对单个对象和组合对象的使用具有一致性 | 树形目录结构 |
Decorator装饰模式 | 动态地给一个对象添加一些额外的职责。它提供了用子类扩展功能的一个灵活的替代,比派生一个子类更加灵活 | 附加职责 |
Facade外观模式 | 定义一个高层接口,为子系统中的一组接口提供一个一致的外观,从而简化了该子系统的使用 | 对外统一接口 |
Flyweight 享元模式 | 提供支持大量细粒度对象共享的有效方法 | 文章共享文字对象 |
行为型
设计模式名称 | 简要说明 | 速记关键字 |
---|---|---|
Chain of Responsibility 职责链模式 | 通过给多个对象处理请求的机会,减少请求的发送者与接收者之间的耦合。将接收对象链接起来,在链中传递请求,直到有一个对象处理这个请求 | 传递职责 |
Command 命令模式 | 将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,将请求排队或记录请求日志,支持可撒销的操作 | 日志记录,可撒销 |
Interpreter 解释器模式 | 给定一种语言,定义它的文法表示,并定义一个解释器,该解释器用来根据文法表示来解释语言中的句子 | 虚拟机的机制?? |
Iterator 迭代器模式 | 提供一种方法来顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表示 | 数据库数据集 |
Mediator 中介者模式 | 用一个中介对象来封装一系列的对象交互。它使各对象不需要显式地相互调用,从而达到低耦合,还可以独立地改变对象间的交互 | 不直接引用 |
Memento 备忘录模式 | 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,从而可以在以后将该对象恢复到原先保存的状态 | 可恢复 |
Observer 观察者模式 | 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新 | 联动 |
State 状态模式 | 允许一个对象在其内部状态改变时改变它的行为 | 状态变成类 |
Strategy 策略模式 | 定义一系列算法,把它们一个个封装起来,并且使它们之间可互相替换,从而让算法可以独立于使用它的用户而变化 | 多方案切换 |
TemplateMethod 模板方法模式 | 定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义算法的某些特定步骤 | 文档模板填空 |
Visitor 访问者模式 | 表示一个作用于某对象结构中的各元素的操作,使得在不改变各元素的类的前提下定义作用于这些元素的新操作 | 数据与操作分离 |
三,设计模式笔记
下面我们就开动啦…嘻嘻,好激动,嗯,笔记不是按照书里的记得,后面根据模式类型调整啦,第一书里原本是策略模式啦…嗯
原型(Prototype)模式-对象创建型
原型模式(Prototype Pattern)
是用于创建重复的对象
,同时又能保证性能。用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口
,该接口用于创建当前对象的克隆
。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
意图
:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决
:在运行期建立和删除原型。
何时使用
: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决
:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码
: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例
: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点
: 1、性能提高。 2、逃避构造函数的约束。
缺点
: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象
,或者引用含有循环结构的时候。 2、必须实现Cloneable
接口。
使用场景
: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
注意事项
:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
创建一个实现了 Cloneable 接口的抽象类。Shape.java
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType(){
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
创建扩展了上面抽象类的实体类。Rectangle.java
public class Rectangle extends Shape {
public Rectangle(){
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
Square.java
public class Square extends Shape {
public Square(){
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
Circle.java
public class Circle extends Shape {
public Circle(){
type = "Circle";
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
创建一个类,从数据库获取实体类,并把它们存储在一个 Hashtable 中。ShapeCache.java
import java.util.Hashtable;
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
// 对每种形状都运行数据库查询,并创建该形状
// shapeMap.put(shapeKey, shape);
// 例如,我们要添加三种形状
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(),rectangle);
}
}
PrototypePatternDemo 使用 ShapeCache 类来获取存储在 Hashtable 中的形状的克隆。PrototypePatternDemo.java
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());
Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
System.out.println("Shape : " + clonedShape3.getType());
}
}
执行程序,输出结果:
Shape : Circle
Shape : Square
Shape : Rectangle
GOF描述
Prototype
有许多和Abstract Factory
和Builder
一样的效果:它对客户隐藏
了具体的产品类
,因此减少了客户知道的名字的数目
。此外,这些模式使客户无需改变即可使用与特定应用相关的类。下面列出Prototype模式的另外一些优点。
1)
运行时刻增加和删除产品
Prototype
允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型
2)改变值以指定新对象
高度动态的系统允许你通过对象复合定义新的行为-例如,通过为一个对象变量指定值-并且不定义新的类。你通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。这种设计使得用户无需编程即可定义新“类”。实际上,克隆一个原型类似于实例化一个类。Prototype模式可以极大的减少系统所需要的类的数目
。
3)改变结构以指定新对象
许多应用由部件和子部件来创建对象。例如电路设计编辑器就是由子电路来构造电路的。。为方便起见,这样的应用通常允许你实例化复杂的、用户定义的结构,比方说,一次又一次的重复使用一个特定的子电路。Prototype模式也支持这一点。我们仅需将这个子电路作为一个原型增加到可用的电路元素选择板中。只要复合电路对象将Clone实现为一个深拷贝(deep copy )
,具有不同结构的电路就可以是原型了。
4)减少子类的构造
Factory Method ( 3.3)经常产生一个与产品类层次平行的Creator类层次。Prototype模式使得你克隆一个原型而不是请求一个工厂方法去产生一个新的对象。因此你根本不需要Creator类层次。
5)用类动态配置应用
一些运行时刻环境允许你动态将类装载到应用中。
Prototype的主要缺陷
是每一个Prototype的子类都必须实现Clone操作,这可能很困难。例如,当所考虑的类已经存在时就难以新增Clone操作。当内部包括一些不支持拷贝或有循环引用的对象时,实现克隆可能也会很困难的。
单例(Singleton)模式-对象行为型:
即在整个生命周期中,对于该对象的生产始终都是一个,不曾变化。保证了一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决
:一个全局使用的类频繁地创建与销毁。
何时使用
:当您想控制实例数目,节省系统资源的时候。
如何解决
:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:
公有的方法获取实例, 私有的构造方法,私有的成员变量。
应用实例:
1 、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
2 、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
3 、要求生产唯一序列号。
4 、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
5 、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
实现
一,饿汉式
饿汉式单例关键在于singleton作为类变量并且直接得到了初始化
,即类中所有的变量都会被初始化
singleton作为类变量在初始化的过程中会被收集进<clinit>()
方法中,该方法能够百分之百的保证同步
,但是因为不是懒加载
,singleton被加载后可能很长一段时间不被使用,即实例所开辟的空间会存在很长时间.
package com.liruilong.singleton;
/**
* @Author: Liruilong
* @Date: 2019/7/20 17:55
*/
public class Singleton {
// 实例变量
private byte[] bate = new byte[1024];
// 私有的构造函数,即不允许外部 new
private Singleton(){ }
private static final Singleton singleton1 = new Singleton();
public static Singleton getInstance1(){
return singleton1;
}
二,懒汉式
懒汉式单例模式,可以保证懒加载,但是线程不安全, 当有两个线程访问时,不能保证单例的唯一性
package com.liruilong.singleton;
/**
* @Author: Liruilong
* @Date: 2019/7/20 17:55
*/
public class Singleton {
// 实例变量
private byte[] bate = new byte[1024];
// 私有的构造函数,即不允许外部 new
private Singleton(){ }
private static Singleton singleton =null;
public static Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
三,懒汉式加同步方法
懒汉式+同步方法单例模式,即能保证懒加载,又可以保证singleton实例的唯一性
,但是synchronizeed关键字的排他性导致getInstance0()方法只能在同一时间被一个线程访问。性能低下
。
package com.liruilong.singleton;
/**
* @Author: Liruilong
* @Date: 2019/7/20 17:55
*/
public class Singleton {
// 实例变量
private byte[] bate = new byte[1024];
// 私有的构造函数,即不允许外部 new
private Singleton(){ }
private static Singleton singleton =null;
public static synchronized Singleton getInstance0(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
四,双重效验锁单例
双重校验锁单例(Double-Check)+Volatile
对懒汉-同步方法的改进,当有两个线程发现singleton为null时,只有一个线程可以进入到同步代码块里。
即满足了懒加载,又保证了线程的唯一性,不加volition的缺点,有时候可能会报NPE,(JVM运行指令重排序),有可能实例对象的变量未完成实例化其他线程去获取到singleton变量。未完成初始化的实例调用其方法会抛出空指针异常。
package com.liruilong.singleton;
/**
* @Author: Liruilong
* @Date: 2019/7/20 17:55
*/
public class Singleton {
// 实例变量
private byte[] bate = new byte[1024];
// 私有的构造函数,即不允许外部 new
private Singleton(){ }
private static volatile Singleton singleton2 = null;
public static Singleton getInstance4() {
if (singleton2 == null){
synchronized (Singleton.class){
if (singleton2 ==null){
singleton2 = new Singleton();
}
}
}
return singleton2;
}
五,静态内部类单例
静态内部类的单例模式
在Singleton类初始化并不会创建Singleton实例,在静态内部类中定义了singleton实例。 当给静态内部类被主动创建时则会创建Singleton静态变量,是最好的单例模式之一,;类似于静态工厂,将类的创建延迟到静态内部类,外部类的初始化不会实例化静态内部类的静态变量。
package com.liruilong.singleton;
/**
* @Author: Liruilong
* @Date: 2019/7/20 17:55
*/
public class Singleton {
// 实例变量
private byte[] bate = new byte[1024];
// 私有的构造函数,即不允许外部 new
private Singleton(){ }
private static class Singtetons{
private static Singleton SINGLETON = new Singleton();
/* static {
final Singleton SINGLETON = new Singleton();
}*/
}
public static Singleton getInstance2(){
return Singtetons.SINGLETON;
}
六,枚举类单例
基于枚举类线程安全
枚举类型不允许被继承,同样线程安全的,且只能被实例化一次。
package com.liruilong.singleton;
/**
* @Author: Liruilong
* @Date: 2019/7/20 17:55
*/
public class Singleton {
// 实例变量
private byte[] bate = new byte[1024];
// 私有的构造函数,即不允许外部 new
private Singleton(){ }
private enum Singtetons {
SINGTETONS; //实例必须第一行,默认 public final static修饰
private Singleton singleton;
Singtetons() { //构造器。默认私有
this.singleton = new Singleton();
}
public static Singleton getInstance() {
return SINGTETONS.singleton;
}
}
public static Singleton getInstance3(){
Head First Python 读书笔记