大话设计模式

大话设计模式

基础知识 - 设计模式概述 - 《设计模式 Java版本》 - 书栈网 · BookStack

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

迪米特法则

迪米特法则-腾讯云开发者社区-腾讯云 (tencent.com)

迪米特法则,也称为最少知识原则(Law of Demeter),是面向对象设计中的一个原则,旨在降低对象之间的耦合性,提高系统的可维护性和可扩展性。该原则强调一个类不应该直接与其它不相关的类相互交互,而是通过少数几个密切相关的类来进行通信。这有助于减少类之间的依赖关系,降低代码的耦合性,使得系统更加灵活和易于维护。

迪米特法则的核心思想可以概括为以下几点:

  1. 一个对象应该尽量少地了解其他对象的内部结构和实现。
  2. 一个对象只与其直接朋友(即与其关联最密切的对象)进行交互。
  3. 避免在一个类中引入不必要的依赖关系,尽量保持类之间的解耦。

策略模式

侧重行为,不同方法解决不同问题,if-else

组成:

  • 行为接口
  • 实现具体的行为类
  • 定义行为发生的类,该类用于注入并执行行为

第一步:定义行为接口

1
2
3
public interface QuackBehavior {
void quack();
}

第二步:实现具体的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("Quack");
}
}

public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("Squeak");
}
}

public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("<< Silence >>");
}
}

第三步:定义鸭子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Duck {
private QuackBehavior quackBehavior;

public Duck(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}

public void performQuack() {
quackBehavior.quack();
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}

第四步:创建测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DuckTestDrive {
public static void main(String[] args) {
Duck duck1 = new Duck(new Quack());
Duck duck2 = new Duck(new Squeak());
Duck duck3 = new Duck(new MuteQuack());

System.out.println("Duck 1:");
duck1.performQuack();

System.out.println("\nDuck 2:");
duck2.performQuack();

System.out.println("\nDuck 3:");
duck3.performQuack();

// 改变鸭子1的行为
System.out.println("\nChanging behavior for Duck 1:");
duck1.setQuackBehavior(new Squeak());
duck1.performQuack();
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
Duck 1:
Quack

Duck 2:
Squeak

Duck 3:
<< Silence >>

Changing behavior for Duck 1:
Squeak

适配器模式

多个方法解决同一个问题

  • 目标接口(target):需要适配的标准接口。
  • 源对象(source):需要被适配的不兼容对象。
  • 适配器对象(adapter):充当中间转换角色,该对象将源对象转换成目标接口。

模板方法

使用场景

当你有个通用的流程需要多个类共享,但某些步骤需要根据具体情况进行定义,可以考虑模板方法设计模式

与策略模式的差异

  • 模板方法:侧重流程(框架)的共用,具体细节的替换
  • 策略模式:侧重行为的多样性,关注算法的可替换性

样例演示

需要处理不同类型的数据文件(如文本文件、图片文件等)。我们可以定义一个抽象的基类 FileProcessor,该类包含一个模板方法 processFile,该方法定义了处理文件的基本步骤。具体的文件处理逻辑由子类来实现。

首先,我们定义一个抽象的基类 FileProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class FileProcessor {
protected String fileName;

public FileProcessor(String fileName) {
this.fileName = fileName;
}

// 模板方法,定义了处理文件的基本步骤
public final void processFile() {
loadFile();
transformData();
saveData();
}

// 具体的加载文件逻辑由子类实现
protected abstract void loadFile();

// 具体的数据转换逻辑由子类实现
protected abstract void transformData();

// 具体的数据保存逻辑由子类实现
protected abstract void saveData();
}

然后,我们定义两个具体的子类来处理不同的文件类型:

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
public class TextFileProcessor extends FileProcessor {

public TextFileProcessor(String fileName) {
super(fileName);
}

@Override
protected void loadFile() {
System.out.println("Loading text file: " + fileName);
// 实现加载文本文件的逻辑
}

@Override
protected void transformData() {
System.out.println("Transforming text data");
// 实现转换文本数据的逻辑
}

@Override
protected void saveData() {
System.out.println("Saving transformed text data");
// 实现保存转换后的文本数据的逻辑
}
}

public class ImageFileProcessor extends FileProcessor {

public ImageFileProcessor(String fileName) {
super(fileName);
}

@Override
protected void loadFile() {
System.out.println("Loading image file: " + fileName);
// 实现加载图像文件的逻辑
}

@Override
protected void transformData() {
System.out.println("Transforming image data");
// 实现转换图像数据的逻辑
}

@Override
protected void saveData() {
System.out.println("Saving transformed image data");
// 实现保存转换后的图像数据的逻辑
}
}

最后,我们可以在主函数中创建不同的处理器对象并调用 processFile 方法:

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
// 创建文本文件处理器对象
FileProcessor textProcessor = new TextFileProcessor("example.txt");
textProcessor.processFile();

// 创建图像文件处理器对象
FileProcessor imageProcessor = new ImageFileProcessor("example.jpg");
imageProcessor.processFile();
}
}

在这个例子中,FileProcessor 类定义了一个模板方法 processFile,它包含了三个步骤:loadFiletransformDatasaveData。这些步骤的具体实现由子类 TextFileProcessorImageFileProcessor 提供。通过这种方式,我们能够保证处理文件的整体流程一致,同时允许不同的子类实现具体的细节。

代理模式

强调通过代理对象来对对象控制,中心不在于装饰

装饰器模式

强调动态的添加新的功能,与代理模式很相似,其实是借鉴了代理模式的思路,

代理模式的与其的不同在于:被代理对象的注入可以通过类动态加载来实现,而不只是注入对象

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地给对象添加一些额外的职责,即在不需要通过子类的情况下扩展其功能。这种模式经常被用作替代继承的一个方法,提供比继承更加灵活的替代方案

下面是一个简单的Java示例来展示装饰器模式的应用:

假设我们有一个Beverage接口,定义了饮料的基本行为,例如计算价格。

1
2
3
4
public interface Beverage {
String getDescription();
double cost();
}

然后我们有一些实现了Beverage接口的具体类,比如HouseBlend

1
2
3
4
5
6
7
8
9
10
11
public class HouseBlend implements Beverage {
@Override
public String getDescription() {
return "House Blend Coffee";
}

@Override
public double cost() {
return 0.89;
}
}

接下来,我们创建一个抽象的装饰器类,它也实现了Beverage接口,并且包含了一个对Beverage类型的引用。这个装饰器可以用来包装任何实现了Beverage接口的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class CondimentDecorator implements Beverage {
private Beverage beverage;

public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}

@Override
public String getDescription() {
return beverage.getDescription();
}

@Override
public double cost() {
return beverage.cost();
}
}

现在我们可以创建具体的装饰器类来为饮料添加不同的特性,比如加糖浆的装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Syrup extends CondimentDecorator {
public Syrup(Beverage beverage) {
super(beverage);
}

@Override
public String getDescription() {
return beverage.getDescription() + ", Syrup";
}

@Override
public double cost() {
return 0.15 + beverage.cost();
}
}

最后,我们可以在客户端代码中使用这些类:

1
2
3
4
5
6
7
8
9
10
public class TestDecorator {
public static void main(String[] args) {
Beverage beverage = new HouseBlend();
System.out.println(beverage.getDescription() + " $" + beverage.cost());

// 添加装饰器
beverage = new Syrup(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
}
}

在这个例子中,Syrup装饰器可以被用来给任何实现了Beverage接口的对象添加糖浆,而不需要修改原始的Beverage类。这样就提供了很大的灵活性,可以方便地组合不同的装饰器来得到所需的最终对象。

备忘录模式

备忘录模式(Memento Pattern)是一种行为设计模式,它允许你在不破坏封装性的前提下捕获一个对象的内部状态,并在稍后恢复该对象之前的状态。这种模式常用于实现撤销(Undo)功能,或者当需要保存和恢复对象的状态时。

  • 文章
  • 文章复制类
  • 备忘录管理类
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
// 备忘录类
class EditorMemento {
private final String content;

public EditorMemento(String content) {
this.content = content;
}

public String getContent() {
return content;
}
}

// 发起人类
class Editor {
private String content;

public EditorMemento save() {
return new EditorMemento(this.content);
}

public void restore(EditorMemento memento) {
this.content = memento.getContent();
}

public void setContent(String content) {
this.content = content;
}

public String getContent() {
return this.content;
}
}

// 管理者类
class EditorHistory {
private List<EditorMemento> states = new ArrayList<>();

public void push(EditorMemento state) {
states.add(state);
}

public EditorMemento pop() {
return states.remove(states.size() - 1);
}
}

备忘当前实体:

1
2
3
Editor editor = new Editor();
EditorMemento editorMemento = editor.save();
new EditorHistory().push(editorMemento);

桥接模式

它将抽象部分与它的实现部分分离,使得它们都可以独立地变化。这种模式可以用来动态地给一个对象添加功能,而不需要通过继承来实现

TIP:一个操作对应多个实现,但这个多个实现也可能依赖于多个实现

场景:假设我们需要创建一个图形库,支持不同类型的图形(如圆形、矩形等),并且能够在不同的平台上(如Windows、Linux等)绘制这些图形。这里我们就可以使用桥接模式来解决这个问题。

画图为一个操作、不同形状对应不同的实现

shape:

1
2
3
4
5
6
7
public abstract class Shape {
protected GraphicsAPI api; // 用于适配多个平台的对象,真正在做业务的API
public Shape(GraphicsAPI api) {
this.api = api;
}
public abstract void draw();
}

shape的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Circle extends Shape {
public Circle(GraphicsAPI api) {
super(api);
}

@Override
public void draw() {
api.draw(this);
}
}

public class Rectangle extends Shape {
public Rectangle(GraphicsAPI api) {
super(api);
}

@Override
public void draw() {
api.draw(this);
}
}

多平台适配总接口:

1
2
3
public interface GraphicsAPI {
void draw(Shape shape);
}

多平台适配实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class WindowsAPI implements GraphicsAPI {
@Override
public void draw(Shape shape) {
System.out.println("Drawing " + shape.getClass().getSimpleName() + " using Windows API");
}
}

public class LinuxAPI implements GraphicsAPI {
@Override
public void draw(Shape shape) {
System.out.println("Drawing " + shape.getClass().getSimpleName() + " using Linux API");
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
GraphicsAPI winApi = new WindowsAPI();
GraphicsAPI linuxApi = new LinuxAPI();

Shape circleWin = new Circle(winApi);
Shape rectLinux = new Rectangle(linuxApi);

circleWin.draw(); // 应该输出 Drawing Circle using Windows API
rectLinux.draw(); // 应该输出 Drawing Rectangle using Linux API
}
}

状态模式

它允许对象在其内部状态改变时改变其行为。对象看起来似乎修改了它的类。

用途:

  • 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  • 控制大量条件语句,当这些条件判断依赖于对象的状态时。
1
假设我们有一个自动售货机,它有三种状态:`等待投币`(WaitingForCoin)、`已经投币`(CoinInserted)、`商品被选中`(ProductSelected)。售货机可以根据用户的操作改变状态,并相应地执行不同的行为。

image-20240924152009612

观察者模式

观察者模式属于行为型模式。在程序设计中,观察者模式通常由两个对象组成:观察者和被观察者。当被观察者状态发生改变时,它会通知所有的观察者对象,使他们能够及时做出响应,所以也被称作“发布-订阅模式”

结构:

  • 抽象被观察者:Newspaper
  • 具体被观察者:NewspaperImpl
  • 抽象观察者:Subscriber
  • 具体观察者:SubscriberImpl