设计模式-《Head First设计模式》

image.png

记忆

  • 慢一点,你理解的越多,需要记的就越少。
  • 勤做练习,自己做笔记。
  • 阅读 “There are no Dumb Questions” 部分
  • 上床睡觉之前不要再看别的书了,或者至少不再看其他有难度的东西。
  • 多喝水。
  • 大声说出来。
  • 听听你的大脑怎么说。
  • 要有点感觉。
  • 设计一些东西。

设计模式入门

模拟鸭子应用

利用接R如何?

软件开发的不变真理

不管当初软件设计得多好,一段时间之后,总是需要成长与改变,否则软件就会“死亡”。

分开变化和不变部分

设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在-起.

image.png

设计鸭子的行为

设计原则: 针对接口编程,而不是针对实现编程。

image.png

“针对接口编程”真正的意思是“针对超类型(supertype)编程”。

image.png
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。而我们可以新增一些行为不会影响到既有的行为类,也不会影响使用到飞行行为的鸭子类。

整合鸭子的行为

关键在于,鸭子现在会将^飞行和呱呱叫的动作“委托”(delegate) 别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和“飞行方法。

  • 首先,在Duck类中“加入两个实例变量”,分别为”flyBehavior”与”quack Behavior”,**声明为接口类型(而不是具体类实现类型)**,每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型(例如: FlyWithWings.Squcak等) 我们也必须将Duck类与其所有子类中的fy(与quack()删除,因为这些行为已经被搬到FlyBehavior与QuackBehavior类中了。我们用两个相似的方法performFly()和performQuack()取代Duck类中的fly()与quack()。

image.png

  • 实现performQuack()

image.png

测试鸭子的代码

image.png

动态地设置行为

image.png

封装行为的大局观

关系可以是IS-A (是一个)、HAS-A (有一个)或IMPLEMENTS (实现)

image.png

“有一个”比“是-个”更好

“有一个”关系相当有趣:每-鸭子都有一个Fly Behavior和一个QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。

当你将两个类结合起来使用,如同本例一般,这就是组合(composition) 。这种做法和“继承”不同的地方在于,
鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。

第三个设计原则:多用组合,少用继承。

策略模式

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

共享模式词汇的威力

  • 你使用模式和他人沟通时,其实“不只是”和他人共享“行话”而已。共享的模式词汇“威力强大”。

  • 当你使用模式名称和其他开发人员或者开发团队沟通时,你们之间交流的不只是模式名称,而是一整套模式背后所象征的质量、特性、约束。

  • 模式能够让你用更少的词汇做更充分的沟通。当你用模式描述的时候,其他开发人员便很容易地知道你对设计的想法。

  • 将说话的方式保持在模式层次,可让你待在“设计圈子”久一点。使用模式谈论软件系统,可以让你保持在设计层次,不会被压低到对象与类这种琐碎的事情上面。

  • 共享词汇可帮你的开发团队快速充电。对于设计模式有深入了解的团队,彼此之间对于设计的看法不容易产生误解。

  • 共享词汇能帮助初级开发人员迅速成长。初级开发人员向有经验的开发人员看齐。当高级开发人员使用设计模式,初级开发人员也会跟着学。把你的组织建立成一个模式使用者的社区.

我如何使用设计模式?

  • 知道OO基础,并不足以让
  • 你设计出良好的00系统。
  • 良好的0O设计必须具备可复用、可扩充、可维护三个特性。
  • 模式可以让我们建造出具有良好00设计质量的系统。
  • 模式被认为是历经验证的00设计经验。
  • 模式不是代码,而是针对设计问题的通用解决方案。你可把它们应用到特定的应用中。
  • 模式不是被发明,而是被发现。
  • 大多数的模式和原则,都着眼于软件变化的主题。
  • 大多数的模式都允许系统局部改变独立于其他部分。
  • 我们常把系统中会变化的部分抽出来封装。
  • 模式让开发人员之间有共享的语言,能够最大化沟通的价值。

观察者模式

又称发布-订阅模式

气象监测应用

image.png
初步设定
image.png

错误示范
image.png

报纸和杂志

我们看看报纸和杂志的订阅是怎么回事:

➊ 报社的业务就是出版报纸。
❷ 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户你就会-直收到新报纸。
❸ 当你不 想再看报纸的时候,取消订阅,他们就不会再送新报纸来。
❹ 只要报社还在运营, 就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。

出版社+订阅者 = 观察者模式

出版社 =(主题Subject)
订阅者 = (观察者)

定义

观察者模式定义了一系列对象之间的一对多关系,当一个对象改变状态,其他依赖者都会收到通知。

类图

松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降低到了最低。

Java API有 内置的观察者模式。java.util包 (package) 内包含最基本的Observer接口与Observable类,这和我们的Subject接口与Observer接口很相似。
Observer接口与Observable类使用上更方便,因为许多功能都已经事先准备好了。你甚至可以使用推(push) 或拉(pull) 的方式传送数据,稍后就会看到这样的例子。

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/observer

装饰者模式

装饰者模式动态地将责任附加到对象上(俄罗斯套娃)。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

背景

当我们去咖啡店买咖啡时系统的设计可能是这样的

购买咖啡时,也可以要求在其中加入各种调料,例如:蒸奶 (Steamed Milk) 、豆染(Soy)、摩卡(Mocha也就是巧克力风味)或覆盖奶泡。卖家会根据所加人的调料收取不同的费用。所以订单系统必需考点到这些调科部分。就会变成下面的的样子

好了,我们己经了解利用继承无法完会解决问题,在星巴效遇到的问题有:类数量爆炸、设计死板。以及基类加人的新功能并不适用手所有的子类所以,在这里要采用不一样的微法:

我们委以饮料为主体,然后在运行时以调“装饰”(decorate)饮料。比方说,如果顾客想要麼卡和奶泡深焙咖啡。

那么,要做的是:
1.拿一个深焙咖啡 (DarkRoast)对象
2.以摩卡(Mocha)对象装饰它
3.以奶泡 (Whip)对象装饰它
4.调用cost() 方法,井依赖委托(delegate) 将调料的价钱加上去

装饰者和被装饰别象有相同的超类裂。
你可以用一个或多个装饰者包装一个对象。
既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为。以达到特定的目的。(关键点)
对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者对象

定义

装饰者模式动态地将责任附加到对象上若要拓展功能,装饰者提供比继承更有弹性的替代方案。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/decorator

开闭原则

设计原则:类应该对扩展开放,对修改关闭。

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。

工厂模式

简单工厂:是静态工厂,由它创建类,大多都是通过传参的方式。如果这个工厂要创建新的对象,那么它就要改代码。

工厂方法:工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法把实例化推迟到子类。这样的好处是再添加新的对象的时候,就不用再改代码了,代码的可扩展性强了。要做的就是:子类工厂继承或实现抽象的工厂,写一个新的创建类的工厂,然后再在客户端调用即可,不需要修改原来的代码。

如果要添加很多子类怎么办呢?如果用工厂方法就得一个一个的添,如果创建的是一个产品族(有关系的一类产品:比如我有一个产品)。使用工厂方法的工作量就太大了,那么就需要用到抽象工厂了。

抽象工厂:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体定。抽象工厂允许客户使用抽象确有工厂创建一组相关的产品,而不需要(或关心)实际产出的具体产品是什么。这样一样,客户就从具体的产品中被解耦。

简单工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.designpattern.factory.simple;

public class Factory {
public static Object getInstance(String name){
if ("a".equals(name)){
return new Object();
}else if ("b".equals(name)){
return new Object();
}else if ("c".equals(name)){
return new Object();
}else if ("d".equals(name)){
return new Object();
}else return null;
}
}

方法工厂

背景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza;
//当我们的pizza类型很多时,下面的这种写法不够灵活
if("cheese".equals(type)){
pizza = new CheesePizza();
} else if("greek".equals(type)){
pizza = new GreekPizza();
} else if("pepperoni".equals(type)){
pizza = new PepperoniPizza();
} else if("xxx".equals(type)){
pizza = new XxxPizza();
}
//这里的代码流程基本稳定不变
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}

我们对其判断的代码提取出来,作为一个工厂类,根据type获取其实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
//通过工厂类
Pizza pizza = factory.createPizza(type);
//这里的代码流程基本稳定不变
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
//简单工厂类
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if("cheese".equals(type)){
pizza = new CheesePizza();
} else if("greek".equals(type)){
pizza = new GreekPizza();
} else if("pepperoni".equals(type)){
pizza = new PepperoniPizza();
} else if("xxx".equals(type)){
pizza = new XxxPizza();
}
return pizza;
}
}

如果每个地方有自己的披萨店,那么将其抽象为如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
//通过工厂类
Pizza pizza = factory.createPizza(type);
//这里的代码流程基本稳定不变
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//抽象工厂方法
abstract Pizza createPizza(String type);
}

现在已经有了一个PizzaStore作为超类,让每个域类型(NYPizzaStore、ChicagoPizzaStore、CaliforniaPizzaStore)都继承这个PizzaStore类,每个子类各自决定如何制作披萨。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NYPizzaStore extends PizzaStore {
public Pizza createPizza(String type){
if(item.equals("cheese")){
return new NYStyleCheesePizza();
}else if(item.equals("veggie")){
return new NYStyleVeggiePizza();
}else if(item.equals("clam")){
return new NYStyleClamPizza();
}else if(item.equals("pepperoni")){
return new NYStylePepperoniPizza();
}else return null;
}
}
public class ChicagoPizzaStore extends PizzaStore {
//...
}
public class CaliforniaPizzaStore extends PizzaStore {
//...
}

定义

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/factory

抽象工厂

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要自动(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/factory

单例模式

定义

单例模式确保一个类只有一个实例,并提供一个全局访问点

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/singleton

命令模式

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

定义

命令模式将请求封装成对象,以便使用不同的请求或者日志来参数化其他对象。命令模式也支持可撤销的操作。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/command

适配器模式

image.png

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

定义

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以协作

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/adapter

外观模式

定义

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/facade

模板方法模式

定义

模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法的情况下,重新定义算法中的某些步骤。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/template

迭代器模式

背景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DinnerMenu{
List<Item> items;
}

class PancakeMenu{
Item[] items;
}

//现在需要遍历这两个的items我们不知道他底层的是数组还是列表
//或者如果分为两种遍历方式代码就显得冗余

for(int i=0;i<items.size();i++){
Item item = items.get(i);
}

for(int i=0;i<items.length;i++){
Item item = items[i];
}

//那么创建迭代器来屏蔽两者的差异


定义

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部的表示

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/iterator

组合模式

定义

组合模 式 元许你特对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/composite

状态模式

定义

状态模式允许对象在内部状态改变时改变他们的行为,对象看起来像是修改了他们的类

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/status

代理模式

定义

代理模式为另一个对象提供一个替身或占位符以控制这个对象的访问

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/proxy

复合模式

复合模式就是多个设计模式复合在一起

桥接模式

定义

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/bridge

建造者模式

定义

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/builder

责任链模式

定义

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/responsibility

享元模式

定义

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。运用共享技术有效地支持大量细粒度的对象,享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/flyweight

解释器模式

定义

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/interpreter

中介者模式

定义

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/mediator

备忘录模式

定义

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/memento

原型模式

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

定义

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/prototype

访问者模式

定义

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。主要将数据结构与数据操作分离

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/visitor

策略模式

定义

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

类图

代码

https://github.com/mikeygithub/design-pattern/tree/master/src/main/java/com/example/designpattern/strategy

文章总结

模式是在某情境 (context)下,针对某问题的某种解决方案。

  • 情境就是应用某个模式的情况。这应该是会不断出现的情况。
  • 问题就是你想在某情境下达到的目标,但也可以是某情境下的约束。
  • 解決方案就是你所追求的:一个通用的设计,用来解决约束、达到日标。

如果你发现自己处于某个情境下,面对着所欲达到的自标被一群约束影响着的问题,然而,你能够应用某个设计克服这些约束并达到该目标,将你领向某个解决方案。

模式 描述
装饰者 包装一个对象,以提供新的行为。
状态 封装了基于状态的行为,并使用委托在行为之间切换。
迷代器 在对象的集合之中游走,而不暴露集合的突现。
外观 简化一群类的接口。
策略 封装可以互换的行为,并使用委托来決定要使用哪一个。
代理 包装对象,以控制对此对象的访问。
工厂方法 由子类決定要创建的具体类是哪一个。
适配器 封装对象,并提供不同的接口。
观察者 让对象能够在状态改变时被通知。
模板方法 由子类决定如何实现一个算法中的步骤。
组合 容户用一致的方式处理对象集合和单个对象。
单例 确保有旦只有一个对象被创建。
抽象工厂 允许客户创建对象的家族,而无需指定他们的具体类。
命令 封装请求成为对象。

相关资料

https://www.runoob.com/design-pattern/
https://github.com/mikeygithub/design-pattern
Head First设计模式(中文版).pdf


设计模式-《Head First设计模式》
https://mikeygithub.github.io/2021/09/04/yuque/设计模式-《Head First设计模式》/
作者
Mikey
发布于
2021年9月4日
许可协议