设计模式

设计模式 封装变化点 面向接口编程

一、前言

进行设计时,重在深刻理解面向对象的设计思想,而不是牵强地套用某个设计模式

因为使用设计模式往往会在系统中添加更多的层,在系统中增加额外的类和对象,影响系统的效率。

下面讲述一些导致系统重新设计的一般原因,以及解决这些问题的设计模式

(1) 通过显示地指定一个类来创建对象

在创建对象时指定类名将使你受特定实现的约束而不是特定接口的约束。这会使未来的变化更复杂。要避免这种情况,应该间接地创建对象。

设计模式:

  • Abstract Factory

  • Factory Method

  • Prototype

(2)对特殊操作的依赖

当你为请求指定一个特殊的操作时,完成该请求的方式就固定下来了。为避免把请求代码写死,你可以在编译时或运行时很方便地改变响应请求的方法。

设计模式:

  • Chain of Responsibility

  • Command

(3)对硬件和软件平台的依赖

外部的操作系统接口和应用编程接口(API)在不同的软硬件平台上是不同的。依赖于特定平台的软件将很难已知到其他平台上,甚至很难跟上平地平台的更新,所以设计系统时限制其平台相关性就很重要了。

设计模式:

  • Abstract Factory

  • Bridge

(4)对对象表示或实现的依赖

知道对象怎么表示、保存、定位或实现的客户在对象发生变化时可能也需要变化。对客户隐藏这些信息能阻止连锁变化。

设计模式:

  • Abstract Factory

  • Bridge

  • Memento

  • Proxy

(5)算法依赖

算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此有可能发生变化的算法应该被孤立起来。

设计模式:

  • Builder

  • Iterator

  • Strategy

  • Template Method

  • Visitor

(6)紧耦合

紧耦合的类很难单独地被复用,因为它们是互相依赖的。紧耦合产生单块的系统,要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统是一个很难学习、移植和维护的密集体。

松散耦合提高了一个类本身被复用的可能性,并且系统更易于学习、移植、修改和扩展。设计模式使用松散耦合分层技术来提高系统的松散耦合性。

设计模式:

  • Abstract Factory

  • Command

  • Facade

  • Mediator

  • Observer

  • Chain of Responsibility

(7) 通过生成子类来扩充功能

通常很难通过定义子类来定制对象。每一个新类都有固定的实现开销(初始化、终止处理等)。定义子类还需要对父类有深入的了解。例如,重定义一个操作可能需要重定义其他操作。一个被重定义的操作可能需要调用继承下来的操作。并且子类方法会导致类爆炸,因为即使对于一个简单的扩充,你也不得不引入许多新的子类。

一般的对象组合技术和具体的委托技术,是继承之外组合对象行为的另一种灵活方法。新的功能可以通过以新的方式组合已有对象,而不是通过定义已存在的类的子类的方式加到应用中去。另一方面,过多的使用对象组合会使设计难于理解。许多设计模式产生的设计中,可以定义一个子类,且将它的实例和已存在的实例进行组合来引入定制的功能。

设计模式:

  • Bridge

  • Chain of Responsibility

  • Composite

  • Decorator

  • Observer

  • Strategy

(8)不能方便地对类进行修改

有时你不得不改变一个难以修改的类。也许你需要源代码而又没有(对于商业类库就有这种情况),或者可能对类的任何改变会要求修改许多已存在的其他子类。设计模式提供在这些情况下对类修改的方法。

设计模式:

  • Adapter

  • Decorator

  • Visitor

二、设计模式与框架

谈到设计模式,就不可避免地要谈到框架

框架不是设计模式框架是针对某个领域,提供用于开发应用系统的类的集合,程序设计者可以使用框架提供的类设计一个应用程序,而且在设计应用程序时可以针对特定的问题使用某个设计模式

  • 层次不同

    设计模式比框架更抽象 ,它是从软件系统中总结出的成功的、可复用的设计方案,设计模式不能向使用者提供直接可以使用的类,设计模式只有在被设计人员使用时才能表示为代码。

    框架,可以简单的理解为用于设计解决某个问题的一些类的集合。例如在Java中,开发人员使用Swing框架提供的类设计用户界面,使用Set集合框架提供的类处理数据结构相关的算法等。

  • 范围不同

    设计模式是逻辑概念,独立于编程语言。

    框架是具体的,以具体的软件组织存在,只能被特定的软件设计者使用。例如在Java中,Java提供的Swing框架只能被Java应用程序所使用。

  • 相互关系

    一个框架中往往包含多个设计模式。

    根据设计模式可以设计出一个框架(譬如后文的简单问题+应用程序)

三、设计模式

3.1 行为型

涉及怎么合理的设计对象之间的交互通信,怎么合理的为对象分配职责

3.1.1 策略模式

定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。使得算法独立于使用它的客户的而变化

3.1.1.1 结构

  • 策略(Strategy)接口

    是一个接口,定义了若干个抽象方法。(algorithm)

  • 上下文(Context)

    关联于策略接口的类

    包含用策略声明的变量

    提供一个方法,该方法委托策略变量调用具体策略所实现的策略接口中的方法

  • 具体策略(ConcreteStrategy)

    实现策略接口的类,给出具体的算法

3.1.1.2 小框架

在多个裁判负责打分的比赛中,每位裁判给选手打一个分,选手的最后得分是根据全体裁判的得分计算出来的。请给出几种计算选手得分的评分方案(策略),对于某次比赛,可以从你的方案中选择一种方案作为本次比赛的评分方案。

Strategy.java (策略)

1
2
3
public interface Strategy {
public double computeAverage(double[] a);
}

AverageScore.java (上下文)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AverageScore {
Strategy strategy;

public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}

public double getAverage(double[] a) {
if (strategy != null) {
return strategy.computeAverage(a);
} else {
System.out.println("没有求平均值的算法,得到的-1不代表平均值");
return -1;
}
}
}

StrategyA.java (具体策略)

1
2
3
4
5
6
7
8
9
10
11
12
// 直接取代数平均值
public class StrategyA implements Strategy {
public double computeAverage(double[] a) {
double score = 0;
double sum = 0;
for (int i = 0; i < a.length; i++) {
sum += a[i];
}
score = sum / a.length;
return score;
}
}

StrategyB.java (具体策略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.lang.reflect.Array;
import java.util.Arrays;

import java.util.Arrays;

// 去掉一个最大值和最小值,然后求平均
public class StrategyB implements Strategy {
public double computeAverage(double[] a) {
if (a.length <= 2) {
return 0;
}
double score = 0;
double sum = 0;
Arrays.sort(a);
for (int i = 1; i < a.length - 1; i++) {
sum += a[i];
}
score = sum / (a.length - 2);
return score;
}
}

3.1.1.3 应用小框架编写应用程序

把上述那些类看成一个小框架,使用小框架中的类编写一个应用程序

Application.java

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
class Person {
String name;
double score;

public void setScore(double score) {
this.score = score;
}

public void setName(String name) {
this.name = name;
}

public double getScore() {
return this.score;
}

public String getName() {
return this.name;
}
}

public class Application {
public static void main(String[] args) {
AverageScore game = new AverageScore();// 上下文对象game
game.setStrategy(new StrategyA());// 上下文对象使用具体策略 使用算法1计算平均值
Person zhang = new Person();
zhang.setName("张三");
double[] a = { 9.12, 9.25, 8.87, 9.99, 6.99, 7.88 };
double aver = game.getAverage(a); // 上下文对象得到平均值
zhang.setScore(aver);
System.out.println("算法A:");
System.out.printf("%s最后得分:%5.3f%n", zhang.getName(), zhang.getScore());
game.setStrategy(new StrategyB());
aver = game.getAverage(a);// 上下文对象得到平均值
zhang.setScore(aver);
System.out.println("算法B:");
System.out.printf("%s最后得分:%5.3f%n", zhang.getName(), zhang.getScore());
}
}

3.1.1.4 适合策略模式的场景

  1. 需要使用一个算法的不同变体

  2. 程序的主要类(相当于上下文角色)不希望暴露复杂的、与算法相关的数据结构,那么可以使用策略模式封装算法,即将算法分别封装到具体的策略中。

  3. 一个类定义了多种行为,并且这些行为在这个类的方法中以多个条件语句的形式出现,那么可以使用策略模式避免在类中使用大量的条件语句。

3.1.2 观察者模式

在不改变各个元素的类的前提下定义作用于这些元素的新操作

又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

3.1.2.1 结构

双重分派技术,将数据的存储和操作解耦合。当执行

元素.accept(访问者)

时,就会导致执行

访问者.visit(元素)

  • 访问者(Visitor)接口

    依赖于抽象元素类,定义了操作元素的方法

  • 抽象元素(Element)类

    依赖于抽象接口,定义了接收访问者的方法

  • 具体访问者

  • 具体元素

3.1.2.2 小框架

某个类可能用自己的实例方法操作自己的数据,但在某些设计中,可能需要定义作用于类中的数据的新操作,而且这个新的操作不应该由该类中的某个实例方法来承担。

根据电表显示的电量,计算用户的电费。电表由自己的显示用电量的方法(用显示盘显示),但需要定义一个方法来计算电费,即需要定义作用于电量的新操作,显然这个新的操作不应当由电表来承担。按照家用标准计算电表的”计表员“和按工业标准计算电表的”计表员“

抽象访问者接口

Visitor.java

1
2
3
public interface Visitor {
public double visit(AmmeterElement element);
}

抽象元素类

AmmeterElement.java

1
2
3
4
5
6
7
public abstract class AmmeterElement {
public abstract void accept(Visitor v);

public abstract double showElectricAmount();

public abstract void setElectricAmount(double n);
}

具体访问者

HomeAmmeterVisitor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HomeAmmeterVisitor implements Visitor {
public double visit(AmmeterElement element) {
double charge = 0;
double unitOne = 0.6;
double unitTwo = 1.05;
int basic = 6000;
double n = element.showElectricAmount();
if (n <= basic) {
charge = n * unitOne;
} else {
charge = basic * unitOne + (n - basic) * unitTwo;
}
return charge;
}
}

IndustryAmmeterVisitor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class IndustryAmmeterVisitor implements Visitor {
public double visit(AmmeterElement element) {
double charge = 0;
double unitOne = 1.52;
double unitTwo = 2.78;
int basic = 15000;
double n = element.showElectricAmount();
if (n <= basic) {
charge = n * unitOne;
} else {
charge = basic * unitOne + (n - basic) * unitTwo;
}
return charge;
}
}

具体元素

Ammeter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Ammeter extends AmmeterElement {
double electricAmount; // 电表的电量

public void accept(Visitor v) {
double feiyong = v.visit(this); // 让访问者访问当前元素
System.out.println("当前电表的用户需要交纳电费:" + feiyong + "元.");
}

public double showElectricAmount() {
return this.electricAmount;
}

public void setElectricAmount(double n) {
this.electricAmount = n;
}
}

3.1.2.3 应用小框架编写应用程序

把上述那些类看成一个小框架,使用小框架中的类编写一个应用程序

Application.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Application {
public static void main(String[] args) {
Ammeter ammeter = new Ammeter(); // 电表
Visitor homeVisitor = new HomeAmmeterVisitor(); // 按照家用标准计算电费的”计表员“
ammeter.setElectricAmount(7890);
ammeter.accept(homeVisitor);

Visitor industryVisitor = new IndustryAmmeterVisitor(); // 按照工业标准计算电费的”计表员“
ammeter.setElectricAmount(7890);
ammeter.accept(industryVisitor);
}
}

3.1.2.4 适合策略模式的场景

某个类可能用自己的实例方法操作自己的数据,但在某些设计中,可能需要定义作用于类中的数据的新操作,而且这个新的操作不应该由该类中的某个实例方法来承担。

(1)在一个对象结构中,例如某个集合中,包含很多对象,想对集合中的对象增加一些新的操作

(2)需要对集合中的对象进行很多不同的并且不相关的操作,而又不想修改对象的类。在访问者接口中定义一些关于集合中对对象的操作。

3.2 结构型

涉及如何合理的组合类和对象

3.2.1 装饰模式

动态地给对象添加一些额外的职责,相比于生成子类更为灵活。

3.2.1.1 结构

  • 抽象组件(Component),被装饰者

    抽象类,定义了需要进行装饰的方法

  • 装饰(Decorator),装饰者

    需要包含被装饰者的引用

    "装饰"角色是抽象组件的一个子类

    "装饰"角色可以是抽象类,也可以是一个非抽象类

  • 具体组件(Concrete Component)

  • 具体装饰(Concrete Decorator)

3.2.1.2 小框架

给麻雀安装智能电子翅膀。智能电子翅膀可以使麻雀不使用自己的翅膀就能飞行50m,那么一只安装了智能电子翅膀的麻雀就能飞行150m,安装了两个智能电子翅膀就能飞行200m,以此类推。

抽象组件

Bird.java

1
2
3
public abstract class Bird {
public abstract int fly();
}

具体组件

Sparrow.java

1
2
3
4
5
6
7
public class Sparrow extends Bird {
public final int DISTANCE = 100;

public int fly() {
return DISTANCE;
}
}

装饰

Decorator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Decorator extends Bird {
Bird bird; // 被装饰者

public Decorator() {

}

public Decorator(Bird bird) {
this.bird = bird;
}

public abstract int eleFly(); // 用于修饰fly()的方法,行为由具体装饰者实现
}

具体装饰

SparrowDecorator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SparrowDecorator extends Decorator {
public final int DISTANCE = 50; // eleFly方法(模拟电子翅膀)能飞50m

SparrowDecorator(Bird bird) {
super(bird);
}

public int fly() {// 被装饰的方法
int distance = 0;
distance = bird.fly() + eleFly();
return distance;
}

public int eleFly() {// 具体装饰者重写装饰者中用于装饰的方法
return DISTANCE;
}
}

3.2.1.3 应用小框架编写应用程序

把上述那些类看成一个小框架,使用小框架中的类编写一个应用程序

Application.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Application {
public static void main(String[] args) {
Bird bird = new Sparrow();
System.out.println("没有安装电子翅膀的小鸟飞行距离:" + bird.fly());
bird = new SparrowDecorator(bird);// bird通过“装饰”安装了1个电子翅膀
System.out.println("安装1个电子翅膀的小鸟飞行距离:" + bird.fly());
bird = new SparrowDecorator(bird);// bird通过“装饰”安装了2个电子翅膀
System.out.println("安装2个电子翅膀的小鸟飞行距离:" + bird.fly());
bird = new SparrowDecorator(bird);// bird通过“装饰”安装了3个电子翅膀
System.out.println("安装3个电子翅膀的小鸟飞行距离:" + bird.fly());
}
}

思考:想要bird飞行240m怎么办?

使用多个装饰者,新增一个类即可。

SparrowDecoratorTwo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SparrowDecoratorTwo extends Decorator {
public final int DISTANCE = 20;// eleFly方法能飞20m

SparrowDecoratorTwo(Bird bird) {
super(bird);
}

public int fly() {
int distance = 0;
distance = bird.fly() + eleFly();
return distance;
}

public int eleFly() {
return DISTANCE;
}
}
1
2
3
4
5
Bird bird = new Sparrow();
bird = new SparrowDecoratorTwo(bird);
bird = new SparrowDecorator(bird);
bird = new SparrowDecorator(bird);
bird = new SparrowDecoratorTwo(bird); // 这样能飞240m了

3.2.1.4 适合装饰模式的场景

程序希望动态地增强类的某个对象的功能,而又不影响该类的其他对象

3.2.2 适配器模式

将一个类的接口转换成客户希望的另外一个接口

3.2.2.1 结构

  • 目标(Target)

    是一个接口,是客户想用的接口

  • 被适配者(Adaptee)

    是一个已经存在的接口或抽象类

  • 适配器(Adapter)

    是一个类,实现了目标接口,并包含被适配者的引用

3.2.2.2 小框架

用户有一台洗衣机,使用交流电,现在用户新买了一个录音机,录音机只能使用直流电。由于供电系统供给用户家里的是交流电,所以用户需要适配器将交流电转化为直流电以便录音机使用。

目标

DirectCurrent.java

1
2
3
public interface DirectCurrent {
public String giveDirectCurrent();
}

被适配者

AlternateCurrent.java

1
2
3
public interface AlternateCurrent {
public String giveAlternateCurrent();
}

适配器

ElectricAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ElectricAdapter implements DirectCurrent {
AlternateCurrent out;

ElectricAdapter(AlternateCurrent out) {
this.out = out;
}

public String giveDirectCurrent() {
String m = out.giveAlternateCurrent(); // 先有out得到交流电
StringBuffer str = new StringBuffer(m);
// 以下将交流电转化为直流电
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '0') {
str.setCharAt(i, '1');
}
}
m = new String(str);
return m; // 返回直流电
}
}

3.2.2.3 应用小框架编写应用程序

把上述那些类看成一个小框架,使用小框架中的类编写一个应用程序

Application.java

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
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Application {
public static void main(String[] args) {
AlternateCurrent aElectric = new PowerCompany();// 交流电
Wash wash = new Wash();
wash.turnOn(aElectric);// 洗衣机使用交流电
// 对交流电aElectric进行适配得到直流电dElectric
DirectCurrent dElectric = new ElectricAdapter(aElectric);// 将交流电适配成直流电
Recorder recorder = new Recorder();
recorder.turnOn(dElectric);
}
}

class PowerCompany implements AlternateCurrent {// 交流电提供者
public String giveAlternateCurrent() {
return "1010101010101010101010";// 用这样的串表示交流电
}
}

class Wash {
String name; // 洗衣机使用交流电

Wash() {
name = "洗衣机";
}

Wash(String s) {
name = s;
}

public void turnOn(AlternateCurrent a) {
String s = a.giveAlternateCurrent();
System.out.println(name + "使用交流电:\n" + s);
System.out.println("开始洗衣物......");
}
}

class Recorder {
String name;// 录音机使用直流电

Recorder() {
name = "录音机";
}

Recorder(String s) {
name = s;
}

public void turnOn(DirectCurrent a) {
String s = a.giveDirectCurrent();
System.out.println(name + "使用直流电:\n" + s);
System.out.println("开始录音......");
}
}

3.2.2.4 适合适配器模式的场景

将一个类的接口转换成客户希望的另外一个接口。

3.3 创建型

涉及对象的实例化,不让用户依赖于对象的创建或者排列方式,避免用户直接使用new创建对象

3.3.1 工厂方法模式

把类的实例化延迟到子类

四、设计模式所支持的设计的可变方面

4.1 创建型

设计模式 可变的方面
Abstract Factory 产品对象家族
Builder 如何创建一个组合对象
Factory Method 被实例化的子类
Prototype 被实例化的类
Singleton 一个类的唯一实例

4.2 结构型

设计模式 可变的方面
Adapter 对象的接口
Bridge 对象的实现
Composite 一个对象的结构和组成
Decorator 对象的职责,不生成子类
Facade 一个子系统的接口
Flyweight 对象的存储开销
Proxy 如何访问一个对象;该对象的位置

4.3 行为型

设计模式 可变的方面
Chain of Responsibility 满足一个请求的对象
Command 何时、怎样满足一个请求对象
Interpreter 一个语言的文法及解释
Iterator 如何遍历、访问一个聚合的各元素
Mediator 对象之间怎样交互、和谁交互
Memento 一个对象中哪些私有信息存放在该对象之外,以及在什么时候进行存储
Observer 多个对象依赖于另外一个对象,而这些对象又如何保持一致
State 对象的状态
Strategy 算法
Template Method 算法中的某些步骤
Visitor 某些可作用于一个(组)对象上的操作,但不修改这些对象的类
文章作者: 小王同学
文章链接: https://morvan.top/2020/07/12/设计模式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 小王同学的精神驿站