设计模式总结

设计模式总结

代码质量

活字印刷

可扩展

要改,只需更改要改之字

可复用

这些字并非用完这次就无用,完全可以在后来的印刷中重复使用

可维护

此诗若要加字,只需另刻字加入即可

灵活性好

字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求

面对客户的需求变化感到痛苦,原因就是因为我们原先所写的程序,不容易维护,灵活性差,不容易扩展,更谈不上复用,因此面对需求变化,加班加点。

面向对象

封装、继承、多态

UML

关联 vs 依赖

关联association:

企鹅需要“知道”气候的变化,需要“了解”气候规律,当一个类“知道”另一个类时,可以用关联

依赖dependency:

动物依赖于氧气和水,他们之间是依赖关系

http://www.yuque.com/docs/share/adc69e1f-554f-4539-a21c-291aae6707c7?inner=kYHCG

组合 vs聚合

组合composition:

是一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。

聚合aggregation:

聚合表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。

继承 vs 接口实现

继承:空心三角形 + 实线

实现接口:空心三角 + 虚线

设计原则

设计模式并不重要,重要的是设计原则

  • 单一职责
  • 里氏替换
  • 依赖倒置
  • 接口隔离
  • 迪米特法则
  • 开闭原则

策略模式

  1. 定义

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

设计原则

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

    • 鸭子的行为在子类里不断改变,并且让所有的子类都有这些行为是不恰当的。
    • 把鸭子的行为从Duck类中取出
  • 针对接口编程,而不是针对实现编程

    • 在运行时动态地改变鸭子的飞行行为
  • 多用组合,少用继承

    • 鸭子的行为通过和适当的行为对象组合而来
    • 使用组合具有很大的弹性,不仅可将算法封装成类,更可以在运行时动态地改变行为。

设计模式和类库的区别:

  1. 库与框架无法帮助我们将应用组织成容易了解、容易维护、具有弹性的架构,所以需要设计模式
  2. 设计模式比库的等级更高,设计模式告诉我们如何组织类和对象以解决某种问题,是针对设计问题的通用解决方案
  3. 库和框架提供了特定的实现,但这并不算是设计模式。有时候,库和框架本身会用到设计模式

观察者模式

1、定义

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。

2、类图

设计原则

为了交互对象之间的松耦合设计而努力。

  1. 观察者对设计原则的应用
  • 找出程序中会变化的方面,然后将其固定不变的方面相分离

    • 在观察者模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。
  • 针对接口编程,不针对实现编程

    • 主题与观察者都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。
  • 多用组合,少用继承

    • 观察者模式利用“组合”将许多观察者组合进主题中,对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生。

装饰者模式

  1. 定义

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

  1. 装饰者和被装饰对象有相同的超类型。
  2. 你可以用一个或多个装饰者包装一个对象。
  3. 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
  4. 利用继承达到类型匹配。继承,是为了有正确的类型,而不是继承它的行为。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系。
  5. 装饰者会导致程序设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

单例模式

  1. 利用单件模式,我们可以在需要时才创建对象。延迟实例化的方式,这种做法对资源敏感的对象特别重要。

  2. 定义

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

  3. 抽象单例

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
33
34
35
36
37
38
39
40
/**
https://blog.csdn.net/zhmt/article/details/50804215
* 线程安全的抽象单例
*
* @author zhmt
* @createdate 2016年3月4日 下午4:39:50
* @param <T>
*/
public abstract class AbstractSingleton<T> {
private final AtomicReference<T> ref = new AtomicReference<T>();

public T get() {
T ret = ref.get();
if (ret == null) {
synchronized (this) {
if (ref.get() == null) {
ret = newObj();
ref.set(ret);
} else {
ret = ref.get();
}
}
}
return ret;
}

protected abstract T newObj();
}

public class A {
public static final AbstractSingleton<A> objHolder = new AbstractSingleton<A>() {
protected A newObj() {
return new A();
}
};

private A() {
A.objHolder.get();
}
}

工厂模式

简单工厂模式

工厂方法模式

1、定义

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

2、简单工厂和工厂方法之间的区别

工厂方法的子类看起来很像简单工厂。简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。简单工厂的做法,可以将对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。

3、依赖倒置原则

要依赖抽象,不要依赖具体类。

低层组件依赖高层的抽象,高层组件也依赖相同的抽象。

抽象工厂模式

1、定义

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

2、抽象工厂和工厂方法的联系

抽象工厂的每个方法实际上看起来都像是工厂方法。每个方法都被声明成抽象,而子类的方法覆盖这些方法来创建某些对象。

抽象工厂的任务是定义一个负责创建一组产品的接口。这个接口内的每个方法都负责创建一个具体产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法,

抽象工厂和工厂方法都是负责创建对象。抽象工厂使用的是对象,通过对象的组合;工厂方法使用的类,通过继承。

需要创建产品家族和想让制造的相关产品集合起来时,可以使用抽象工厂。工厂方法可以把客户代码从需要实例化的具体类中解耦,或者如果目前还不知道将来需要实例化哪些具体类时,也可以用工厂方法。

命令模式

1、命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者的一个或一组动作。

2、定义

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

3、实现撤销

  • 在Invoker中存一个uncommand
  • 在Command中存储prevState

4、实现多层次撤销

  • 不要只是记录最后一个被执行的命令,使用一个堆栈记录过程中的每一个命令

5、MacroCommand:一次执行一组命令

适配器模式

1、适配器实现了目标接口,并持有被适配者的实例

2、定义

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

3、客户和接口绑定,不是和实现绑定。我们可以使用多个适配器。

4、对象适配器和类适配器

  • 对象适配器:适配器使用组合来适配被适配者

    • 优点:不仅可以适配某个类,也可以适配该类的任何子类
  • 类适配器:适配器继承被适配者和目标类

    • 优点:采用某个特定的被适配类,不需要重新实现我的整个被适配者,必要的时候可以覆盖被适配者的行为;

5、装饰者模式和适配器模式的对比

  • 装饰者模式不改变接口,扩展包装的对象的行为或责任,
  • 适配器模式转换接口,允许客户使用新的库和子集合

6、外观模式和适配器模式的对比

  • 外观模式的意图:简化接口
  • 适配器模式的意图:将接口转换成不同接口

7、双向适配器

Iterator: hasNext() next() remove()

Enumeration: hasMoreElements() nextElement()

  • 将Enumeration适配成Iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class EnumerationIterator implements Iterator {
Enumeration enumeration;

public EnumerationIterator(Enumeration enumeration) {
this.enumeration = enumeration;
}

public boolean hasNext() {
return enumeration.hasNextElement();
}

public Object nextElement() {
return enumeration.nextElement();
}

public void remove() {
throws new UnsupportedOperationException();
}
}
  • 将Iterator适配成Enumeration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class IteratorEnumeration implements Enumeration {
Iterator iterator;

public IteratorEnumeration(Iterator iterator) {
this.iterator = iterator;
}

public boolean hasMoreElements(){
return iterator.hasNext();
}

public Object nextElement() {
return iteraror.next();
}
}

外观模式

定义:

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

设计原则

最少知识原则:只和你的密友谈话

模板方法模式

1、模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。 需要由子类提供的方法,必须在超类中声明为抽象

2、定义

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

3、模板就是一个方法,更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。

4、我们也可以有“默认不做事的方法”,我们称这种方法为‘hook’。子类可以视情况决定要不要覆盖它们。钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。

5、当你的子类必须提供算法中的某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。

6、钩子的几种用法:

  • 钩子可以让子类实现算法中可选的部分或者在钩子对子类的实现并不重要的时候,子类可以对钩子置之不理。
  • 让子类能够有机会对模板方法中某些即将发生(或刚刚发生)的步骤作出反应
  • 钩子也可以让子类有能力为其抽象类作一些决定

7、模板方法应用:排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void sort(Object[] a) {
Object aux[] = (Object[]) a.clone();
mergeSort(aux, a, 0, a.length, 0);
}

private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off){
for(int i = low; i < high, i++) {
for (int j = i; j > low &&
((Comparable) dest[j-1]).compareTo((Comparable) dest[j] > 0); j--) {
swap(dest, j, j-1);
}
}
return;
}

排序鸭子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Duck implements Comparable {
String name;
int weight;

public Duck(String name, int weight) {
this.name = name;
this.weight = weight;
}

public String toString() {
return name + ' weights ' + weight;
}

public int compareTo(Object object) {
Duck otherDuck = (Duck) object;
if(this.weight < otherDuck.weight) {
return -1;
} else if(this.weight == otherDuck.weight) {
return 0;
} else {
return 1;
}
}
}

设计原则

别调用我们,我们会调用你。

允许底层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。

  1. 模板方法和策略的比较

模板方法模式必须依赖超类中的方法的实现,因为这是算法的一部分。而策略模式不依赖任何人。

模板方法中会被重复使用的代码都被放进了超类中,好让所有子类共享。策略模式使用组合,运行时改变算法。

迭代器模式

  1. 定义

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

  1. 迭代器模式把在元素之间游走的责任交给迭代器,而不是聚合对象。这不仅让聚合的接口和实现变得简洁,也可以让聚合更专注在它所应该专注的事情上面。

设计原则

一个类应该只有一个引起变化的原因。

当我们允许一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(例如遍历)时,我们就给了这个类两个变化的原因。

组合模式

  1. 定义

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

  1. composite包含component。component有两种元素:leaf和composite。
  2. 组合模式以单一责任设计原则换取透明性。透明性是指通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。
  3. 当你有数个对象的集合,它们彼此之间有“整体/部分”关系,并且你想用一直的方式对待这些对象时,你就需要我。
  4. 为了保持透明性,组合内的所有对象都必须实现相同的接口,否则客户就必须操心哪个对象是用哪个接口。
  5. 客户不再需要操心面对的是组合对象还是叶节点对象了,所以就不需要写一大堆的if语句来保证它们对正确的对象调用了正确的接口。
  6. 组合模式和迭代器模式

状态模式

  1. 策略模式和状态模式是双胞胎。策略模式是围绕可以互换的算法来创建成功业务的。状态则是通过改变对象的内部状态来帮助对象控制自己的行为,是条件判断的替代方案。
  2. 定义

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

这个模式将状态封装成独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。

代理模式

  1. 代理,就是代表某个真实的对象。代理就像是糖果机对象,但其实幕后是它利用网络和一个远程的真正糖果机沟通。
  2. 远程代理就好比“远程对象的本地代表”
  3. 你的客户对象所做的就像是在做远程方法调用,但其实只是调用本地堆中的“代理”对象上的方法,再由代理处理所有网络通信的底层细节
  4. 客户对象–>客户辅助对象–>服务辅助对象–>服务对象
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
33
34
35
36
37
public interface MyRemote extends Remote {
public String sayHello() throws RemoteException;
}

public MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
public String sayHello() {
return "Server says, Hey"
}

public MyRemoteImpl throws RemoteException {}

public static void main(String[] args) {
try {
MyRemote service = new MyRemoteImpl();
Naming.rebind("RemoteHello", service);
} catch(Exception ex) {
ex.printStackTrace();
}
}
}

//客户端
public class MyRemoteClient {
public static void main(String[] args) {
new MyRemoteClient().go();
}

public void go() {
try {
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");

String s = service.sayHello();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}

定义

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

分类

  • 远程代理:控制访问远程对象

调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。

  • 虚拟代理:控制访问创建开销大的资源

虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。

  • 保护代理:基于权限控制对资源的访问

建造者模式

定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

主要作用:

隔离了客户与生产过程、负责控制产品的生产过程

  1. 建造者模式和抽象工厂模式的区别

原型模式

桥接模式

享元模式

复习课

  1. 简答(对pattern的理解为主)和综合应用 每个pattern都会考到 应用题:单个或多个模式联合应用 所有考试内容都是PPT讲过的
  2. 英文试卷,答题可以中文
  3. 重点的先讲了
  4. 第一阵列:工厂方法模式 Java反射机制 (桥接模式引入抽象后难以理解,因为它将一个概念拆分成了两个部分)
  • ✅抽象工厂和工厂动机有什么不同,这导致了实现有什么不同? 产品(继承)和产品族(组合方式) 一个抽象工厂关联所有类型的产品
  • ✅单例模式 可以进行扩展 增加抽象层 重点

结构型模式:

  • ✅适配器模式 Adapter 透明性【要考】(策略模式和状态模式) 对象适配器更灵活 将多个对象的接口构成一个大的对象 默认适配器不考 双向适配器在考试范围内 掌握对象适配器和类适配器以及一个模式扩展双向适配器
  • ✅组合模式 模式结构图比较像的,装饰者模式 两个模式的component的差别? 类图的区别,装饰者还有一层抽象,组合对象只有一层 观察者模式和组合模式(都是容器)可以很容易到一起 组合模式最容易和其他模式结合 组合模式是一个重要考点(考察模式联用) 组合模式也很容易和命令模式结合
  • ✅装饰者模式 看到红字比较重要 responsibility dynamically 扩展功能可以联想装饰者 extending functionality(dynamically通常都是组合) 符合开闭原则的一般缺乏约束 挺重要的模式,但正常掌握就可以了

建造者模式 普通掌握型(意图、定义、类图等)

  • ✅原型模式 在Java中的实现,最重要的:深克隆、浅克隆 区别?邮件复制深克隆复制哪些 浅克隆复制哪些

  • ✅桥接模式 重点是变化的维度如何确定 识别抽象部分和实现部分 抽象化是实体的本身 实现化是针对抽象化的具体实现 对于单一职责原则支持非常好 策略模式、状态模式对单一职责体现明显,体现在哪里? 适配器和桥接联用 (要考 抽象、实现、模式联用)

  • ✅外观模式 概念(还有很多其他和接口相关的模式 和其他模式的区分)有几条原则在外观模式中体现的特别明显 为什么要设计外观模式? 单一职责、迪米特、对开闭原则哪里支持不好(在不引人抽象外观类的情况下,增加新的子系统可能需要修改外观类)

  • ✅享元Flyweight模式 定义 元:状态(分为内部状态、外部状态)外部状态和内部状态不一定要通过组合构成对象,可以通过任意方式进行联合使用 构造注入,参数注入都可以 享元和工厂联用 共享网络设备类图,就是通过参数注入的外部状态 关键就是区别出内部状态和外部状态

  • ✅代理模式 掌握的点很多 代理和真实对象一起工作,继承相同的抽象类 三种代理:远程代理(讲的重点 RMI 牵涉到复杂的流程,RMI中谁是代理,有相同的接口 接口从哪里来 根据真实对象来,如何获取远程代理,如何注册服务…等)、虚拟代理、保护代理 重点是三种代理,代理的应用细节,具体应用中谁是代理,代理的接口是哪一个

  • ✅命令模式:命令的发送者和命令的实现者解耦 各角色职责,命令模式和其他模式的联用 任务中还有子任务,这个时候如何和其他模式组合 几种角色:invoker,receiver等 undo和redo怎么做 宏命令,是命令模式和组合模式联用的产物 命令模式不使用宏的方式和直接调用有什么差别?(书上有)

  • ✅ 观察者模式 MVC中观察者是如何体现的 MVC之间到底是如何交互的,交互的细节是如何通过模式实现的 Java中的Observable的notify是如何实现的?pull push 外部观察者和内部观察者的差别? 内部观察隐含了this,外部观察没有信息,需要多一个参数 广播通信 通知哪些对象并不总是同一组对象 可以有多个观察队列 观察、通知是一个迭代的过程,通知部分的时候可以使用组合、迭代联合的方式 深入考察 Java观察者选择的是推还是拉,内部还是外部观察,优缺点?还受限于语言本身 保留对主题的引用,为什么?有一个拉数据的作用 外部观察是一种很好的实现方式,但是有一个致命的缺点

  • ✅状态模式 和策略模式类图相同 透明性的问题 由于意图不同,所以对客户的要求不同 策略不能对客户透明,状态对客户透明 转换、状态类是否复用 由动作引发的,在状态类中实现是最直观的 但如果通过判定状态属性来决定是否状态迁移,主要看数据在哪边 复用动作vs数据,生成新的状态 考点:场景、状态模式在具体实现的时候需要注意的点

  • ✅策略模式 策略模式体现了哪几个设计原则 和状态模式的差异

  • ✅模板方法模式 工厂是模板方法的一个刻画 方便地和其他模式联用 联用过程中另外那个方法的代码要根据模板方法实现 final方法 abstract方法


设计模式总结
https://zhangfuli.github.io/2019/12/05/设计模式总结/
作者
张富利
发布于
2019年12月5日
许可协议