概念

1、共有23 种设计模式,归为创建型、结构型、行为型,设计模式是指软件设计问题的推荐方案

5 个具体的创建型模式,它们分别是:

工厂方法模式【Factory Method】、抽象工厂模式【Abstract Factory】、创建者模式【Builder】

原型模式【Prototype】、单例模式【Singleton】

7 个结构型模式可供研究,它们分别是:

外观模式【Facade】、适配器模式【Adapter】、代理模式【Proxy】、装饰模式【Decorator】

桥接模式【Bridge】、组合模式【Composite】、享元模式【Flyweight】

11 个具体的行为型模式,它们分别是:

模板方法模式【Template Method】、观察者模式【Observer】、状态模式【State】、策略模式【Strategy】

职责链模式【Chain of Responsibility】、命令模式【Command】、访问者模式【Visitor】、

调停者模式【Mediator】、备忘录模式【Memento】、迭代器模式【Iterator】、解释器模式【Interpreter】

六大原则

1、单一原则(Single Responsibility Principle)

一个类只负责一项职责,尽量做到类只有一个行为原因引起变化;

业务对象(BO business object)、业务逻辑(BL business logic)拆分

2.里氏替换原则(LSP liskov substitution principle)

子类可以扩展父类的功能,但不能改变原有父类的功能;

目的:增强程序的健壮性。实际项目中,每个子类对应不同的业务含义,使父类作为参数,传递不同的子类完成不同的业务逻辑。

3.依赖倒置原则(dependence inversion principle)

面向接口编程;(通过接口作为参数实现应用场景)

依赖于抽象而不依赖于具体。

抽象就是接口或者抽象类,细节就是实现类

依赖倒置原则定义:

上层模块不应该依赖下层模块,两者应依赖其抽象抽象不应该依赖细节细节应该依赖抽象接口负责定义 public 属性和方法,并且申明与其他对象依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑。

4.接口隔离(interface segregation principle)

建立单一接口;(扩展为类也是一种接口,一切皆接口)。

使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度。

降低依赖,降低耦合。

定义:

客户端不应该依赖它不需要的接口;类之间依赖关系应该建立在最小的接口上;

5.迪米特原则(law of demeter LOD)

最少知道原则,尽量降低类与类之间的耦合;

一个对象应该对其他对象有最少的了解,即一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6.开闭原则(open closed principle)

用抽象构建架构,用实现扩展原则;

开闭原则就是说对扩展开放,对修改关闭

一个软件实体通过扩展来实现变化,而不是通过修改原来的代码来实现变化。实现一个热插拔的效果。

开闭原则是对软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。

创建型模式

简单工厂模式

在讲这个之前,先说说我们常用的简单工厂模式,这个不算设计模式,只能算编程习惯

首先我们定义一个工厂类,通过传入不同的参数返回不同的实例,被创建的实例具有共同的父类或接口

适用场景

创建的对象较少、客户端不关心对象的创建过程

现在有个任务、要求做个绘图工具、可以画长方形、圆形、正方形、每个图形都要 draw()方法

面向对象的思想,都属于图形,并且都有draw方法,可以去定义一个接口或抽象类作为这3的公共父类,并在其中声明公共方法draw,然后每个图形去实现这个方法

主要角色:

抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。

具体产品(ConcreteProduct):具体产品类,是简单工厂模式的创建目标。

1
2
3
4
5
public interface Shape {

void draw();

}
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
public class Rectangle implements Shape{

@Override

public void draw() {

System.out.println("长方形...");

}

}

public class Round implements Shape{

@Override

public void draw() {

System.out.println("圆形...");

}

}

public class Square implements Shape{

@Override

public void draw() {

System.out.println("正方形...");

}

}

枚举类、防止输错

1
2
3
4
5
6
7
8
9
public enum ShapeType {

ROUND,

RECTANGLE,

SQUARE;

}

创建工厂类,提供一个静态方法,客户端只需要传入产品的类型,就会实例化相应的产品。

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
public class ShapeFactory {

//方法的参数改为我们定义的枚举类型

public static Shape getShape(ShapeType shapeType){

Shape shape = null;

if ("round".equalsIgnoreCase(shapeType.name())){

shape = new Round();

}

if ("rectangle".equalsIgnoreCase(shapeType.name())){

shape = new Rectangle();

}

if ("square".equalsIgnoreCase(shapeType.name())){

shape = new Square();

}

return shape;

}

}

调用

1
2
3
4
5
6
7
8
9
10
11
Shape round = ShapeFactory.getShape(ShapeType.ROUND);

round.draw();

Shape rectangle = ShapeFactory.getShape(ShapeType.RECTANGLE);

rectangle.draw();

Shape square = ShapeFactory.getShape(ShapeType.SQUARE);

square.draw();

上面简单工厂模式 demo 违背了开闭原则,如果我需要再来一个五边形呢,是不是要去工厂类里的方法继续加入这个图形的判断,所以违背,不该修改工厂类的源码

而工厂方法模式则是简单工厂模式的进一步深化,其不像简单工厂模式通过一个工厂来完成所有对象的创建,而是通过不同的工厂来创建不同的对象,每个对象有对应的工厂创建。

客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。

使用了 静态(static) 工厂方法,造成工厂角色无法形成基于继承的等级结构

工厂模式

工厂方法模式是简单工厂的进一步抽象和推广,由于工厂方法模式具有多态性,工厂方法模式既保持了简单工厂的优点,同时又克服了它的缺点,简单工厂模式中的工厂类负责创建实例,那么如果有新的类型添加,就要修改工厂类,这样也不利于进一步松耦合。工厂方法模式中核心工厂类不负责所有实例的创建,而是将具体的创建工作交给了子类去完成,这个核心类成为了一个抽象工厂角色。

主要角色:

1、抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
2、具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
3、抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
4、具体工厂(ConcreteFactory):主要是实现抽象工厂中的接口,完成具体产品的创建。

现在要求设计一个图片加载器、具有多个图片加载器用来加载 jpg、png、gif 格式的图片、每个加载器有 read 方法、用来读取图片

定义公共接口·

1
2
3
4
5
6
7
8
9
10
11
12
public interface Reader {

void read();

}
//或者创建抽象工厂类,上面就接口也可以
/**
* 创建抽象工厂类,提供了工厂的生成方法
*/
public interface AbstractFactory {
public Product newProduct();//这种还需要创建抽象产品类Product,具体产品要继承它
}

实现类

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
public class JPGReader implements Reader{

@Override

public void read() {

System.out.println("read jpg...");

}

}

public class PngReader implements Reader{

@Override

public void read() {

System.out.println("read png...");

}

}

public class GIFReader implements Reader{

@Override

public void read() {

System.out.println("read gif...");

}

}

定义一个抽象的工厂接口 ReaderFactory

1
2
3
4
5
public interface ReaderFactory {

public Reader getReader();

}

getReader()方法返回我们的 Reader 类,接下来我们把上面定义好的每个图片加载器都提供一个工厂类,这些工厂类实现了 ReaderFactory

1
2
3
4
5
6
7
8
9
10
11
public class JPGFactory implements ReaderFactory{  //具体产品1

@Override

public Reader getReader() {

return new JPGReader();

}

}
1
2
3
4
5
6
7
8
9
10
11
public class PNGFactory implements ReaderFactory{  //具体产品2

@Override

public Reader getReader() {

return new PngReader();

}

}

……

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//读取JPG

ReaderFactory jpgFactory = new JPGFactory();

Reader jpgReader = jpgFactory.getReader();

jpgReader.read();

//读取PNG

ReaderFactory pngFactory = new PNGFactory();

Reader pngReader = pngFactory.getReader();

pngReader.read();

分别读取了不同格式的图片,不同之处在于针对不同的图片格式声明了不同的工厂,进而创建了相应的图片加载器

和简单工厂对比一下,最根本的区别在于简单工厂只有一个统一的工厂类,而工厂方法是针对每个要创建的对象都会提供一个工厂类,这些工厂类都实现了一个工厂基类。

简单来说就是给你一个接口,你去实现它,并重写成自己需要的。灵活性增强,对于新产品的创建,只需多写一个相应的工厂类,典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类

缺点就是类的个数容易过多、增加复杂度、抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决

应用场景:

  • 客户端不需要知道它所创建的对象的类。例子中我们不知道每个图片加载器具体叫什么名,只知道创建它的工厂名就完成了创建过程。

  • 客户端可以通过子类来指定创建对应的对象。 以上场景使用于采用工厂方法模式。

抽象工厂模式

抽象工厂模式是工厂方法的进一步深化,在这个模式中的工厂类不单单可以创建一个对象,而是可以创建一组对象。这是和工厂方法最大的不同点

抽象工厂和工厂方法一样可以划分为 4 大部分:

  • AbstractFactory(抽象工厂)工厂方法模式的核心,与应用程序无关。任何在模式中创建对象的工厂类必须实现这个接口。
  • ConcreteFactory(具体工厂):这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用就可以创建某一种产品对象。
  • AbstractProduct(抽象产品):工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
  • ConcreteProduct(具体产品):抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例。在抽象工厂中创建的产品属于同一产品族,这不同于工厂模式中的工厂只创建单一产品。

例子:现在需要做一款跨平台的游戏,需要兼容 Android,IOS,Windows PC 三个移动操作系统,该游戏针对每个系统都设计了一套操作控制器(OperationController)和界面控制器(UIController),下面通过抽象工厂方式完成这款游戏的架构设计。

定义公共接口 OperationController 和 UIController –> 抽象产品

1
2
3
4
5
6
7
8
9
10
11
public interface OperationController {

void control();

}

public interface UIController {

void display();

}

Android

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AndroidOperationController implements OperationController {

@Override

public void control() {

System.out.println("Android OperationController System");

}

}

public class AndroidUIController implements UIController {

@Override

public void display() {

System.out.println("Android UIController System");

}

}

IOS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class IOSOperationController implements OperationController {

@Override

public void control() {

System.out.println("IOS OperationController System");

}

}

public class IOSUIController implements UIController {

@Override

public void display() {

System.out.println("IOS UIController System");

}

}

WP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WpOperationController implements OperationController {

@Override

public void control() {

System.out.println("Wp OperationController System");

}

}

public class WpUIController implements UIController {

@Override

public void display() {

System.out.println("Wp UIController System");

}

}

然后我们在定义一个抽象工厂类,这个工厂可以生产 OperationController 和 UIController –> 抽象工厂

1
2
3
4
5
6
7
public interface SystemFactory {

public OperationController createOperationController();

public UIController createUIController();

}

接着我们再来写各个平台的具体工厂实现 –> 具体工厂

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
65
Android

public class AndroidFactory implements SystemFactory{

@Override

public OperationController createOperationController() {

return new AndroidOperationController();

}

@Override

public UIController createUIController() {

return new AndroidUIController();

}

}

IOS

public class IOSFactory implements SystemFactory{

@Override

public OperationController createOperationController() {

return new IOSOperationController();

}

@Override

public UIController createUIController() {

return new IOSUIController();

}

}

WP

public class WPFactory implements SystemFactory{

@Override

public OperationController createOperationController() {

return new WpOperationController();

}

@Override

public UIController createUIController() {

return new WpUIController();

}

}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
//android操作系统使用示例

SystemFactory androidFactory = new AndroidFactory();

OperationController operationController = androidFactory.createOperationController();

UIController uiController = androidFactory.createUIController();

operationController.control();

uiController.display();

//其他操作系统使用方式一样

抽象工厂模式中,存在多个相关的产品族,每个产品族都有对应的具体产品类。而工厂方法模式则针对单个产品族,每个具体产品类有对应的工厂类来创建。

以下是抽象工厂模式的示例代码,用于创建多个产品族(形状和颜色)的对象:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 形状接口
interface Shape {
void draw();
}

// 颜色接口
interface Color {
void fill();
}

// 具体形状:圆形
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}

// 具体形状:长方形
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}

// 具体颜色:红色
class Red implements Color {
@Override
public void fill() {
System.out.println("Filling with red color");
}
}

// 具体颜色:蓝色
class Blue implements Color {
@Override
public void fill() {
System.out.println("Filling with blue color");
}
}

// 抽象工厂接口
interface AbstractFactory {
Shape createShape();
Color createColor();
}

// 具体工厂:形状工厂
class ShapeFactory implements AbstractFactory {
@Override
public Shape createShape() {
return new Circle(); // 返回圆形对象
}

@Override
public Color createColor() {
return null; // 不负责颜色的创建,返回 null
}
}

// 具体工厂:颜色工厂
class ColorFactory implements AbstractFactory {
@Override
public Shape createShape() {
return null; // 不负责形状的创建,返回 null
}

@Override
public Color createColor() {
return new Red(); // 返回红色对象
}
}

// 客户端代码
public class Main {
public static void main(String[] args) {
AbstractFactory shapeFactory = new ShapeFactory();
Shape circle = shapeFactory.createShape();
if (circle != null) {
circle.draw(); // Output: Drawing a circle
}

AbstractFactory colorFactory = new ColorFactory();
Color red = colorFactory.createColor();
if (red != null) {
red.fill(); // Output: Filling with red color
}
}
}

AbstractFactory 是抽象工厂接口,定义了创建形状和颜色对象的方法。ShapeFactoryColorFactory 是具体工厂类,分别用于创建形状和颜色对象。

每个具体工厂类只负责创建对应的产品族,ShapeFactory 创建形状对象,ColorFactory 创建颜色对象。这样,客户端可以通过选择合适的工厂类来获取所需的产品。

1、优点:抽象工厂模式除了具有工厂方法模式的优点外,还有如下几个优点
可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
2、缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。

总结

工厂和抽象工厂的创建步骤:

先创建产品接口含有方法(抽象就多个)——>具体产品去实现它重写它的方法——>创建工厂类(抽象就是含多方法)——>具体工厂(抽象继承 1 个实现多个方法)——->调用 new 工厂—–>获取里面的产品—–>调用产品的方法

原型模式

它允许一个对象创建另一个可定制的对象,而无需知道创建的细节。其工作原理是通过拷贝实现对象创建,即 clone()。原型模式类似于工厂模式,提供了隔离对象与使用者之间的耦合关系,绕开了 new 的过程,但需要这些具体的对象有稳定的接口。

原型模式的应用场景包括:利用拷贝替换构造对象,提升效率;避免了重复 new 相同对象的操作。

原型模式在性能和创建过程方面有优势,但需要解决一些克隆方法和循环引用的问题

浅克隆
将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

深克隆
将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深克隆进行了完全彻底的复制,而浅克隆不彻底。

创建者模式

new Person(“”….)一大堆,mp 里的也用到

单例模式

类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

指一个类只有一个实例,且该类能自行创建这个实例的一种模式

特点:

某个类只能有一个实例(即构造器私有,防止外部通过new XXX()创建对象

自行在类的内部创建对象实例

向外暴露一个静态的公共方法

八种单例模式实现方式:

  • 饿汉式(静态常量)

  • 饿汉式(静态代码块)

  • 懒汉式(线程不安全)

  • 懒汉式(线程安全,同步方法)

  • 懒汉式(线程安全,同步代码块)

  • 双重检查

  • 静态内部类

  • 枚举

饿汉式(静态常量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
//创建实例对象
private final static Singleton instance = new Singleton();

/**
* 构造器私有化,防止外部类能直接new
*/
private Singleton() {}

/**
* 提供一个公有的静态方法,返回实例对象
* @return
*/
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
public class Singleton {
//实例对象
private static Singleton instance;

/**
* 在静态代码块中,创建单例对象
*/
static {
instance = new Singleton();
}

/**
* 构造器私有化,防止外部类能直接new
*/
private Singleton() {}

/**
* 提供一个公有的静态方法,返回实例对象
* @return
*/
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
public class Singleton {
//实例对象
private static Singleton instance;

/**
* 构造器私有化,防止外部类能直接new
*/
private Singleton() {}

/**
* 提供一个公有的静态方法,当使用到该方法时,才会创建instance
* @return
*/
public static Singleton getInstance() {
//若对象为空,则创建
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

起到了懒加载的效果,但是只能在单线程下使用,在实际开发中,不要使用这种方式

懒汉式(线程安全,同步方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class YamlUtils {
private static YamlUtils instance; // 类实例变量
private YamlUtils() {
loadConfig(); // 加载配置信息
}
/**
* 提供一个公有的静态方法,加入同步处理,解决线程安全问题
* @return
*/
public static synchronized YamlUtils getInstance() {
if (instance == null) { // 若实例为空,则创建新实例
instance = new YamlUtils();
}
return instance;
}

private void loadConfig() {
}

}

效率低,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类的实例,直接 return 就行,方法进行同步效率太低

在实际开发中,不推荐使用

懒汉式(线程安全,同步代码块)

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
class Singleton {
//实例对象
private static Singleton instance;

/**
* 构造器私有化,防止外部类能直接new
*/
private Singleton() {}

/**
* 提供一个公有的静态方法
* @return
*/
public static Singleton getInstance() {
//若对象为空,则创建
if (instance == null) {
//同步代码块
synchronized (Singleton.class) {
instance = new Singleton();
}
}
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
public class Singleton {
//实例对象
private static volatile Singleton instance;

/**
* 构造器私有化,防止外部类能直接new
*/
private Singleton() {}

/**
* 提供一个公有的静态方法,加入双重检查处理,解决线程安全问题,
* 同时解决懒加载问题,也保证了效率
* @return
*/
public static Singleton getInstance() {
//实例没创建,才会进入内部的 synchronized代码块
if (instance == null) {
synchronized (Singleton.class) {
//此处再判断一次,可能会出现其他线程创建了实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

1、DoubleCheck 概念是多线程开发中常使用到的,如代码中所示,进行了两次 if (instance == null)检查,这样就保证了线程安全。
2、实例化代码只用执行一次,后面再次访问时,判断 if (instance == null),直接 return 实例化对象,也避免反复进行方法同步。
3、线程安全,延迟加载,效率较高。
4、推荐使用这种单例设计模式。

静态内部类 🧐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private Singleton() {}

/**
* 静态内部类,类中有一个静态属性
*/
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}

public static final Singleton getInstance() {
return SingletonInstance.INSTANCE ;
}
}

1、这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2、静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法时,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
3、类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
4、避免了线程不安全,利用静态内部类特点实现延迟加载,效率高,推荐使用。

枚举实现单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum Singleton{
INSTANCE;
public Singleton getInstance(){
return INSTANCE;
}
}

public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE.getInstance();
Singleton instance2 = Singleton.INSTANCE.getInstance();
System.out.println(instance1 == instance2);
System.out.println("instance1=" + instance1.hashCode());
System.out.println("instance2=" + instance2.hashCode());
}
}

1、借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

推荐使用

总结

1、单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2、当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new。
3、单例模式使用的场景:
需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(如:数据源、session 工厂等)

结构型模式

装饰模式

场景 new buffstream()

代理模式

理解中介,被代理人不需要改变原来的代码就能实现想要的功能

外观模式

将复杂的组件都不管,只要将它的接口啥的放到外观类里面,客户端只负责调用外观

桥接模式

桥接,顾名思义,就是用来连接两个部分,为被分离了的抽象部分和实现部分搭桥

假设我们有一个画笔,可以画正方形、长方形、圆形。现在我们需要给这些形状进行上色,这里有三种颜色:白色、灰色、黑色。这里我们可以画出 3*3=9 种图形:白色正方形、白色长方形、白色圆形。。。。。。

此时我们可以将图形和颜色抽象出来,根据实际需要对颜色和形状进行组合

对于这样的实现方案,我们就可以称之为桥接模式

将抽象和实现解耦,让它们可以独立变化。

JDBC 驱动是桥接模式的经典应用。如果我们想要把 MySQL 数据库切换成 Oracle 数据库,只需要更换数据库驱动,动一行代码就可以实现了:

将 com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver。

组合模式

“组合模式”也被称为“部分整体模式”

组合模式就运用了树形结构,该模式的核心思想是:将多个对象组合成树形结构,以此结构来表示“整体-部分”之间的层次关系。

组合模式的主要优点有:

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  2. 不容易限制容器中的构件;
  3. 不容易用继承的方法来增加构件的新功能;
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class ShoppingTest {
public static void main(String[] args) {
float s = 0;
Bags BigBag, mediumBag, smallRedBag, smallWhiteBag;
Goods sp;
BigBag = new Bags("大袋子");
mediumBag = new Bags("中袋子");
smallRedBag = new Bags("红色小袋子");
smallWhiteBag = new Bags("白色小袋子");
sp = new Goods("婺源特产", 2, 7.9f);
smallRedBag.add(sp);
sp = new Goods("婺源地图", 1, 9.9f);
smallRedBag.add(sp);
sp = new Goods("韶关香菇", 2, 68);
smallWhiteBag.add(sp);
sp = new Goods("韶关红茶", 3, 180);
smallWhiteBag.add(sp);
sp = new Goods("景德镇瓷器", 1, 380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
sp = new Goods("李宁牌运动鞋", 1, 198);
BigBag.add(sp);
BigBag.add(smallWhiteBag);
BigBag.add(mediumBag);
System.out.println("您选购的商品有:");
BigBag.show();
s = BigBag.calculation();
System.out.println("要支付的总价是:" + s + "元");
}
}

//抽象构件:物品
interface Articles {
public float calculation(); //计算

public void show();
}

//树叶构件:商品
class Goods implements Articles {
private String name; //名字
private int quantity; //数量
private float unitPrice; //单价

public Goods(String name, int quantity, float unitPrice) {
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
}

public float calculation() {
return quantity * unitPrice;
}

public void show() {
System.out.println(name + "(数量:" + quantity + ",单价:" + unitPrice + "元)");
}
}

//树枝构件:袋子
class Bags implements Articles {
private String name; //名字
private ArrayList<Articles> bags = new ArrayList<Articles>();

public Bags(String name) {
this.name = name;
}

public void add(Articles c) {
bags.add(c);
}

public void remove(Articles c) {
bags.remove(c);
}

public Articles getChild(int i) {
return bags.get(i);
}

public float calculation() {
float s = 0;
for (Object obj : bags) {
s += ((Articles) obj).calculation();
}
return s;
}

public void show() {
for (Object obj : bags) {
((Articles) obj).show();
}
}
}

在这个例子中,抽象构件为 Articles 接口,其中定义了两个方法:calculation 和 show。Goods 和 Bags 分别实现了 Articles 接口。Goods 是树叶构件,表示商品,包含了商品的名称、数量和单价。Bags 是树枝构件,表示袋子,包括了一个 List 类型的属性 bags,用于存储袋子中的商品或子袋子。

在客户端代码中,创建了 4 个袋子和 5 个商品,并将它们放入不同的袋子之中。最终将货物放入大袋子中,并计算出袋子中所有商品的总价钱.结果为:

您选购的商品有:
李宁牌运动鞋(数量:1,单价:198.0 元)
韶关香菇(数量:2,单价:68.0 元)
韶关红茶(数量:3,单价:180.0 元)
景德镇瓷器(数量:1,单价:380.0 元)
婺源特产(数量:2,单价:7.9 元)
婺源地图(数量:1,单价:9.9 元)
要支付的总价是:1279.7 元

在组合模式中,抽象构件定义了统一的操作接口,文件夹和文件都是具体的构件,它们都实现了这个接口。文件夹是树枝构件,可以包含其他文件夹或者文件作为子节点,形成一个层次结构。文件是树叶构件,表示最底层的叶子节点,不能再包含其他节点。

通过使用组合模式,可以将文件夹和文件统一地看待为构件对象,客户端可以以相同的方式进行操作,无论是访问文件夹还是文件。例如,可以通过递归遍历的方式对整个文件夹结构进行操作,或者针对单个文件进行特定的操作。

此外,还可以使用其他设计模式来增强文件夹和文件的功能。例如,可以使用迭代器模式来遍历文件夹中所有的文件和子文件夹;可以使用装饰器模式来为文件夹和文件添加额外的功能;可以使用访问者模式来对文件夹和文件进行不同的操作等等。

综上所述,组合模式是对文件夹和文件进行设计的一种常用模式,它能够将它们组织成树形结构,并提供统一的操作接口,同时还可以与其他设计模式结合使用,以满足更复杂的需求。

享元模式

旨在通过共享对象来减少内存占用和提高性能

String 类底层就是通过享元模式进行完成的

String s1 = “hello”

此时栈中是 String s1 而 hello 会放入常量池中,当 s2 也等于它的时候,由于 String 内部的缓存机制,有则复用的原则继续用同一内存地址

数据库连接池,线程池以及缓存池 共性,那就是通过共享对象或者说共享内存来解决系统开销问题,充分利用系统的内存与性能,从而达到用最小的系统开销去适应更多的数据响应。

抽象享元类:在享元模式中,存在一个抽象的享元父类,其中一共包含两部分,第一部分为可共享的内部状态,也就是不随着外部而改变的部分;第二部分为不可共享的外部状态,即可能会随时外部环境随时发生改变的部分。

具体享元类:抽象享元类的具体实现,它主要实现了抽象享元类的内部状态,对于外部状态我们需要外部提供,通常在调用该方法时通过参数传递的方式进行动态改变即可;

享元工厂类:主要用于创建享元对象的一个角色,其中聚合一种数据结构,用于起一个缓存以及复用作用。

Mybatis 的一二级缓存,redis 缓存

-128-127,在 Integer 底层,对这一区间的整数是做了缓存的,即有就复用,相应的享元对象,所以就直接返回

1
2
3
4
5
6
7
Integer num1 = 127;
Integer num2 = 127;
Integer num3 = 128;
Integer num4 = 128;

System.out.println(num1 == num2); //true
System.out.println(num3 == num4); //false

正常扑克牌 54 张需要 54 个对象,用了享元模式只要 4 个对象即可

2 黑桃和 3 黑桃都属于黑桃花色的一种,它们只有点数这一项状态不同。因此,1 黑桃和 2 黑桃可以共享同一个黑桃对象,只需要为它们分别动态添加点数(外部状态)即可。

在享元模式中,相同的内部状态只会被创建一次,并在需要使用时进行共享。这样可以大幅减少重复对象的创建,提高了资源利用率,并且可以使得系统更加灵活和可扩展。

扑克牌来说每种牌点的花色是固定的,即黑桃、红桃、梅花以及方块,所以可以把四种花色看作共享状态。那对每种花色而言,会根据当前的牌点范围又是在不断变化的,所以可以将扑克牌的点数看作不共享的外部状态。

比如我们早上去买豆浆,因为每个人的口味不一样,有的人喜欢吃加糖的,有的人喜欢吃加辣的。卖主不可能每种口味都做一锅,第一是浪费精力,其次就是万一第二天某种口味不受欢迎就会造成极大的浪费。所以卖主在出摊前会做好一锅纯豆浆,然后售卖时根据顾客的口味,只需在原有的纯豆浆中加入相应的调料即可。这样一来呢,卖主就不会担心某种豆浆因为不收欢迎而造成浪费,也不会担心豆浆的口味做得太少而造成亏损。相比较做纯豆浆而言,能够充分利用豆浆,卖的时候针对每位顾客的口味不同,只需要调好对应的调料就可以了。

行为模式

模板方法模式

它在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。

定制表格
设计者先将所有需要填写的信息头(字段名)抽取出来,再将它们整合在一起成为一种既定格式的表格,最后让填表人按照这个标准化模板去填写自己特有的信息,而不必为书写内容、先后顺序、格式而感到困扰。

模版方法模式很好体现了这点,作为父类的模板会在需要的时候,调用子类相应的方法,也就是由父类找子类,而不是子类找父类。

这其实也是一种反向的控制结构,按照通常的思路,是子类找父类才对,也就是应该是子类来调用父类的方法,因为父类根本就不知道子类,而子类是知道父类的,但是在模板方法模式里面,是父类来找子类,所以是一种反向的控制结构。

开闭原则:对修改关闭,对扩展开放。首先从设计上,先分离变与不变,然后把不变的部分抽取出来,定义到父类里面,比如算法骨架,比如一些公共的、固定的实现等等。这些不变的部分被封闭起来,尽量不去修改它了,要扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放的。

李氏替换原则:派生类型(子类)必须能够替换掉它们的基类型(父类),运行时子类对象覆盖父类对象。能够实现统一的算法骨架,通过切换不同的具体实现来切换不同的功能,一个根本原因就是里氏替换原则,遵循这个原则,保证所有的子类实现的是同一个算法模板(为了防止子类改变模板方法中的算法,可以将模板方法声明为 final),并能在使用模板的地方,根据需要,切换不同的具体实现。

  • 背景:小成希望学炒菜:手撕包菜 & 蒜蓉炒菜心
  • 冲突:两道菜的炒菜步骤有的重复有的却差异很大,记不住
  • 解决方案:利用代码记录下来

步骤 1: 创建抽象模板结构(Abstract Class):炒菜的步骤

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
public  abstract class Cookie{
//模板方法,用来控制炒菜的流程 (炒菜的流程是一样的-复用)
//声明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序
final void cookProcess(){
//第一步:倒油
this.pourOil();
//第二步:热油
this.HeatOil();
//第三步:倒蔬菜
this.pourVegetable();
//第四步:倒调味料
this.pourSauce();
//第五步:翻炒
this.fry();
}

//定义结构里哪些方法是所有过程都是一样的可复用的,哪些是需要子类进行实现的

//第一步:倒油是一样的,所以直接实现

void pourOil(){
System.out.println("倒油");

}
//第二步:热油是一样的,所以直接实现
void HeatOil(){
System.out.println("热油");

}

//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)

//所以声明为抽象方法,具体由子类实现

abstract void pourVegetable()
//第四步:倒调味料是不一样的(一个下辣椒,一个是下蒜蓉)


//所以声明为抽象方法,具体由子类实现

abstract void pourSauce()
//第五步:翻炒是一样的,所以直接实现
void fry(){
System.out.println("炒啊炒啊炒到熟啊");
}
}

步骤 2: 创建具体模板(Concrete Class),即”手撕包菜“和”蒜蓉炒菜心“的具体

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
//炒手撕包菜的类

public class ConcreteClass_BaoCai extend Abstract Class{

@Override
public void pourVegetable(){

System.out.println(”下锅的蔬菜是包菜“);
}

@Override
public void pourSauce(){
System.out.println(”下锅的酱料是辣椒“);
}
}


//炒蒜蓉菜心的类
public class ConcreteClass_CaiXin extend Abstract Class{

@Override
public void pourVegetable(){
System.out.println(”下锅的蔬菜是菜心“);

}

@Override
public void pourSauce(){
System.out.println(”下锅的酱料是蒜蓉“);
}
}

步骤 3:客户端调用-炒菜了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Template Method{
public static void main(String[] args){

//炒 - 手撕包菜

ConcreteClass_BaoCai BaoCai = new ConcreteClass_BaoCai();

BaoCai.cookProcess();

//炒 - 蒜蓉菜心

ConcreteClass_ CaiXin = new ConcreteClass_CaiXin();

CaiXin.cookProcess();

}
}

结果输出

倒油
热油
下锅的蔬菜是包菜
下锅的酱料是辣椒
炒啊炒啊炒到熟

倒油
热油
下锅的蔬菜是菜心
下锅的酱料是蒜蓉

观察者模式

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

优点:被观察者和观察者对象之间不需要知道对方的具体实现,只需要知道对方的接口,避免了紧耦合的关系。
由于被观察者对象并不关心具体的观察者是谁,所以在程序运行的过程中,可以动态地增加或者删除观察者对象,增加了灵活性。
符合开闭原则,当需要添加新的观察者时,只需要添加一个实现观察者接口的类,而不需要修改被观察者对象的代码。

缺点:当观察者没有被正确移除时,可能会导致内存泄漏的问题。
实现观察者模式,需要定义多个接口和类,增加了程序的复杂度。
在某些情况下,被观察者和观察者对象之间可能出现循环依赖的问题。

场景:

当一个对象的状态发生改变时,需要通知多个对象做出相应的响应。例如,王者荣耀更新前,会通知所有用户要更新的时间。
当很多对象同时对某一个主题感兴趣时,可以采用观察者模式实现发布-订阅模式。例如,生产者发送消息到消息队列中,并通知所有订阅此队列的消费者进行消费。
数据库开发中,当数据库表中的数据发生变化时,需要通知相关的模块进行更新或其他操作。例如,当用户更新了数据库中的某个记录时,就可以通过观察者模式通知所有注册的监听器进行响应。

状态模式

它将一个对象的状态封装成不同的类,使得对象在不同状态下可以表现出不同的行为,从而实现了状态与行为的解耦。

状态模式的核心思想是将各种状态抽象成独立的类,并定义一个公共的状态接口。具体的状态类实现该接口,每个具体的状态类负责处理对象在该状态下的行为。而对象本身会持有一个当前状态的引用,并在运行时根据状态的不同来调用相应的方法。

典型的状态模式包含以下角色:

  • Context(上下文):定义客户端所感兴趣的接口,同时维护一个具体状态类的实例,将请求委托给当前状态对象。
  • State(抽象状态类):定义状态的接口,描述不同状态下对象的行为。
  • ConcreteState(具体状态类):实现状态接口,负责处理对象在该状态下的行为和状态转换。

使用状态模式的好处在于可以使代码更加清晰、可维护、可扩展。当对象的状态较多且状态之间的行为存在复杂的转换逻辑时,状态模式能够将各个状态的处理逻辑分散到各个具体状态类中,降低了代码的复杂度,提高了代码的可读性和可维护性。

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
// 状态接口
interface State {
void handle();
}

// 具体状态类A
class ConcreteStateA implements State {
public void handle() {
System.out.println("当前状态是A");
// 具体的A状态处理逻辑
}
}

// 具体状态类B
class ConcreteStateB implements State {
public void handle() {
System.out.println("当前状态是B");
// 具体的B状态处理逻辑
}
}

// 上下文类
class Context {
private State currentState;

public void setState(State state) {
currentState = state;
}

public void request() {
// 委托给具体的状态对象进行处理
currentState.handle();
}
}

// 使用示例
public class Main {
public static void main(String[] args) {
Context context = new Context();

State stateA = new ConcreteStateA();
context.setState(stateA);
context.request(); // 输出:当前状态是A

State stateB = new ConcreteStateB();
context.setState(stateB);
context.request(); // 输出:当前状态是B
}
}

假设我们有一个电灯的控制器,可以通过按钮进行开关操作。在每个操作中,我们需要判断当前电灯的状态,并根据状态执行相应的逻辑。下面是使用大量条件语句的实现方式:

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
javaCopy Codeenum LightState {
ON,
OFF
}

class LightController {
private LightState currentState;

public void switchButton() {
if (currentState == LightState.ON) {
turnOffLight();
currentState = LightState.OFF;
} else if (currentState == LightState.OFF) {
turnOnLight();
currentState = LightState.ON;
}
}

private void turnOnLight() {
// 打开电灯的逻辑
System.out.println("打开电灯");
}

private void turnOffLight() {
// 关闭电灯的逻辑
System.out.println("关闭电灯");
}
}

上述代码中使用了条件语句来判断电灯的状态,然后根据状态执行相应的操作。但是,随着业务逻辑的复杂度增加以及状态种类的增多,条件语句会变得越来越复杂和臃肿,不易维护和扩展。

下面是使用状态模式进行重构的示例:

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
javaCopy Codeinterface LightState {
void switchButton(LightController controller);
}

class OnState implements LightState {
public void switchButton(LightController controller) {
controller.turnOffLight();
controller.setState(new OffState());
}
}

class OffState implements LightState {
public void switchButton(LightController controller) {
controller.turnOnLight();
controller.setState(new OnState());
}
}

class LightController {
private LightState currentState;

public LightController() {
currentState = new OffState(); // 初始状态为关闭
}

public void switchButton() {
currentState.switchButton(this);
}

public void turnOnLight() {
// 打开电灯的逻辑
System.out.println("打开电灯");
}

public void turnOffLight() {
// 关闭电灯的逻辑
System.out.println("关闭电灯");
}

public void setState(LightState state) {
currentState = state;
}
}

在重构后的代码中,我们将每种状态抽象成一个独立的类,并实现了共同的接口。在LightController中维护了当前状态的引用,而不再依赖于条件语句。每个状态类负责自己的行为,并在需要切换状态时更新控制器的状态。

这样,在新增状态时,只需要创建一个新的状态类并实现相应的逻辑即可,无需修改原有的条件语句,实现了开闭原则和单一职责原则。同时,代码变得更加简洁、可扩展和易于理解。

总结来说,使用状态模式可以将复杂的条件语句进行解耦,提高代码的可维护性和可扩展性,同时使得代码逻辑更加清晰。特别是在状态种类较多、状态之间的转换逻辑复杂时,使用状态模式能够有效地管理状态,减少代码的耦合性。

策略模式

它允许在运行时根据需要选择算法的行为。该模式通过将算法封装成独立的类,使得它们可以相互替换,而不影响使用算法的客户端代码。

策略模式主要包含以下角色:

  1. 环境(Context):环境对象持有一个策略对象的引用,它提供了一个接口用于执行具体的算法。
  2. 抽象策略(Strategy):定义了策略类的统一接口,用于约束具体策略类的行为。
  3. 具体策略(Concrete Strategy):实现了抽象策略定义的接口,具体实现算法逻辑。

下面以一个简单的支付系统为例来说明策略模式的应用:

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
// 抽象策略类
public interface PaymentStrategy {
void pay(double amount);
}

// 具体策略类
public class AliPayStrategy implements PaymentStrategy {
public void pay(double amount) {
System.out.println("使用支付宝支付:" + amount + "元");
// 具体的支付逻辑
}
}

public class WeChatPayStrategy implements PaymentStrategy {
public void pay(double amount) {
System.out.println("使用微信支付:" + amount + "元");
// 具体的支付逻辑
}
}

// 环境类
@Data
@NoArgsConstructor
public class PaymentContext {
private PaymentStrategy strategy;

public void pay(double amount) {
strategy.pay(amount);
}
}

在上述示例中,我们定义了一个抽象策略类PaymentStrategy,并有两个具体的策略类AliPayStrategyWeChatPayStrategy分别实现了支付宝支付和微信支付的具体逻辑。

环境类PaymentContext持有一个策略对象的引用,并提供了设置策略和支付方法。客户端通过设置不同的策略对象来实现不同的支付方式。这样,客户端代码与具体的支付算法解耦,可以动态地在运行时切换支付策略。

下面是使用策略模式实现的客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
javaCopy Codepublic class Client {
public static void main(String[] args) {
PaymentContext context = new PaymentContext();

// 使用支付宝支付
PaymentStrategy aliPayStrategy = new AliPayStrategy();
context.setPaymentStrategy(aliPayStrategy);
context.pay(100.0);

// 使用微信支付
PaymentStrategy weChatPayStrategy = new WeChatPayStrategy();
context.setPaymentStrategy(weChatPayStrategy);
context.pay(200.0);
}
}

运行上述客户端代码,输出如下:

Copy Code 使用支付宝支付:100.0 元
使用微信支付:200.0 元

通过策略模式,我们可以轻松地在运行时切换不同的支付方式,而不需要改动客户端代码。策略模式将算法的选择和使用进行了解耦,提高了代码的灵活性和可维护性。同时,策略模式也符合开闭原则,当需要新增一种支付方式时,只需要添加新的具体策略类即可,无需修改原有代码逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client {
public static void main(String[] args) {
double price = 100.0;
String type = "normal";
double discount = 1.0;

// 根据商品类型设置折扣率
if (type.equals("vip")) {
discount = 0.9;
} else if (type.equals("member")) {
discount = 0.95;
} else if (type.equals("promotion")) {
discount = 0.8;
}

double actualPrice = price * discount;
System.out.println("商品的实际价格为:" + actualPrice);
}
}

上述代码中,我们根据商品类型手动设置相应的折扣率,然后计算实际价格。这样的代码虽然简单,但存在以下问题:

  1. 客户端代码与具体的折扣算法高度耦合,如果需要更改算法,例如新增一种商品类型,就需要修改客户端代码,这会导致代码的可维护性变差。
  2. 没有遵循开闭原则,当需要新增一种商品类型时,就需要修改原有代码逻辑,这样会影响到其他代码的稳定性。

因此,采用策略模式能更好地解决这些问题,实现代码的松耦合和可维护性。

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
package com.nwa.sheji;

/**
* @Author Lucky友人a
* @Date 2024/1/19 -14:48
*/


public class CeLue {

public static void main(String[] args) {
double price = 100.0;
String type = "vip";
PayStrategy conType = getPayStrategy(type);
PayContext payContext = new PayContext(conType);
payContext.pay(price);
}

private static PayStrategy getPayStrategy(String type) {
switch (type) {
case "vip":
return new VipType();
default:
return new NormalType();
}
}
}

interface PayStrategy {
void pay(double amount);
}

class NormalType implements PayStrategy {
@Override
public void pay(double amount) {
System.out.println("普通支付" + amount + "元");
}
}

class VipType implements PayStrategy {
@Override
public void pay(double amount) {
System.out.println("vip支付" + amount * 0.9 + "元");
}
}

class PayContext {
private PayStrategy payStrategy;

public PayContext(PayStrategy payStrategy) {
this.payStrategy = payStrategy;
}

public void pay(double amount) {
payStrategy.pay(amount);
}

}

分享一个案例:统一认证登录

认证接口类 AuthService

1
2
3
4
5
public interface AuthService {

UserExt execute(AuthParamsDto authParamsDto);

}

它的实现类 可以是密码登录PasswordAuthServiceImpl 手机号登录PhoneAuthServiceImpl 微信登录WxAuthServiceImpl 等,获取前缀,拼接bean名称,Spring上下文拿到bean

调用,我用的是SpringsSecurity框架并重写了 UserDetailsService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Autowired
ApplicationContext applicationContext;

//传入的是AuthParamsDto的json串
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
AuthParamsDto authParamsDto = null;
try {
//将认证参数转为AuthParamsDto类型
authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
} catch (Exception e) {
log.info("认证请求不符合项目要求:{}",s);
throw new RuntimeException("认证请求数据格式不对");
}
//认证方式
String authType = authParamsDto.getAuthType();
//从spring容器中拿具体的认证bean实例
AuthService authService = applicationContext.getBean(authType + "_authservice", AuthService.class);
//开始认证,认证成功拿到用户信息
XcUserExt xcUserExt = authService.execute(authParamsDto);

return getUserPrincipal(xcUserExt);
}
1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class AuthParamsDto {

private String username; //用户名
private String password; //域 用于扩展
private String cellphone;//手机号
private String checkcode;//验证码
private String checkcodekey;//验证码key
private String authType; // 认证的类型 password:用户名密码模式类型 sms:短信模式类型
private Map<String, Object> payload = new HashMap<>();//附加数据,作为扩展,不同认证类型可拥有不同的附加数据。如认证类型为短信时包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情况下都包含clientId

}

职责链模式

它允许多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连接成一条链,并沿着这条链传递请求,直到有一个对象处理请求为止。

在职责链模式中,每个处理请求的对象都有一个处理方法,并且持有对下一个处理对象的引用。当一个请求到达时,它会被传递给当前处理对象进行处理,如果当前对象无法处理,则将请求传递给下一个处理对象,直到有一个对象处理为止。

职责链模式的主要优点:

  1. 将请求的发送者和接收者解耦,可以灵活地增加或删除处理对象,改变它们之间的顺序,从而简化对象的相互连接。
  2. 可以动态地组合处理器,添加或删除某些处理器,从而为客户端提供更多的选择。
  3. 具有良好的可扩展性,可以在不影响其他处理器的情况下添加新的处理器。

职责链模式的主要缺点:

  1. 如果链条过长,会影响系统的性能。
  2. 可能存在请求得不到处理的情况,因此需要及时对链条进行检查、维护和优化,确保请求能够得到正确的处理。

职责链模式适用于以下场景:

  1. 需要将一个请求发送给多个对象进行处理,但不知道哪个对象能够处理请求;
  2. 需要动态地指定可以处理请求的对象集合;
  3. 对象需要按照特定的顺序进行处理;
  4. 一个请求可能被多个对象处理,但最终结果只取决于其中一个对象。

总之,职责链模式可有效解耦代码中不同模块的处理逻辑,提高代码的灵活性和可维护性,如果应用得当,它可以是优秀的扩展点和优化点。

假设我们有一个订单处理系统,订单的处理需要经过多个环节,如验证、折扣计算、库存检查和物流安排等。我们可以使用职责链模式来实现这个订单处理过程。

首先,我们定义一个抽象处理器(Handler):

1
2
3
4
5
6
7
8
9
javaCopy Codepublic abstract class Handler {
protected Handler nextHandler;

public void setNextHandler(Handler handler) {
this.nextHandler = handler;
}

public abstract void handleRequest(Order order);
}

然后,我们创建具体的处理器类,分别是验证处理器(ValidationHandler)、折扣处理器(DiscountHandler)、库存处理器(InventoryHandler)和物流处理器(LogisticsHandler):

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
javaCopy Codepublic class ValidationHandler extends Handler {
public void handleRequest(Order order) {
if (order.isDataValid()) {
System.out.println("订单数据验证通过");
if (nextHandler != null) {
nextHandler.handleRequest(order);
}
} else {
System.out.println("订单数据验证失败,无法继续处理");
}
}
}

public class DiscountHandler extends Handler {
public void handleRequest(Order order) {
order.applyDiscount();
System.out.println("折扣计算完成");
if (nextHandler != null) {
nextHandler.handleRequest(order);
}
}
}

public class InventoryHandler extends Handler {
public void handleRequest(Order order) {
if (order.checkInventory()) {
System.out.println("库存检查通过");
if (nextHandler != null) {
nextHandler.handleRequest(order);
}
} else {
System.out.println("库存不足,无法继续处理");
}
}
}

public class LogisticsHandler extends Handler {
public void handleRequest(Order order) {
order.arrangeLogistics();
System.out.println("物流安排完成");
// 不需要调用nextHandler,这是处理链的最后一个处理器
}
}

最后,我们创建一个客户端示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
javaCopy Codepublic class Client {
public static void main(String[] args) {
Handler validationHandler = new ValidationHandler();
Handler discountHandler = new DiscountHandler();
Handler inventoryHandler = new InventoryHandler();
Handler logisticsHandler = new LogisticsHandler();

// 构建处理链
validationHandler.setNextHandler(discountHandler);
discountHandler.setNextHandler(inventoryHandler);
inventoryHandler.setNextHandler(logisticsHandler);

// 创建订单并提交处理请求
Order order = new Order();
validationHandler.handleRequest(order);
}
}

在这个示例中,订单的处理经过了验证、折扣计算、库存检查和物流安排四个环节。每个处理器都有一个处理方法来处理请求,并且持有对下一个处理器的引用。当一个处理器完成自己的处理逻辑后,如果存在下一个处理器,则将请求传递给下一个处理器进行处理。最终,订单会依次经过所有的处理器进行处理。

使用职责链模式可以灵活地修改、扩展和调整订单处理过程,例如可以在处理器中添加日志记录、异常处理等功能,而不需要修改原有的处理逻辑。此外,职责链模式还可以简化客户端和处理器之间的耦合关系,提高代码的可维护性和扩展性。

职责链模式可以在许多地方应用,常见的应用场景包括:

  1. 请求处理。职责链模式可以用于请求处理中,将请求传递给不同的处理器进行处理,例如 Java Servlet 中的过滤器、Spring 框架中的拦截器等。
  2. 异常处理。职责链模式可以用于异常处理中,将异常传递给不同的异常处理器进行处理,例如 Java 中的异常链、.NET 中的异常过滤器等。
  3. 日志记录。职责链模式可以用于日志记录中,将日志信息传递给不同的日志记录器进行记录,例如 Log4j、Slf4j 等。
  4. 权限控制。职责链模式可以用于权限控制中,将权限验证请求传递给不同的权限验证器进行验证,例如 Shiro 框架中的过滤器链、Spring Security 框架中的过滤器链等。
  5. 消息通知。职责链模式可以用于消息通知中,将消息传递给不同的通知接收者进行处理,例如 Android 中的事件传递机制、Node.js 中的 EventEmitter 等。

总之,职责链模式可以应用于任何需要处理一个请求或消息的场景,特别是在业务逻辑比较复杂、处理流程变化频繁的情况下,使用职责链模式可以提高代码的可维护性和可扩展性。

命令模式

它将请求封装成一个对象,从而使你可以使用不同的请求对客户端进行参数化。该模式允许请求的发送者和接收者彻底解耦。

在命令模式中,包含以下几个角色:

  1. 命令(Command):定义了执行操作的接口,通常包括一个执行方法(execute)。
  2. 具体命令(Concrete Command):实现了命令接口,并且包含一个接收者对象(Receiver),用于执行具体的操作。
  3. 接收者(Receiver):负责执行与请求相关的操作。
  4. 调用者(Invoker):持有一个命令对象,并在需要的时候调用命令对象的执行方法。调用者并不直接与接收者交互,而是通过命令对象来间接执行请求。
  5. 客户端(Client):创建具体命令对象,并设置命令对象的接收者。

命令模式的工作流程如下:

  1. 客户端创建具体命令对象,并设置命令对象的接收者。
  2. 调用者持有命令对象。
  3. 调用者通过调用命令对象的执行方法,间接地执行与请求相关的操作。
  4. 命令对象将请求传递给接收者,接收者执行具体操作。

通过使用命令模式,可以将请求的发送者和接收者解耦,实现了请求的封装、参数化和灵活性。命令模式也可以用于实现可撤销的操作、日志记录以及事务管理等功能。

总结来说,命令模式适用于需要将请求封装成对象,并支持请求的参数化、排队、记录日志和撤销等操作的场景。它提供了一种更加灵活和可扩展的方式来处理命令相关的操作。

  1. 定义命令接口(Command):
1
2
3
javaCopy Codepublic interface Command {
void execute();
}
  1. 实现具体命令类(Concrete Command):
1
2
3
4
5
6
7
8
9
10
11
12
javaCopy Codepublic class PlayMusicCommand implements Command {
private MusicPlayer player;

public PlayMusicCommand(MusicPlayer player) {
this.player = player;
}

@Override
public void execute() {
player.play();
}
}
  1. 定义接收者类(Receiver):
1
2
3
4
5
6
7
8
9
10
11
12
13
javaCopy Codepublic class MusicPlayer {
public void play() {
System.out.println("播放音乐");
}

public void stop() {
System.out.println("停止播放");
}

public void adjustVolume(int volume) {
System.out.println("调整音量为:" + volume);
}
}
  1. 实现调用者类(Invoker):
1
2
3
4
5
6
7
8
9
10
11
javaCopy Codepublic class RemoteController {
private Command command;

public void setCommand(Command command) {
this.command = command;
}

public void pressButton() {
command.execute();
}
}
  1. 客户端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
javaCopy Codepublic class Client {
public static void main(String[] args) {
// 创建接收者对象
MusicPlayer player = new MusicPlayer();

// 创建具体命令对象,并设置接收者
Command playCommand = new PlayMusicCommand(player);

// 创建调用者对象,并设置具体命令
RemoteController controller = new RemoteController();
controller.setCommand(playCommand);

// 按下按钮,执行具体命令
controller.pressButton();
}
}

在上述示例中,家庭智能音响系统中的播放音乐操作被封装成了一个具体的命令对象(PlayMusicCommand)。通过使用命令模式,可以将播放音乐这个请求与发送者(RemoteController)和接收者(MusicPlayer)进行解耦。当用户按下遥控器按钮时,命令对象被执行,从而触发音乐播放操作。

通过命令模式,我们可以轻松地添加新的命令,如停止播放、调节音量等,而无需修改原有的代码。这种设计使得系统更加灵活和可扩展,同时提高了代码的可维护性。

访问者模式

它可以将算法与对象结构分离,使得算法可以独立于该对象结构来变化。

在访问者模式中,包含以下几个角色:

  1. 访问者(Visitor):定义了对每个元素对象访问时所要执行的操作。可以通过多态实现不同的访问策略。
  2. 具体访问者(Concrete Visitor):实现了访问者接口,并包含了针对各个具体元素的访问方法。
  3. 元素(Element):定义了一个接受访问的方法(accept),通常以访问者作为参数。
  4. 具体元素(Concrete Element):实现了元素接口,并且包含一个接受访问的方法。
  5. 对象结构(Object Structure):包含了元素的集合,并提供了遍历元素集合的方法。

访问者模式的工作流程如下:

  1. 访问者对象被创建。
  2. 具体元素对象被创建,并传递给访问者对象。
  3. 访问者对象调用该元素对象的接受访问的方法,并将自己作为参数传递进去。
  4. 元素对象将自己作为参数传递给访问者对象中相应的访问方法,从而完成对该元素对象的访问操作。

通过使用访问者模式,可以将具体元素对象与访问算法分离开来,使得两者可以独立变化而不会对彼此产生影响。这样的设计也便于添加新的访问算法。

访问者模式常常用于数据结构相对稳定的系统,需要用于定义、扩展和修改一些操作。它的优点是易于增加新的访问操作,同时也能保证访问者对象的行为符合单一职责原则。但是它的缺点是增加新的元素类比较困难,并且会导致访问者类的代码复杂度增加。

一个简单的例子:假设我们要对一组不同类型的电脑进行访问,该电脑由 CPU、硬盘和内存三个部分组成。我们可以使用访问者模式将访问者对象与具体电脑对象分离开来,并实现多种不同的访问策略。

  1. 定义元素接口(Element):
1
2
3
javaCopy Codepublic interface ComputerPart {
void accept(ComputerVisitor visitor);
}
  1. 实现具体元素类(Concrete Element):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
javaCopy Codepublic class Cpu implements ComputerPart {
@Override
public void accept(ComputerVisitor visitor) {
visitor.visitCpu(this);
}
}

public class HardDisk implements ComputerPart {
@Override
public void accept(ComputerVisitor visitor) {
visitor.visitHardDisk(this);
}
}

public class Memory implements ComputerPart {
@Override
public void accept(ComputerVisitor visitor) {
visitor.visitMemory(this);
}
}
  1. 定义访问者接口(Visitor):
1
2
3
4
5
javaCopy Codepublic interface ComputerVisitor {
void visitCpu(Cpu cpu);
void visitHardDisk(HardDisk hardDisk);
void visitMemory(Memory memory);
}
  1. 实现具体访问者类(Concrete Visitor):
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
javaCopy Codepublic class RepairVisitor implements ComputerVisitor {
@Override
public void visitCpu(Cpu cpu) {
System.out.println("正在修复CPU");
}

@Override
public void visitHardDisk(HardDisk hardDisk) {
System.out.println("正在修复硬盘");
}

@Override
public void visitMemory(Memory memory) {
System.out.println("正在修复内存");
}
}

public class UpgradeVisitor implements ComputerVisitor {
@Override
public void visitCpu(Cpu cpu) {
System.out.println("正在升级CPU");
}

@Override
public void visitHardDisk(HardDisk hardDisk) {
System.out.println("正在升级硬盘");
}

@Override
public void visitMemory(Memory memory) {
System.out.println("正在升级内存");
}
}
  1. 实现对象结构类(Object Structure):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
javaCopy Codepublic class Computer {
private List<ComputerPart> parts = new ArrayList<>();

public Computer() {
parts.add(new Cpu());
parts.add(new HardDisk());
parts.add(new Memory());
}

public void accept(ComputerVisitor visitor) {
for (ComputerPart part : parts) {
part.accept(visitor);
}
}
}
  1. 客户端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
javaCopy Codepublic class Client {
public static void main(String[] args) {
Computer computer = new Computer();
ComputerVisitor repairVisitor = new RepairVisitor();
ComputerVisitor upgradeVisitor = new UpgradeVisitor();

// 对电脑进行维修操作
computer.accept(repairVisitor);

// 对电脑进行升级操作
computer.accept(upgradeVisitor);
}
}

在上述示例中,访问者模式被用于实现对不同类型电脑部件的维修或升级操作。通过将访问者对象与具体元素对象分离开来,我们可以轻松地添加新的访问操作,如更换显示器等,同时也能保证访问者对象的行为符合单一职责原则。

调停者模式

它用于降低多个对象之间的耦合度,并且通过中介者对象来协调它们的交互。

在调停者模式中,有以下几个角色:

  1. 抽象中介者(Abstract Mediator):定义了中介者对象的接口,它知道各个同事对象,并负责协调它们之间的交互关系。
  2. 具体中介者(Concrete Mediator):实现了中介者接口,它通过协调各个同事对象之间的交互来实现协作行为。
  3. 抽象同事类(Abstract Colleague):定义了同事类的接口,它保存了一个对中介者对象的引用,可以通过中介者与其他同事进行通信。
  4. 具体同事类(Concrete Colleague):实现了同事类的接口,它与其他具体同事类通过中介者进行通信。

调停者模式的工作流程如下:

  1. 同事对象将自己的信息发送给中介者对象。
  2. 中介者对象收到信息后,根据具体的业务逻辑,协调其他同事对象并向它们发送通知。
  3. 各个同事对象根据中介者发出的通知进行自身操作。

通过使用调停者模式,可以将对象之间的交互逻辑封装在中介者对象中,降低了对象之间的直接耦合。各个同事对象只需与中介者对象进行通信,而不需要与其他同事对象进行直接通信,从而简化了对象之间的交互关系。

一个简单的例子:假设有一个聊天室,里面有多个用户参与,并且用户之间可以相互发送消息。我们可以使用调停者模式将聊天室作为中介者对象,并让每个用户作为具体同事类。

  1. 定义抽象中介者类(Abstract Mediator):
1
2
3
4
javaCopy Codepublic abstract class ChatRoom {
public abstract void sendMessage(String message, User sender);
public abstract void addUser(User user);
}
  1. 实现具体中介者类(Concrete Mediator):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
javaCopy Codepublic class ConcreteChatRoom extends ChatRoom {
private List<User> users = new ArrayList<>();

@Override
public void sendMessage(String message, User sender) {
for (User user : users) {
if (user != sender) {
user.receiveMessage(message);
}
}
}

@Override
public void addUser(User user) {
users.add(user);
}
}
  1. 定义抽象同事类(Abstract Colleague):
1
2
3
4
5
6
7
8
9
10
javaCopy Codepublic abstract class User {
protected ChatRoom chatRoom;

public User(ChatRoom chatRoom) {
this.chatRoom = chatRoom;
}

public abstract void sendMessage(String message);
public abstract void receiveMessage(String message);
}
  1. 实现具体同事类(Concrete Colleague):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
javaCopy Codepublic class ConcreteUser extends User {
private String name;

public ConcreteUser(String name, ChatRoom chatRoom) {
super(chatRoom);
this.name = name;
}

@Override
public void sendMessage(String message) {
chatRoom.sendMessage(message, this);
}

@Override
public void receiveMessage(String message) {
System.out.println(name + " 收到消息:" + message);
}
}
  1. 客户端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
javaCopy Codepublic class Client {
public static void main(String[] args) {
ChatRoom chatRoom = new ConcreteChatRoom();

User user1 = new ConcreteUser("User1", chatRoom);
User user2 = new ConcreteUser("User2", chatRoom);
User user3 = new ConcreteUser("User3", chatRoom);

chatRoom.addUser(user1);
chatRoom.addUser(user2);
chatRoom.addUser(user3);

user1.sendMessage("大家好!");
user2.sendMessage("你好,User1!");
}
}

在上述示例中,调停者模式被用于实现聊天室的功能。聊天室作为中介者对象,负责协调各个用户之间的消息交互。每个用户都是具体同事类,通过中介者对象发送和接收消息。通过使用调停者模式,用户之间的交互逻辑被封装在中介者对象中,使得系统结构更加清晰且易于扩展。

备忘录模式

它用于捕获一个对象的内部状态,并在不破坏封装性的前提下将其保存,以便在需要时进行恢复。

备忘录模式主要涉及以下几个角色:

  1. 发起人(Originator):负责创建一个备忘录(Memento),并记录当前对象的内部状态。
  2. 备忘录(Memento):存储发起人对象的内部状态。可以包含发起人的部分或全部状态。
  3. 管理者(Caretaker):负责保存备忘录,并在需要时将备忘录返回给发起人。

备忘录模式的工作流程如下:

  1. 发起人创建备忘录对象,并将需要保存的状态传递给备忘录。
  2. 管理者接收到备忘录对象后,将其保存起来。
  3. 当需要恢复状态时,管理者将备忘录对象返回给发起人。
  4. 发起人使用备忘录对象中保存的状态进行恢复。

备忘录模式的主要目的是让对象能够保存和恢复内部状态,同时保持对其他对象的封装。这样可以在不暴露对象实现细节的情况下,提供一种可靠的恢复机制。

下面是一个简单的示例来说明备忘录模式的使用场景和实现方法:

  1. 定义发起人类(Originator):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
javaCopy Codepublic class Originator {
private String state;

public void setState(String state) {
this.state = state;
}

public String getState() {
return state;
}

public Memento saveToMemento() {
return new Memento(state);
}

public void restoreFromMemento(Memento memento) {
state = memento.getState();
}
}
  1. 定义备忘录类(Memento):
1
2
3
4
5
6
7
8
9
10
11
javaCopy Codepublic class Memento {
private String state;

public Memento(String state) {
this.state = state;
}

public String getState() {
return state;
}
}
  1. 定义管理者类(Caretaker):
1
2
3
4
5
6
7
8
9
10
11
javaCopy Codepublic class Caretaker {
private Memento memento;

public void saveMemento(Memento memento) {
this.memento = memento;
}

public Memento retrieveMemento() {
return memento;
}
}
  1. 客户端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
javaCopy Codepublic class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();

// 设置初始状态并保存到备忘录
originator.setState("State 1");
caretaker.saveMemento(originator.saveToMemento());

// 修改状态并保存到备忘录
originator.setState("State 2");
caretaker.saveMemento(originator.saveToMemento());

// 修改状态并保存到备忘录
originator.setState("State 3");
caretaker.saveMemento(originator.saveToMemento());

// 恢复到之前保存的备忘录状态
originator.restoreFromMemento(caretaker.retrieveMemento());
System.out.println("Current State: " + originator.getState());
}
}

在上述示例中,我们使用备忘录模式保存和恢复对象的状态。发起人类(Originator)负责创建备忘录对象,并记录当前的内部状态;备忘录类(Memento)用于存储发起人对象的状态;管理者类(Caretaker)负责保存和提供备忘录对象;客户端代码根据需要设置、保存和恢复对象的状态。

通过使用备忘录模式,可以在不违反封装性的情况下,实现对象的状态保存和恢复。这样可以有效地处理撤销、重做、历史记录以及错误恢复等场景,提高系统的可靠性和灵活性。

迭代器模式

它提供了一种遍历集合对象的统一接口,而无需关心集合对象的内部结构。通过使用迭代器模式,我们可以在不暴露集合对象的内部实现细节的情况下,逐个访问集合中的元素。

迭代器模式包含以下几个角色:

  1. 迭代器(Iterator):定义访问和遍历元素的接口,通常包括方法如获取下一个元素、判断是否还有下一个元素等。
  2. 具体迭代器(ConcreteIterator):实现迭代器接口,负责具体的元素遍历和访问操作。
  3. 集合(Aggregate):定义创建迭代器的接口,通常包含获取迭代器的方法。
  4. 具体集合(ConcreteAggregate):实现集合接口,创建对应的具体迭代器,并返回该迭代器。

使用迭代器模式的优点包括:

  1. 简化集合类的接口:通过将遍历集合的行为委托给迭代器,集合类可以更专注于自身的功能实现。
  2. 封装集合的内部结构:迭代器模式将集合类的内部结构进行封装,使得客户端无需了解具体的迭代算法和内部存储方式。
  3. 支持多种遍历方式:同一个集合可以使用不同的迭代器进行遍历,满足不同的需求。

迭代器模式常见的应用场景包括:

  1. 需要遍历不同类型的集合对象而无需关注其内部实现结构的情况。
  2. 需要提供统一的访问接口以支持多种遍历方式的情况。

总之,迭代器模式通过将遍历行为抽象出来,使得我们可以以一种统一且灵活的方式遍历集合对象,提高了代码的可复用性和扩展性。

首先,我们需要定义迭代器接口(Iterator),包含获取下一个任务和判断是否还有下一个任务的方法。

1
2
3
4
javaCopy Codepublic interface Iterator {
boolean hasNext();
Task next();
}

然后,我们定义具体的迭代器类(ConcreteIterator),实现迭代器接口,负责实现具体的遍历逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
javaCopy Codepublic class TaskIterator implements Iterator {
private TaskList taskList;
private int position;

public TaskIterator(TaskList taskList) {
this.taskList = taskList;
this.position = 0;
}

@Override
public boolean hasNext() {
return position < taskList.size();
}

@Override
public Task next() {
if (hasNext()) {
Task task = taskList.get(position);
position++;
return task;
}
return null;
}
}

接下来,我们定义集合接口(Aggregate),包含获取迭代器的方法。

1
2
3
javaCopy Codepublic interface Aggregate {
Iterator createIterator();
}

最后,我们定义具体的集合类(ConcreteAggregate),实现集合接口,创建对应的具体迭代器,并返回该迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
javaCopy Codepublic class TaskList implements Aggregate {
private List<Task> tasks;

public TaskList() {
this.tasks = new ArrayList<>();
}

public void addTask(Task task) {
tasks.add(task);
}

public Task getTask(int index) {
return tasks.get(index);
}

public int size() {
return tasks.size();
}

@Override
public Iterator createIterator() {
return new TaskIterator(this);
}
}

现在,我们可以使用迭代器模式来遍历任务列表并处理每个任务。

1
2
3
4
5
6
7
8
9
10
11
javaCopy CodeTaskList taskList = new TaskList();
taskList.addTask(new Task("Task 1"));
taskList.addTask(new Task("Task 2"));
taskList.addTask(new Task("Task 3"));

Iterator iterator = taskList.createIterator();
while (iterator.hasNext()) {
Task task = iterator.next();
// 处理任务逻辑
System.out.println("Processing task: " + task.getName());
}

通过使用迭代器模式,我们可以方便地遍历任务列表,并且不需要关心具体的遍历实现方式。这样,我们可以将集合的内部结构进行封装,提高代码的灵活性和可维护性。

解释器模式

它用于解析特定的语法或表达式,并执行相应的操作。该模式将一个复杂的语法规则表示为类的层次结构,并使用解释器来解析和执行这些语法规则。

解释器模式包含以下几个角色:

  1. 抽象表达式(Abstract Expression):定义一个解释器的接口,声明了解释器需要实现的方法,通常包括解释方法 interpret()。
  2. 终结符表达式(Terminal Expression):实现抽象表达式接口,并表示语法规则中的终结符,不能再进行拆分的最小单位。终结符表达式通常是一些具体的类,可以有自己的内部状态和逻辑。
  3. 非终结符表达式(Non-terminal Expression):实现抽象表达式接口,并表示语法规则中的非终结符,可以进行进一步的拆分和解析。非终结符表达式通常包含其他表达式作为其子表达式,用于构建语法树。
  4. 上下文(Context):包含解释器需要解析的语句或表达式,并提供给解释器进行解释和执行的环境。

使用解释器模式的关键是将复杂的语法规则表示为解释器的类层次结构,每个类对应不同的语法规则或语法元素。通过解释器的递归调用,可以逐步分解和解析语法规则,最终得到执行结果。

解释器模式常见的应用场景包括:

  1. 正则表达式的解析与匹配。
  2. 编程语言中的解释器,如解析并执行脚本语言。
  3. 配置文件解析,例如 XML、JSON 等配置文件的解释器。

总之,解释器模式提供了一种处理特定语法或表达式的灵活方式,使得我们可以根据需要定义复杂的语法规则,并通过解释器进行解析和执行。这种模式的好处是能够将解析逻辑与具体表达式的结构进行解耦,增加了系统的灵活性和可扩展性。

假设我们需要解析一些简单的数学表达式,例如 “3 + 2 * 4 - 1”。为了实现解析和计算,可以使用解释器模式来将该表达式表示为一个解释器对象,并依次对表达式中的每个元素进行解释,最终得到计算结果。

首先,我们定义抽象表达式接口 Expression,包含解析方法 interpret()。

1
2
3
javaCopy Codepublic interface Expression {
int interpret();
}

然后,我们定义具体的终结符表达式类 NumberExpression,表示简单的数字表达式。

1
2
3
4
5
6
7
8
9
10
11
12
javaCopy Codepublic class NumberExpression implements Expression {
private int number;

public NumberExpression(int number) {
this.number = number;
}

@Override
public int interpret() {
return number;
}
}

接下来,我们定义具体的非终结符表达式类 AddExpression、SubtractExpression、MultiplyExpression 和 DivideExpression,表示加减乘除等运算。

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
javaCopy Codepublic class AddExpression implements Expression {
private Expression left;
private Expression right;

public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}

public class SubtractExpression implements Expression {
private Expression left;
private Expression right;

public SubtractExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}

public class MultiplyExpression implements Expression {
private Expression left;
private Expression right;

public MultiplyExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}

public class DivideExpression implements Expression {
private Expression left;
private Expression right;

public DivideExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}

@Override
public int interpret() {
int denominator = right.interpret();
if (denominator == 0) {
throw new IllegalArgumentException("Division by zero");
}
return left.interpret() / denominator;
}
}

最后,我们定义上下文类 Context,包含待解析的表达式和解释器实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
javaCopy Codepublic class Context {
private String expression;
private Stack<Expression> parserStack;

public Context(String expression) {
this.expression = expression;
this.parserStack = new Stack<>();
}

public void pushParser(Expression expression) {
parserStack.push(expression);
}

public Expression popParser() {
if (!parserStack.isEmpty()) {
return parserStack.pop();
}
return null;
}

public String getExpression() {
return expression;
}
}

现在,我们可以使用这些类来解析并计算数学表达式了:

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
javaCopy CodeString expression = "3 + 2 * 4 - 1";
Context context = new Context(expression);

String[] tokens = expression.split("\\s+");
for (String token : tokens) {
if (token.equals("+")) {
Expression left = context.popParser();
Expression right = new NumberExpression(Integer.parseInt(tokens[++i]));
context.pushParser(new AddExpression(left, right));
} else if (token.equals("-")) {
Expression left = context.popParser();
Expression right = new NumberExpression(Integer.parseInt(tokens[++i]));
context.pushParser(new SubtractExpression(left, right));
} else if (token.equals("*")) {
Expression left = context.popParser();
Expression right = new NumberExpression(Integer.parseInt(tokens[++i]));
context.pushParser(new MultiplyExpression(left, right));
} else if (token.equals("/")) {
Expression left = context.popParser();
Expression right = new NumberExpression(Integer.parseInt(tokens[++i]));
context.pushParser(new DivideExpression(left, right));
} else {
context.pushParser(new NumberExpression(Integer.parseInt(token)));
}
}

System.out.println(expression + " = " + context.popParser().interpret());

通过使用解释器模式,我们将数学表达式表示为一个解释器对象,并逐个解析其元素,最终得到计算结果。这种方式虽然比较繁琐,但是可以处理更加复杂的表达式,并且不需要构建抽象语法树。

反射

反射可以让我们在程序运行时动态加载类并获取类的详细信息,本质是JVM得到 java.lang.Class 对象之后,再对 class 对象进行反编译,从而获取对象的各种信息

下面的一个案例是简单工厂模式的升级版也就是工厂模式,结合反射机制

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
interface Shape {
void draw();
}

class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}

class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}

class ShapeFactory {
private Map<String, Class<? extends Shape>> registeredShapes = new HashMap<>();

public void registerShape(String shapeName, Class<? extends Shape> shapeClass) {
registeredShapes.put(shapeName, shapeClass);
}

public Shape createShape(String shapeName) {
if (registeredShapes.containsKey(shapeName)) {
try {
Class<? extends Shape> shapeClass = registeredShapes.get(shapeName);
return shapeClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}

public class Main {
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactory();
factory.registerShape("Circle", Circle.class);
factory.registerShape("Rectangle", Rectangle.class);

Shape circle = factory.createShape("Circle");
if (circle != null) {
circle.draw(); // Output: Drawing a circle
}

Shape rectangle = factory.createShape("Rectangle");
if (rectangle != null) {
rectangle.draw(); // Output: Drawing a rectangle
}
}
}

通过上述代码,我们将具体产品的类名以字符串的形式注册到工厂类中,并使用反射在创建对象时动态实例化具体产品。这样,当需要添加新的具体产品时,只需调用 registerShape 方法来注册即可,无需修改工厂类的代码。

这种方式更符合开闭原则,使得工厂类对于新增具体产品类型是开放的,而对于修改已有代码是封闭的。

满足六大原则

  1. 单一职责原则(Single Responsibility Principle,SRP):
    • 在示例中,ShapeFactory 类负责创建形状对象的职责,它只有一个职责。它没有额外的功能或责任,符合 SRP 原则。
  2. 开放封闭原则(Open-Closed Principle,OCP):
    • 示例中的工厂类可以通过扩展来支持更多类型的形状,而无需修改现有代码。只需要新增具体产品类并在工厂中加入相应的判断即可,符合 OCP 原则。
  3. 里氏替换原则(Liskov Substitution Principle,LSP):
    • CircleRectangle 类都实现了 Shape 接口,可以在不影响代码功能的情况下互相替换,符合 LSP 原则。
  4. 接口隔离原则(Interface Segregation Principle,ISP):
    • 示例中的抽象产品接口 Shape 只包含一个方法 draw(),而具体产品类只需实现该方法。没有强制要求具体产品类实现其不需要的方法,符合 ISP 原则。
  5. 依赖倒置原则(Dependency Inversion Principle,DIP):
    • 工厂类 ShapeFactory 依赖于抽象接口 Shape 而不是具体的产品类,符合 DIP 原则。这样,当需要扩展工厂类以支持新的形状时,不需要修改工厂类的代码。
  6. 迪米特法则(Law of Demeter,LoD):
    • 示例中的客户端只与工厂类 ShapeFactory 交互,而不与具体产品类直接交互。客户端只需要知道如何使用工厂类来创建对象,无需知道具体对象的创建细节,符合 LoD 原则。