diff --git a/notes/设计模式.md b/notes/设计模式.md index 1a12eb41..47195c72 100644 --- a/notes/设计模式.md +++ b/notes/设计模式.md @@ -1,75 +1,77 @@ -* [前言](#前言) -* [设计模式入门](#设计模式入门) -* [观察者模式](#观察者模式) -* [装饰模式](#装饰模式) -* [工厂模式](#工厂模式) -* [单件模式](#单件模式) -* [命令模式](#命令模式) -* [适配器模式](#适配器模式) -* [外观模式](#外观模式) -* [模板方法模式](#模板方法模式) -* [迭代器模式](#迭代器模式) -* [组合模式](#组合模式) -* [状态模式](#状态模式) -* [代理模式](#代理模式) -* [复合模式](#复合模式) -* [与设计模式相处](#与设计模式相处) -* [剩下的模式](#剩下的模式) +* [一、前言](#一前言) +* [二、设计模式概念](#二设计模式概念) +* [三、策略模式](#三策略模式) +* [三、观察者模式](#三观察者模式) +* [四、装饰模式](#四装饰模式) +* [五、简单工厂](#五简单工厂) +* [六、工厂方法模式](#六工厂方法模式) +* [七、抽象工厂模式](#七抽象工厂模式) +* [八、单例模式](#八单例模式) +* [九、命令模式](#九命令模式) +* [十、适配器模式](#十适配器模式) +* [十、外观模式](#十外观模式) +* [十一、模板方法模式](#十一模板方法模式) +* [十二、迭代器模式](#十二迭代器模式) +* [十三、组合模式](#十三组合模式) +* [十四、状态模式](#十四状态模式) +* [十五、代理模式](#十五代理模式) +* [十六、MVC](#十六mvc) +* [十七、与设计模式相处](#十七与设计模式相处) -# 前言 +# 一、前言 -文中涉及一些 UML 类图,为了更好地理解,可以先阅读 [UML 类图](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#1-%E7%B1%BB%E5%9B%BE)。 +文中涉及一些 UML 类图,为了更好地理解,可以先阅读 [UML 类图](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E7%AC%AC%E4%B8%89%E7%AB%A0-uml)。 需要说明的一点是,文中的 UML 类图和规范的 UML 类图不大相同,其中组合关系使用以下箭头表示:

-# 设计模式入门 - -**1. 设计模式概念** +# 二、设计模式概念 设计模式不是代码,而是解决问题的方案,学习现有的设计模式可以做到经验复用。 拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。 -**2. 问题描述** +# 三、策略模式 + +## 模式定义 + +定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 + +## 问题描述 设计不同种类的鸭子拥有不同的叫声和飞行方式。 -**3. 简单实现方案** +## 简单实现方案 使用继承的解决方案如下,这种方案代码无法复用,如果两个鸭子类拥有同样的飞行方式,就有两份重复的代码。

-**4. 设计原则** +## 设计原则 -封装变化 :在这里变化的是鸭子叫和飞行的行为方式。 +**封装变化** :在这里变化的是鸭子叫和飞行的行为方式。 -针对接口编程,而不是针对实现编程 :变量声明的类型为父类,而不是具体的某个子类。父类中的方法实现不在父类,而是在各个子类。程序在运行时可以动态改变变量所指向的子类类型。 +**针对接口编程,而不是针对实现编程** :变量声明的类型为父类,而不是具体的某个子类。父类中的方法实现不在父类,而是在各个子类。程序在运行时可以动态改变变量所指向的子类类型。 运用这一原则,将叫和飞行的行为抽象出来,实现多种不同的叫和飞行的子类,让子类去实现具体的叫和飞行方式。

-多用组合,少用继承:组合也就是 HAS-A 关系,通过组合,可以在运行时动态改变实现,只要通过改变父类对象具体指向哪个子类即可。而继承就不能做到这些,继承体系在创建类时就已经确定。 +**多用组合,少用继承** :组合也就是 HAS-A 关系,通过组合,可以在运行时动态改变实现,只要通过改变父类对象具体指向哪个子类即可。而继承就不能做到这些,继承体系在创建类时就已经确定。 运用这一原则,在 Duck 类中组合 FlyBehavior 和 QuackBehavior 类,performQuack() 和 performFly() 方法委托给这两个类去处理。通过这种方式,一个 Duck 子类可以根据需要去初始化 FlyBehavior 和 QuackBehavior 的子类对象,并且也可以动态地进行改变。

-**5. 整体设计图** +## 问题的解决方案类图

-**6. 模式定义** - -策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 - -**7. 实现代码** +## 代码实现 ```java public abstract class Duck { @@ -172,15 +174,15 @@ FlyBehavior.FlyWithWings FlyBehavior.FlyNoWay ``` -# 观察者模式 +# 三、观察者模式 -**1. 模式定义** +## 模式定义 定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。

-**2. 模式类图** +## 模式类图 主题具有注册和移除观察者、并通知所有注册者的功能,主题是通过维护一张观察者列表来实现这些操作的。 @@ -188,19 +190,19 @@ FlyBehavior.FlyNoWay

-**3. 问题描述** +## 问题描述 天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。 -**4. 解决方案类图** +## 问题的解决方案类图

-**5. 设计原则** +## 设计原则 为交互对象之间的松耦合设计而努力:当两个对象之间松耦合,它们依然可以交互,但是不清楚彼此的细节。由于松耦合的两个对象之间互相依赖程度很低,因此系统具有弹性,能够应对变化。 -**6. 实现代码** +## 代码实现 ```java public interface Subject { @@ -304,13 +306,13 @@ CurrentConditionsDisplay.update:1.0 1.0 1.0 StatisticsDisplay.update:1.0 1.0 1.0 ``` -# 装饰模式 +# 四、装饰模式 -**1. 问题描述** +## 问题描述 设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。 -**2. 模式定义** +## 模式定义 动态地将责任附加到对象上。在扩展功能上,装饰者提供了比继承更有弹性的替代方案。 @@ -318,25 +320,25 @@ StatisticsDisplay.update:1.0 1.0 1.0

-**3. 模式类图** +## 模式类图 装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。

-**4. 问题的解决方案类图** +## 问题的解决方案类图

-**5. 设计原则** +## 设计原则 类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。在本章问题中该原则体现在,饮料可以动态添加新的配料,而不需要去修改饮料的代码。观察则模式也符合这个原则。不可能把所有的类设计成都满足这一原则,应当把该原则应用于最有可能发生改变的地方。 -**6. Java I/O 中的装饰者模式** +## Java I/O 中的装饰者模式

-**7. 代码实现** +## 代码实现 ```java public interface Beverage { @@ -407,15 +409,13 @@ public class StartbuzzCoffee { 3.0 ``` -# 工厂模式 +# 五、简单工厂 -## 1. 简单工厂 - -**1. 问题描述** +## 问题描述 Pizza 类有很多子类,要求根据不同的情况用不同的子类实例化一个 Pizza 对象。 -**2. 定义** +## 模式定义 简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个子类来实例化。 @@ -423,11 +423,11 @@ Pizza 类有很多子类,要求根据不同的情况用不同的子类实例

-**3. 问题的解决方案类图** +## 问题的解决方案类图

-**4. 代码实现** +## 代码实现 ```java public interface Pizza { @@ -483,17 +483,17 @@ public class PizzaStore { CheesePizza ``` -## 2. 工厂方法模式 +# 六、工厂方法模式 -**1. 问题描述** +## 问题描述 每个地区的 PizzaStore 卖的 Pizza 虽然种类相同,但是都有自己的风味。一个客户点了纽约的 cheese 种类的 Pizza 和在芝加哥点的相同种类的 Pizza 是不同的。要求设计出满足条件的 PizzaStore。 -**2. 模式定义** +## 2. 模式定义 定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化推迟到子类。 -**3. 模式类图** +## 3. 模式类图 在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。 @@ -501,19 +501,19 @@ CheesePizza

-**4. 问题的解决方案类图** +## 4. 问题的解决方案类图 PizzaStore 有 orderPizza() 方法,顾客可以用它来下单。下单之后需要先使用 createPizza() 来制作 Pizza,这里的 createPizza() 就是 factoryMethod(),不同的 PizzaStore 子类实现了不同的 createPizza()。

-**5. 设计原则** +## 5. 设计原则 依赖倒置原则:要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 Pizza 是抽象类,PizzaStore 和 Pizza 子类都依赖于 Pizza 这个抽象类。

-**6. 代码实现** +## 6. 代码实现 ```java public interface Pizza { @@ -609,13 +609,13 @@ NYStyleCheesePizza is making.. ChicagoStyleCheesePizza is making.. ``` -## 3. 抽象工厂模式 +# 七、抽象工厂模式 -**1. 模式定义** +## 模式定义 提供一个接口,用于创建 **相关的对象家族** 。 -**2. 模式类图** +## 模式类图 抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂模式只是用于创建一个对象,这和抽象工厂模式有很大不同。 @@ -627,11 +627,11 @@ ChicagoStyleCheesePizza is making..

-**3. 解决方案类图** +## 解决方案类图

-**4. 代码实现** +## 代码实现 ```java public interface Dough { @@ -740,13 +740,13 @@ ThickCrustDough MarinaraSauce ``` -# 单件模式 +# 八、单例模式 -**1. 模式定义** +## 模式定义 确保一个类只有一个实例,并提供了一个全局访问点。 -**2. 模式类图** +## 模式类图 使用一个私有构造器、一个私有静态变量以及一个公有静态函数来实现。 @@ -754,7 +754,7 @@ MarinaraSauce

-**3. 懒汉式-线程不安全** +## 懒汉式-线程不安全 以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。 @@ -777,7 +777,7 @@ public class Singleton { } ``` -**4. 懒汉式-线程安全** +## 懒汉式-线程安全 只需要对 `getUniqueInstance()` 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。 @@ -792,7 +792,7 @@ public static synchronized Singleton getUniqueInstance() { } ``` -**5. 饿汉式-线程安全** +## 饿汉式-线程安全 线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。 @@ -800,7 +800,7 @@ public static synchronized Singleton getUniqueInstance() { private static Singleton uniqueInstance = new Singleton(); ``` -**6. 双重校验锁-线程安全** +## 双重校验锁-线程安全 uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。 @@ -837,9 +837,9 @@ if (uniqueInstance == null) { } ``` -# 命令模式 +# 九、命令模式 -**1. 问题描述** +## 问题描述 设计一个遥控器,它有很多按钮,每个按钮可以发起一个命令,命令会让一个家电完成相应操作。有非常多的家电,并且之后会增加家电。 @@ -849,11 +849,11 @@ if (uniqueInstance == null) {

-**2. 模式定义** +## 模式定义 将命令封装成对象,以便使用不同的命令来参数化其它对象。 -**3. 问题的解决方案类图** +## 问题的解决方案类图 - RemoteControl 是遥控器,它可以为每个按钮设置命令对象,并且执行命令。 @@ -865,11 +865,11 @@ if (uniqueInstance == null) {

-**4. 模式类图** +## 模式类图

-**5. 代码实现** +## 代码实现 ```java public interface Command { @@ -948,31 +948,31 @@ public class RemoteLoader { Light is on! ``` -# 适配器模式 +# 十、适配器模式 -**1. 模式定义** +## 模式定义 将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间。

-**2. 模式类图** +## 模式类图 适配器(Adapter)组合一个适配者(Adaptee),Adapter 把操作委托给 Adaptee。

-**3. 问题描述** +## 问题描述 鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。 要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子! -**4. 解决方案类图** +## 问题的解决方案类图

-**5. 代码实现** +## 代码实现 ```java public interface Duck { @@ -1026,69 +1026,69 @@ public class DuckTestDrive { gobble! ``` -# 外观模式 +# 十、外观模式 -**1. 模式定义** +## 模式定义 提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。 -**2. 模式类图** +## 模式类图

-**3. 问题描述** +## 问题描述 家庭影院中有众多电器,当要进行观看电影时需要对很多电器进行操作。要求简化这些操作,使得家庭影院类只提供一个简化的接口,例如提供一个看电影相关的接口。

-**4. 解决方案类图** +## 解决方案类图

-**5. 设计原则** +## 设计原则 -最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。 +**最少知识原则** :只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。 -**6. 代码实现** +## 代码实现 过于简单,无实现。 -# 模板方法模式 +# 十一、模板方法模式 -**1. 模式定义** +## 模式定义 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 -**2. 模式类图** +## 模式类图 模板方法 templateMethod() 定义了算法的骨架,确定了 primitiveOperation1() 和 primitiveOperation2() 方法执行的顺序,而 primitiveOperation1() 和 primitiveOperation2() 让子类去实现。

-**3. 问题描述** +## 问题描述 冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。

-**4. 问题的解决方案类图** +## 问题的解决方案类图 prepareRecipe() 方法就是模板方法,它确定了其它四个方法的具体执行步骤。其中 brew() 和 addCondiments() 方法在子类中实现。

-**5. 设计原则** +## 设计原则 -好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。这一原则可以防止依赖腐败,即防止高层组件依赖低层组件,低层组件又依赖高层组件。该原则在模板方法的体现为,只有父类会调用子类,子类不会调用父类。 +**好莱坞原则** :别调用(打电话给)我们,我们会调用(打电话给)你。这一原则可以防止依赖腐败,即防止高层组件依赖低层组件,低层组件又依赖高层组件。该原则在模板方法的体现为,只有父类会调用子类,子类不会调用父类。 -**6. 钩子** +## 钩子 某些步骤在不同实现中可有可无,可以先定义一个什么都不做的方法,把它加到模板方法中,如果子类需要它就覆盖默认实现并加上自己的实现。 -**7. 代码实现** +## 代码实现 ```java public abstract class CaffeineBeverage { @@ -1168,13 +1168,13 @@ pourInCup Tea.addCondiments ``` -# 迭代器模式 +# 十二、迭代器模式 -**1. 模式定义** +## 模式定义 提供顺序访问一个聚合对象中的各个元素的方法,而又不暴露聚合对象内部的表示。 -**2. 模式类图** +## 模式类图 - Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator; @@ -1184,7 +1184,7 @@ Tea.addCondiments

-**3. 代码实现** +## 代码实现 ```java public class Aggregate { @@ -1258,7 +1258,7 @@ public class Client { 9 ``` -**4. Java 内置的迭代器** +## Java 内置的迭代器 需要让聚合类实现 Iterable 接口,该接口有一个 iterator() 方法会返回一个 Iterator 对象。 @@ -1320,19 +1320,19 @@ public class Client { } ``` -# 组合模式 +# 十三、组合模式 -**1. 设计原则** +## 设计原则 一个类应该只有一个引起它改变的原因。 -**2. 模式定义** +## 模式定义 允许将对象组合成树形结构来表现“整体/部分”关系。 组合能让客户以一致的方式处理个别对象以及组合对象。 -**3. 模式类图** +## 模式类图 组件(Component)类是组合类(Composite)和叶子类(Leaf)的父类,可以把组合类看成是树的中间节点。 @@ -1340,7 +1340,7 @@ public class Client {

-**4. 代码实现** +## 代码实现 ```java public abstract class Component { @@ -1441,19 +1441,19 @@ Composite:root --left:3 ``` -# 状态模式 +# 十四、状态模式 -**1. 模式定义** +## 模式定义 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。 -**2. 模式类图** +## 模式类图 Context 的 request() 方法委托给 State 对象去处理。当 Context 组合的 State 对象发生改变时,它的行为也就发生了改变。

-**3. 与策略模式的比较** +## 与策略模式的比较 状态模式的类图和策略模式一样,并且都是能够动态改变对象的行为。 @@ -1463,13 +1463,13 @@ Context 的 request() 方法委托给 State 对象去处理。当 Context 组合 状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 需要使用哪个算法。 -**4. 问题描述** +## 问题描述 糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。

-**5. 直接解决方案** +## 直接解决方案 在糖果机的每个操作函数里面,判断当前的状态,根据不同的状态进行不同的处理,并且发生不同的状态转移。 @@ -1477,7 +1477,7 @@ Context 的 request() 方法委托给 State 对象去处理。当 Context 组合

-**6 代码实现** +## 代码实现 糖果销售机即 Context。 @@ -1763,34 +1763,36 @@ You turned, but there are no gumballs No gumball dispensed ``` -# 代理模式 +# 十五、代理模式 -# 复合模式 +# 十六、MVC -## MVC - -**传统 MVC** +## 传统 MVC 视图使用组合模式,模型使用了观察者模式,控制器使用了策略模式。

-**Web 中的 MVC** +## Web 中的 MVC 模式不再使用观察者模式。

-# 与设计模式相处 +# 十七、与设计模式相处 -定义:在某情境下,针对某问题的某种解决方案。 +## 定义 + +在某情境下,针对某问题的某种解决方案。 + +## 何时使用 过度使用设计模式可能导致代码被过度工程化,应该总是用最简单的解决方案完成工作,并在真正需要模式的地方才使用它。 -反模式:不好的解决方案来解决一个问题。主要作用是为了警告人们不要使用这些解决方案。 +## 反模式 -模式分类: +不好的解决方案来解决一个问题。主要作用是为了警告人们不要使用这些解决方案。 + +## 模式分类

- -# 剩下的模式