设计模式学习

设计模式大致分为三类。

  • 创建者模式:提供一种创建对象的方式,同时隐藏了创建对象的逻辑。而不是直接使用new创建对象。
  • 结构型模式:结构型模式更加关注对象与对象之间的关系与组合,旨在构建灵活可复用的类和对象结构。
  • 行为型模式:关注类或对象之间的通信、协作、职责分配。旨在对象间的责任分配和算法封装。

记住所有的设计模式是愚蠢的,关注自己所在领域常用设计模式,语言框架中默认使用的设计模式。

创建型模式

创建者模式封装了创建对象的逻辑,将复杂对象的构建过程与其表示(使用)分离。代替手动用new操作符调用构造函数繁琐的过程。

优点:对象构造与表示分离,隐藏对象的内部结构。

工厂模式

工厂模式提供了一种将对象的实例化过程封装在工厂类中的方式。

分类:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

缺点:

  • 增加了系统中的类和对象的个数,复杂度增加。

  • 需要额外工作量创建和维护工厂类和产品类,增加开发成本。

应用场景:

  • 日志配置器:日志记录层次、记录格式、记录的存放路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ShapeFactory {
public Shape getShape(String shapeType){ //使用 getShape 方法获取形状类型的对象
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}

自己的应用:

深度学习中,经常需要对比不同学习模型的在相同任务上的效果。而不同的模型的创建逻辑大体相同,但部分细节不同。我自己写了一个模型工厂,根据传入的模型名称、模型层数、模块机制来自动构建模型。这样,每当我要改变一个模型训练,我就修改相应参数。

其实,这么说,整个深度学习任务的过程都可以看成工厂模式,只不过我没有做整个大任务的抽象封装。在Python代码中,我调用了一个argparse这么一个模块,它的作用就是解析命令行的参数。我训练一个任务,大体有数据集、模型、学习率、迭代次数这些个参数,每一次任务可以用参数组合表示。那么就是说我用参数组合来定义任务,这与工厂模式的思想是不谋而合的,我把要改变的地方全部提取出来,放到一起。

单例模式

单例模式也就说保证一个类只有一个实例存在,整个系统中只有一个全局对象。

应用场景:当系统只需要一个实例来协调整个系统的行为时

  • 日志记录器
  • 配置管理器
  • windos中的任务管理器

单线程下实现方式:

  • 私有化构造函数,使得构造函数无法通过外部调用;
  • 创建一个持有字段hold,类型为对象的引用;
  • 创建一个静态方法get,用于获得对象实例的引用。首先检查hold是否为空,为空才创建对象,否则返回引用。

多线程下实现注意点:

  • 线程安全性,get方法得加锁
  • 双重检查:在单例模式中,通常需要进行双重检查锁定,即先检查单例对象是否已经被创建,然后再加锁并再次检查。
  • 饿汉式和懒汉式的选择:饿汉式在类加载时就完成了初始化,而懒汉式则在第一次调用getInstance方法时才进行初始化。

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {  
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) { // 进入前检测
synchronized (Singleton.class) { // synchronized加锁
if (instance == null) { //进入后检测
instance = new Singleton();
}
}
}
return instance;
}
}

饿汉模式

1
2
3
4
5
6
7
8
9
public class Singleton {  
private static Singleton instance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}

建造者模式

建造者模式将一个复杂对象分解成多个简单部分,对复杂对象分模块构建,一步步构建最终对象。

一般适合创建实例有多个步骤的复杂对象,比如说汽车有各个零件,每种零件的厂商是不固定的,但汽车的组装步骤是一定的。再提炼来说:一个复杂对象由各个部分的子对象组成,子对象可能会经常变化,而各个子对象的组合逻辑却不怎么变化。

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
public class Computer {
private final String cpu;//必须
private final String ram;//必须
private final int usbCount;//可选
private final String keyboard;//可选
private final String display;//可选

private Computer(Builder builder){
this.cpu=builder.cpu;
this.ram=builder.ram;
this.usbCount=builder.usbCount;
this.keyboard=builder.keyboard;
this.display=builder.display;
}
public static class Builder{
private String cpu;//必须
private String ram;//必须
private int usbCount;//可选
private String keyboard;//可选
private String display;//可选

public Builder(String cup,String ram){
this.cpu=cup;
this.ram=ram;
}

public Builder setUsbCount(int usbCount) {
this.usbCount = usbCount;
return this;
}
public Builder setKeyboard(String keyboard) {
this.keyboard = keyboard;
return this;
}
public Builder setDisplay(String display) {
this.display = display;
return this;
}
public Computer build(){
return new Computer(this);
}
}
//省略getter方法
}

---
// 使用方式
Computer computer=new Computer.Builder("因特尔","三星")
.setDisplay("三星24寸")
.setKeyboard("罗技")
.setUsbCount(2)
.build();

结构型模式

类与类之间的关系与组合,关注的是类间的布局。

装饰器模式

装饰器模式允许向一个对象添加新的功能,同时却不改变其原有代码。

应用:

  • Java中的注解
  • python里装饰器
  • Spring框架的注解AOP

Java中可以自定义注解,然后通过反射的方式获取注解(注解拦截),然后进行功能增强。

适配器模式

适配器模式允许你将不兼容的对象包装成一个适配器类,使它们能够与其他对象一起工作。适配器模式通常用于处理与现有类库或框架不兼容的类或接口。

在STL里,栈和队列都是适配器,它们底层使用Deque作为容器存储,对外却表现为栈或队列的特性。

代理模式

unix系统调用中,错误包装函数。

我们需要从概念上了解代理和装饰的区别:

  • 代理是全权代理,目标根本不对外,全部由代理类来完成。
  • 装饰是增强,是辅助,目标仍然可以自行对外提供服务,装饰器只起增强作用。

行为型模式

行为型模式关注的是对象间的通信、写作、职责分配等。

责任链模式

将处理对象连成一条链,请求沿着链传递,直到有一个对象处理为止。

状态模式

对象的行为跟随着状态而改变。【状态机】

迭代器模式

提供一个方法顺序访问对象的各个元素,但不暴露对象的内部表示。

CPP的STL库里的迭代器就很好体现这一点。原生指针不足以支持元素的顺序访问,譬如链表节点、树的节点。迭代器的出现使得容器和算法分离,算法通过迭代器访问容器元素却不用知晓其实现,换句话说,算法更加通用。

作者

Desirer

发布于

2023-12-01

更新于

2024-11-15

许可协议