设计原则

内容

  1. 面向对象的设计原则
  2. 常见七种设计原则

面向对象的设计原则:可维护性的复用

对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题。
如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。
在面向对象设计中,可维护性的复用是以设计原则为基础的。

面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则。面向对象设计原则也是用于评价一个设计模式的重要指标之一,在设计面向对象软件系统的过程中,要多思考,“XXX模式符合XXX原则吗?”、“XXX模式违反了XXX原则吗?”。

常见设计原则 - SOLID

  1. Robert C. Martin(“Uncle Bob”)在2000年代初期提出并推广了SOLID原则,《Agile Software Development, Principles, Patterns, and Practices》一书中,对这些原则进行了详细的解释和讨论。
  2. 里氏代换原则(LSP)由计算机科学家Barbara Liskov在1987年的一篇名为《Data Abstraction and Hierarchy》的论文中首次提出。虽然Liskov并没有明确提出SOLID这个术语,但她的工作为其中的LSP原则奠定了基础。
设计原则名称 定义 使用频率
单一职责原则, Single Responsibility, SRP 一个类只负责一个功能领域中的相应职责 ★★★★☆
开闭原则, Open-Closed, OCP 软件实体应对扩展开放,而对修改关闭 ★★★★★
里氏代换原则, Liskov Substitution, LSP 如果一个子类可以替换其父类,并且程序的行为没有发生变化,那么这个子类是符合里氏代换原则的。
换句话说,子类对象必须能够替换父类对象,并且不影响程序的正确性。
★★★★★
接口隔离原则, Interface Segregation, ISP 客户端不应该被迫依赖它们不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。
具体来说,接口隔离原则提倡将臃肿的接口拆分为多个小型的、特定客户的接口,这样每个接口只包含客户端真正需要的方法。
★★☆☆☆
依赖倒转原则, Dependence Inversion, DIP (软件设计的灵魂)高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。 ★★★★★

开闭原则

一个软件实体(类、模块、函数等)应该在不修改其源代码的情况下,扩展或增强其行为。
通过在不修改现有代码的情况下增加新功能,减少对已稳定系统的影响,避免引入新的错误。这可以通过使用接口、抽象类和多态等面向对象的设计机制来实现。

示例

假设我们有一个简单的图形绘制程序,最初只支持绘制圆形。为了遵循开闭原则,我们首先定义一个图形接口,并实现一个圆形类:

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
// 图形接口
interface Shape {
void draw();
}

// 圆形类
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}

// 绘制类
class Drawing {
private List<Shape> shapes;

public Drawing() {
shapes = new ArrayList<>();
}

public void addShape(Shape shape) {
shapes.add(shape);
}

public void drawShapes() {
for (Shape shape : shapes) {
shape.draw();
}
}
}

public class Main {
public static void main(String[] args) {
Drawing drawing = new Drawing();
drawing.addShape(new Circle());
drawing.drawShapes();
}
}

在这个例子中,我们的绘制程序可以绘制圆形。如果我们需要添加新的图形,例如矩形,我们可以通过扩展而不是修改现有代码来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 矩形类
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Rectangle");
}
}

public class Main {
public static void main(String[] args) {
Drawing drawing = new Drawing();
drawing.addShape(new Circle());
drawing.addShape(new Rectangle());
drawing.drawShapes();
}
}

通过添加一个实现了Shape接口的新类Rectangle,我们可以扩展绘制程序的功能,而不需要修改现有的Drawing类和Circle类。这种设计符合开闭原则,因为我们通过扩展(添加新类)而不是修改现有代码来增加新功能。

依赖倒转原则(DIP)

  1. 从项目管理的角度来说,如果A直接依赖B,那么开发周期就会被B模块拖累,A模块闲置,人力效率不高,从甘特图看需要开发28天。
    1. 而如果A、B在事先经过良好的协商,规范出一套接口的话,A、B就可以共同依赖这个接口,各自同时开发。假如协商需要3天,那么开发周期只需要3+14=173+14=17天。
  2. 从软件的扩展性来说,如果A直接依赖于B,没有事先协商好,那么B可能不能完全满足A的需求,到时候还是需要不断修改代码,开发效率低下。
    1. 如果都依赖一个接口,那么后期的扩展只需要在接口上增设方法即可。

示例

假设有一个简单的场景,一个Service类依赖于一个Repository类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Repository {
public void save(Object data) {
// 保存数据的实现
}
}

public class Service {
private Repository repository;

public Service() {
this.repository = new Repository();
}

public void process(Object data) {
// 处理数据
repository.save(data);
}
}

在这个例子中,Service类直接依赖于具体的Repository类。这种设计违反了依赖倒转原则。如果我们想要更改数据存储的方式,比如使用另一个数据存储实现,就必须修改Service类的代码。

按照依赖倒转原则,我们可以使用接口来实现依赖注入,将Repository的实现抽象化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IRepository {
void save(Object data);
}

public class Repository implements IRepository {
@Override
public void save(Object data) {
// 保存数据的实现
}
}

public class Service {
private IRepository repository;

public Service(IRepository repository) {
this.repository = repository;
}

public void process(Object data) {
// 处理数据
repository.save(data);
}
}

在这个设计中,Service类依赖于IRepository接口,而不是具体的Repository类。这样,如果我们想要更改数据存储的方式,只需要提供一个实现IRepository接口的新类即可,而不需要修改Service类的代码。通过这种方式,高层模块(Service)和低层模块(Repository)都依赖于抽象(IRepository),细节(Repository的具体实现)依赖于抽象(IRepository),符合依赖倒转原则。

这种设计提高了系统的灵活性和可维护性,使得代码更容易扩展和修改。

接口隔离原则

示例

假设有一个大型接口IMultiFunctionDevice,其中包含多种功能的方法:

1
2
3
4
5
6
public interface IMultiFunctionDevice {
void print();
void scan();
void fax();
void copy();
}

如果某个客户端只需要打印功能,那么它必须实现所有的方法,即使不需要扫描、传真和复印功能。这违反了接口隔离原则。

按照接口隔离原则,我们可以将这个大接口拆分为多个小接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IPrinter {
void print();
}

public interface IScanner {
void scan();
}

public interface IFax {
void fax();
}

public interface ICopier {
void copy();
}

然后,对于只需要打印功能的客户端,它只需要依赖IPrinter接口:

1
2
3
4
5
6
public class SimplePrinter implements IPrinter {
@Override
public void print() {
// 实现打印功能
}
}

这样,客户端只依赖于它需要的接口,实现了接口隔离原则的目标。

里氏替换原则

子类对象必须能够替换父类对象(兼容父类对象的功能),并且不影响程序的正确性。
里氏代换原则主要有以下几个要点:

  1. 子类必须完全实现父类的方法:子类不能抛出父类没有抛出的异常,子类的方法参数要与父类方法参数类型一致,返回值类型也要一致或是其子类型。
  2. 子类可以有自己的特性,但这些特性不能改变父类原有功能的含义和行为。
  3. 继承的原则:子类应该扩展父类的功能,而不是削弱父类的功能。也就是说,子类不应该删除父类中已有的功能。

比如:Old Bank的add行为结果是3,而New Bank的add虽然形式上一致,但行为却不正确。违反了里氏替换原则。猜测New Bank在重写add方法后,优化代码或更新、升级组件后造成了副作用,导致运行时行为不一致、结果不正确。

示例

考虑一个父类Bird和一个子类Penguin的例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class Bird {
public void fly() {
System.out.println("Bird is flying");
}
}

public class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins cannot fly");
}
}

在这个例子中,Penguin不能替换Bird,因为企鹅不能飞。这违反了里氏代换原则。正确的做法是重新设计类结构,使得企鹅和其他鸟类能够更好地反映现实中的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Bird {
public void move() {
System.out.println("Bird is moving");
}
}

public class FlyingBird extends Bird {
public void fly() {
System.out.println("Bird is flying");
}
}

public class Penguin extends Bird {
@Override
public void move() {
System.out.println("Penguin is walking or swimming");
}
}

在这个设计中,FlyingBird继承了Bird,并增加了fly方法,而企鹅只是继承了Bird,并重写了move方法。这样,企鹅和其他鸟类都能正确替换Bird,符合里氏代换原则。

其他设计原则

设计原则名称 定义 使用频率
合成复用原则, Composite Reuse, CRP 尽量使用对象组合,而不是继承来达到复用的目的 ★★★★☆
迪米特法则, Law of Demeter, LoD 一个软件实体应当尽可能少地与其他实体发生相互作用,只与最近关系的实体关联。 ★★★☆☆

迪米特法则

迪米特法则由Ian Holland在1987年提出,并首次在Northeastern University(美国东北大学)的Demeter Project中得到应用,因此得名。
该原则被认为是一种促进模块化和高内聚低耦合设计的重要策略,广泛应用于面向对象的软件设计中。

核心思想是:一个对象应当对其他对象有最少的了解,具体来说:

  1. 一个对象只应与直接的朋友通信:即仅与自己直接持有的对象或在方法中创建的对象通信,而不应与被间接持有的对象通信。
  2. 避免“火车式”方法调用:不要通过一个对象链式地调用多个对象的方法,例如 a.getB().getC().doSomething()。
  3. 通过引入中介减少对象间的直接依赖:通过封装对象之间的关系,使得对象之间的通信通过中介完成,从而减少直接依赖。

示例

假设有以下类结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Engine {
public void start() {
System.out.println("Engine started");
}
}

class Car {
private Engine engine;

public Car() {
this.engine = new Engine();
}

public Engine getEngine() {
return engine;
}
}

class Driver {
public void startCar(Car car) {
car.getEngine().start(); // 违反了迪米特法则
}
}

在这个例子中,Driver类通过Car类访问Engine类的方法,违反了迪米特法则。我们可以通过引入一个中介方法来改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Car {
private Engine engine;

public Car() {
this.engine = new Engine();
}

public void startEngine() {
engine.start();
}
}

class Driver {
public void startCar(Car car) {
car.startEngine(); // 符合迪米特法则
}
}

在改进后的设计中,Driver类不再直接访问Engine类的方法,而是通过Car类的startEngine方法来启动引擎。这符合迪米特法则,降低了对象之间的耦合性,提高了系统的可维护性。

设计原则的价值

优秀的面向对象的程序应该是有以下五个特征的:低耦合,高内聚,高复用,易维护,易扩展。

  1. 开闭原则让程序易扩展,易维护。
  2. 里氏替换原则、依赖倒置和合成复用原则可以降低耦合。
  3. 接口隔离、单一职责原则和迪米特原则可以提高内聚性。

而低耦合,高内聚的代码才能实现高复用。

设计模式概述

内容

  1. 模式是什么
  2. 软件模式的由来
  3. 软件模式的构成
  4. 设计模式是什么
  5. 设计模式用途分类,常用模式一览
  6. 设计模式的价值
  7. 如何学习设计模式

模式是什么

美国Christopher Alexander博士及其研究团队用了约20年的时间,对住宅和周边环境进行了大量的调查研究和资料收集工作,发现人们对舒适住宅和城市环境存在一些共同的认同规律,Christopher Alexander在著作A Pattern Language: Towns, Buildings, Construction中把这些认同规律归纳为253个模式,对每一个模式(Pattern)都从Context(前提条件)、Theme或Problem(目标问题)、 Solution(解决方案)三个方面进行了描述,并给出了从用户需求分析到建筑环境结构设计直至经典实例的过程模型。

在Christopher Alexander的另一部经典著作《建筑的永恒之道》中,他给出了关于模式的定义:

每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心,通过这种方式,我们可以无数次地重用那些已有的成功的解决方案,无须再重复相同的工作。

这个定义可以简单地用一句话表示:

模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。
A pattern is a successful or efficient solution to a recurring problem within a context.

软件模式的由来(23种)

1990年,软件工程界开始关注Christopher Alexander等在这一住宅、公共建筑与城市规划领域的重大突破。最早将模式的思想引入软件工程方法学的是1991-1992年以“四人组”自称的四位著名软件工程学者。

Gang of Four,简称GoF,分别是Erich Gamma, Richard Helm, Ralph Johnson和John Vlissides。
书名:Design Patterns: Elements of Reusable Object-Oriented Softwarec - 设计模式:可复用面向对象软件的基础

他们在1994年归纳发表了23种在软件开发中使用频率较高的设计模式,旨在用模式来统一沟通面向对象方法在分析、设计和实现间的鸿沟

GoF将模式的概念引入软件工程领域,这标志着软件模式的诞生。软件模式是将模式的一般概念应用于软件开发领域,即软件开发的总体指导思路或参照样板。软件模式并非仅限于设计模式,还包括架构模式、分析模式和过程模式等,实际上,在软件开发生命周期的每一个阶段都存在着一些被认同的模式

软件模式的构成

软件模式是在软件开发中某些可重现问题的一些有效解决方法,软件模式的基础结构主要由四部分构成,包括问题描述(待解决的问题是什么)、前提条件(在何种环境或约束条件下使用)、解法(如何解决)和效果(有哪些优缺点)。

设计模式是什么

在软件模式中,设计模式是研究最为深入的分支,设计模式用于在特定的条件下为一些重复出现的软件设计问题提供合理的、有效的解决方案,它融合了众多专家的设计经验,已经在成千上万的软件中得以应用。 1995年, GoF将收集和整理好的23种设计模式汇编成Design Patterns: Elements of Reusable Object-Oriented Software(《设计模式:可复用面向对象软件的基础》)一书,该书的出版也标志着设计模式正式成为面向对象软件工程的一个重要研究分支。

设计模式的一般定义如下:

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。

设计模式的要素

设计模式一般包含模式名称、问题、目的、解决方案、效果等组成要素,其中关键要素是模式名称、问题、解决方案和效果。

  1. 模式名称(Pattern Name)通过一两个词来描述模式的问题、解决方案和效果,以便更好地理解模式并方便开发人员之间的交流,绝大多数模式都是根据其功能或模式结构来命名的(GoF设计模式中没有一个模式用人名命名)
  2. 问题(Problem)描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因
  3. 解决方案(Solution)描述了一个设计模式的组成成分,以及这些组成成分之间的相互关系,各自的职责和协作方式,通常解决方案通过UML类图和核心代码来进行描述
  4. 效果(Consequences)描述了模式的优缺点以及在使用模式时应权衡的问题。

设计模式的用途分类

虽然GoF设计模式只有23个,但是它们各具特色,每个模式都为某一个可重复的设计问题提供了一套解决方案。

根据它们的用途,设计模式可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种。

  1. 创建型模式主要用于描述如何创建对象
  2. 结构型模式主要用于描述如何实现类或对象的组合
  3. 行为型模式主要用于描述类或对象怎样交互以及怎样分配职责

在GoF 23种设计模式中包含5种创建型设计模式、7种结构型设计模式和11种行为型设计模式。

此外,根据某个模式主要是用于处理类之间的关系还是对象之间的关系,设计模式还可以分为类模式对象模式

我们经常将两种分类方式结合使用,如单例模式是对象创建型模式,模板方法模式是类行为型模式。

常用设计模式一览表

值得一提的是,有一个设计模式虽然不属于GoF 23种设计模式,但一般在介绍设计模式时都会对它进行说明,它就是简单工厂模式,也许是太“简单”了,GoF并没有把它写到那本经典著作中,不过现在大部分的设计模式书籍都会对它进行专门的介绍。

(以下难度、频率统计结果来自刘伟)

类型 模式名称 学习难度 使用频率
创建型模式 Creational 单例模式 Singleton ★☆☆☆☆ ★★★★☆
简单工厂模式 ★★☆☆☆ ★★★☆☆
工厂方法模式 Factory Method ★★☆☆☆ ★★★★★
抽象工厂模式 Abstract Factory ★★★★☆ ★★★★★
原型模式 Prototype ★★★☆☆ ★★★☆☆
建造者模式 Builder ★★★★☆ ★★☆☆☆
结构型模式 Structural 适配器模式 Adapter ★★☆☆☆ ★★★★☆
桥接模式 Bridge ★★★☆☆ ★★★☆☆
组合模式 Composite ★★★☆☆ ★★★★☆
装饰模式 Decorator ★★★☆☆ ★★★☆☆
外观模式 Facade ★☆☆☆☆ ★★★★★
享元模式 Flyweight ★★★★☆ ★☆☆☆☆
代理模式 Proxy ★★★☆☆ ★★★★☆
行为型模式 Behavioral 职责链模式 Chain of Responsibility ★★★☆☆ ★★☆☆☆
命令模式 Command ★★★☆☆ ★★★★☆
解释器模式 Interpreter ★★★★★ ★☆☆☆☆
迭代器模式 Iterator ★★★☆☆ ★★★★★
中介者模式 Mediator ★★★☆☆ ★★☆☆☆
备忘录模式 Memento ★★☆☆☆ ★★☆☆☆
观察者模式 Observer ★★★☆☆ ★★★★★
状态模式 State ★★★☆☆ ★★★☆☆
策略模式 Strategy ★☆☆☆☆ ★★★★☆
模板方法模式 Template Method ★★☆☆☆ ★★★☆☆
访问者模式 Visitor ★★★★☆ ★☆☆☆☆

1句话描述23个设计模式

结构型

  1. 适配器模式(Adapter):将一个类的接口转换成客户希望的另一个接口。

    • 例子:电源适配器,将两孔插头适配成三孔插头。
  2. 桥接模式(Bridge):将抽象部分与它的实现部分分离,使它们可以独立变化。

    • 例子:遥控器(抽象部分)和不同类型的电视机(实现部分)。
    • 苹果系统中OC和C语言之间的桥梁。
  3. 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

    • 例子:文件夹和文件的结构,一个文件夹可以包含文件或子文件夹。
  4. 装饰模式(Decorator):动态地给对象添加一些额外的职责。

    • 例子:给圣诞树添加装饰品,每个装饰品增加新的特性。
  5. 外观模式(Facade):为子系统中的一组接口提供一个一致的界面,使子系统更容易使用。

    • 例子:餐厅服务员,统一点菜、送菜和结账的接口。
  6. 享元模式(Flyweight,也叫轻量对象模式):通过共享大量细粒度对象来节省内存。

    • 例子:围棋棋子,黑白棋子共享一个对象,节省内存。
  7. 代理模式(Proxy):为其他对象提供一种代理,以控制对这个对象的访问。

    • 例子:明星的经纪人,代理处理事务和安排活动。

行为型

  1. 责任链模式(Chain of Responsibility):将请求消息沿着处理者链传递,直到有处理者处理它。

    • 例子:公司请假审批流程,依次由主管、经理、总监审批。
  2. 命令模式(Command):将请求封装成对象,使不同的请求、队列或日志参数化。

    • 例子:遥控器上的按钮,每个按钮封装一个命令。
  3. 解释器模式(Interpreter):给定一个语言,定义它的文法表示,并定义一个解释器来处理这些表示。

    • 例子:正则表达式解析器,解释和处理正则表达式。
    • 3D软件中通过脚本或命令按钮控制界面,或控制物体运动。
  4. 迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露其内部表示。

    • 例子:图书馆的书架,使用迭代器遍历书籍。
    • STL中容器和算法之间的桥梁。
  5. 中介者模式(Mediator):用一个中介对象来封装一系列对象的交互。

    • 例子:机场的塔台,调度和管理飞机的起降。
    • 游戏引擎中用的居多。
  6. 备忘录模式(Memento):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

    • 例子:电脑的系统还原点,记录系统状态以便将来恢复。
    • Word编辑过程中的undo、redo。
  7. 观察者模式(Observer):定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

    • 例子:订阅报纸,当报纸有新内容时,通知所有订阅者。
  8. 状态模式(State):允许对象在内部状态改变时改变它的行为。

    • 例子:电梯的状态,不同状态(上下楼、开关门)下电梯行为不同。
  9. 策略模式(Strategy):定义一系列算法,将每个算法封装起来,并使它们可以互换。

    • 例子:旅游出行策略,根据情况选择不同的出行方式(开车、坐公交、骑自行车)。
    • 比如HDC,不同的设备对应不同的DC。
  10. 模板方法模式(Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法的某些步骤。

    • 例子:制作咖啡的步骤,具体的咖啡制作细节由不同种类的咖啡决定。
    • Windows_面向对象程序设计中建立的Window框架就是模板方法模式。
  11. 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作,使得可以在不改变各元素类的前提下定义作用于这些元素的新操作。

    • 例子:博物馆导览员,不同导览员为游客讲解不同展品的信息。

创建型

  1. 单例模式(Singleton):确保一个类只有一个实例,并提供全局访问点。

    • 例子:总统办公室(全国只有一个总统)。
  2. 工厂方法模式(Factory Method):定义一个创建对象的接口,但让子类决定实例化哪个类。

    • 例子:披萨店,根据订单制作不同类型的披萨。
  3. 抽象工厂模式(Abstract Factory):提供一个接口,用于创建相关或依赖对象的家族,而无需指定具体类。

    • 例子:家具工厂,可以创建一整套风格一致的家具(如现代风格或古典风格)。
  4. 建造者模式(Builder):将一个复杂对象的构建过程拆解,与其表示分离,使同样的构建过程可以创建不同的表示。

    • 例子:建筑工人,按照设计图纸建造不同的房屋。
  5. 原型模式(Prototype):通过复制现有对象来创建新对象。

    • 例子:细胞分裂,通过复制自身产生新细胞。

设计模式的价值

大部分设计模式都兼顾了系统的可重用性和可扩展性,这使得我们可以更好地重用一些已有的设计方案、功能模块甚至一个完整的软件系统,避免我们经常做一些重复的设计、编写一些重复的代码。

此外,随着软件规模日益增大,系统的可维护性和可扩展性也越来越重要,许多设计模式将有助于提高系统的灵活性和可扩展性,让我们在不修改或者少修改现有系统的基础上增加、删除或者替换功能模块。

如何学习设计模式

  1. 在学习每一个设计模式时至少应该掌握如下几点:当你能够回答所有问题时,恭喜你,你了解一个设计模式了,至于掌握它,那就在开发中去使用吧,用多了你自然就掌握了。
    1. 这个设计模式的意图是什么,它要解决一个什么问题,什么时候可以使用它;
    2. 它是如何解决的,掌握它的结构图,记住它的关键代码;
    3. 能够想到至少两个它的应用实例,一个生活中的,一个软件中的;
    4. 这个模式的优缺点是什么,在使用时要注意什么。
  2. “如果想体验一下运用模式的感觉,那么最好的方法就是运用它们。”正如在本章最开始所说的,设计模式是“内功心法”,它还是要与“实战招式”相结合才能够相得益彰。学习设计模式的目的在于应用,如果不懂如何使用一个设计模式,而只是学过,能够说出它的用途,绘制它的结构,充其量也只能说你了解这个模式,严格一点说:不会在开发中灵活运用一个模式基本上等于没学。所以一定要做到:少说多做。
  3. 不要滥用模式,不要试图在一个系统中用上所有的模式。每个模式都有自己的适用场景,不能为了使用模式而使用模式。
  4. 如果将设计模式比喻成“三十六计”,那么每一个模式都是一种计策,它为解决某一类问题而诞生,不管这个设计模式的难度如何,使用频率高不高,都应该好好学学,多学一个模式也就意味着你多了“一计”,说不定什么时候就用上了。
  5. 设计模式的“上乘”境界:“手中无模式,心中有模式”。模式使用的最高境界是你已经不知道具体某个设计模式的定义和结构了,但你会灵活自如地选择一种设计方案(其实就是某个设计模式)来解决某个问题,设计模式已经成为你开发技能的一部分,能够手到擒来,“内功”与“招式”已浑然一体,要达到这个境界并不是看完某本书或者开发一两个项目就能够实现的,它需要不断沉淀与积累,所以,对模式的学习不要急于求成。
  6. GoF已故成员、软件工程大师之一John Vlissides的著作《设计模式沉思录》(Pattern Hatching Design Patterns Applied):模式从不保证任何东西,它不能保证你一定能够做出可复用的软件,提高你的生产率,更不能保证世界和平;模式并不能替代人来完成软件系统的创造,它们只不过会给那些缺乏经验但却具备才能和创造力的人带来希望。