大话设计模式

大话设计模式

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

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

迪米特法则

21c9f5b76c8e285f81f927924682b381

https://www.bilibili.com/video/BV1Ge41157dY/?spm_id_from=333.337.search-card.all.click&vd_source=70a5c913e74574ad96afc2ae210ba3e0

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

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

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

image-20250313151754091


image-20250313151529363

策略模式

侧重行为,不同方法解决不同问题,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):需要适配的标准接口。
  • 需要适配的类(adaptee):需要被适配的不兼容对象。
  • 适配器对象(adapter):充当中间转换角色,该对象将源对象转换成目标接口。
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
// 目标接口(Target Interface)
interface Target {
void request();
}

// 需要适配的类(Adaptee)
class Adaptee {
void specificRequest() {
System.out.println("Adaptee's specific request");
}
}

// 适配器类(Adapter),通过继承Adaptee并实现Target接口
class Adapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest(); // 调用Adaptee的方法
}
}

// 客户端代码
public class Client {
public static void main(String[] args) {
Target target = new Adapter();
target.request(); // 输出: Adaptee's specific request
}
}

模板方法

使用场景

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

与策略模式的差异

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

样例演示

需要处理不同类型的数据文件(如文本文件、图片文件等)。我们可以定义一个抽象的基类 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

观察者模式

发布订阅模式

观察者模式在Java中有广泛的应用,尤其是在事件处理中。以下是一个简单的Java示例来说明观察者模式:

首先,我们定义一个Subject接口,代表被观察的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;

public interface Subject {
// 注册观察者
void registerObserver(Observer o);

// 移除观察者
void removeObserver(Observer o);

// 通知所有观察者
void notifyObservers();

// 被观察对象的状态变化
void setState(String state);

// 获取被观察对象的状态
String getState();
}

接下来,我们实现Subject接口的RealSubject类:

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
import java.util.ArrayList;
import java.util.List;

public class RealSubject implements Subject {
private List<Observer> observers;
private String state;

public RealSubject() {
observers = new ArrayList<>();
}

@Override
public void registerObserver(Observer o) {
observers.add(o);
}

@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}

@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this);
}
}

@Override
public void setState(String state) {
this.state = state;
notifyObservers(); // 当状态改变时,通知所有观察者
}

@Override
public String getState() {
return state;
}
}

然后,我们定义Observer接口,代表观察者:

1
2
3
4
public interface Observer {
// 更新方法,当被观察对象状态改变时调用
void update(Subject subject);
}

接着,我们实现Observer接口的BinaryObserver类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BinaryObserver implements Observer {
@Override
public void update(Subject subject) {
System.out.println("BinaryObserver: State is " + subject.getState());
}
}

public class HexObserver implements Observer {
@Override
public void update(Subject subject) {
System.out.println("HexObserver: State is " + subject.getState());
}
}

最后,我们编写测试代码来演示观察者模式的工作过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ObserverPatternDemo {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Observer binaryObserver = new BinaryObserver();
Observer hexObserver = new HexObserver();

// 注册观察者
realSubject.registerObserver(binaryObserver);
realSubject.registerObserver(hexObserver);

// 改变状态并触发通知
realSubject.setState("1101");
realSubject.setState("1010");

// 移除一个观察者
realSubject.removeObserver(hexObserver);

// 再次改变状态,这次不会通知已移除的观察者
realSubject.setState("1111");
}
}

在上面的代码中,RealSubject是被观察的对象,BinaryObserverHexObserver(这里未给出实现)是观察者。当RealSubject的状态改变时,它会调用notifyObservers()方法来通知所有注册的观察者。每个观察者通过实现update()方法来响应状态的改变。

在实际应用中,观察者模式可以用于多种场景,比如GUI编程中的事件监听、数据库连接的监听、用户界面的变化监听等。它使得对象之间的依赖关系解耦,降低了系统的耦合度,提高了代码的可维护性和可扩展性。

责任链设计模式

适用于流水线任务,对每一个环节进行类的定义,顺序执行,可以保障流程扩展性

组成

  • Handler(环节类):包含执行当前环节任务的方法,以及下一个环节的指针
  • Factory(工厂类):用于创建环节链表关系,从第一个环节开始执行

image-20241007205727423

代码示例:

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
54
55
56
57
58
59
60
61
62
63
64
// 定义处理者接口
interface Handler {
void setNext(Handler handler);
Object handle(Object request);
}

// 抽象处理者类,实现Handler接口,并定义next处理者
abstract class AbstractHandler implements Handler {
private Handler next;

@Override
public void setNext(Handler handler) {
this.next = handler;
}

@Override
public Object handle(Object request) {
if (this.next != null) {
return this.next.handle(request);
}
return null;
}
}

// 具体处理者类
class ConcreteHandler1 extends AbstractHandler {
@Override
public Object handle(Object request) {
if ((Integer) request < 10) {
System.out.println("Request handled in ConcreteHandler1");
} else {
super.handle(request);
}
return request;
}
}

class ConcreteHandler2 extends AbstractHandler {
@Override
public Object handle(Object request) {
if ((Integer) request >= 10 && (Integer) request < 20) {
System.out.println("Request handled in ConcreteHandler2");
} else {
super.handle(request);
}
return request;
}
}

public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
// 创建处理者
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();

// 设置责任链
handler1.setNext(handler2);

// 发送请求
for (int i = 0; i <= 25; i += 5) {
handler1.handle(i);
}
}
}

在这个例子中,ConcreteHandler1ConcreteHandler2 分别处理不同范围的请求值。当一个请求进入链后,每个处理者检查是否满足它的条件,如果满足则处理请求,否则将请求传给下一个处理者。这样就形成了一个处理请求的责任链。

注意:在实际应用中,需要确保责任链的末尾处理者能够妥善处理无法识别的请求,以避免无限递归的问题。在上面的例子中,如果没有处理者接受请求,则默认返回null