设计模式分类

  • 创建型模式

    用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。

  • 结构型模式

    用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。

  • 行为型模式

    用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。

创建型-单例模式(Singleton pattern)

单例模式(Singleton pattern)确保一个类只有一个实例,并提供该实例的全局访问点

通过单例的创建时机分类,可分为懒汉式和饿汉式

实现类型 单例创建时机 应用场景
饿汉式 - 不可控,类加载时自动创建单例 初始化时就需要创建单例,希望单例对象初始化速度快,适合占用内存小的单例
懒汉式 可控,有需要时才手动创建单例 按需、延迟创建单例,单例初始化时间长,希望后续再加载此单例以提高服务启动速度,单例占用内存高

饿汉式最佳实践-枚举类

枚举类的特点是:

  • 不可被继承(final)
  • 线程安全,每个枚举元素都是类静态常量,只会被装载一次

  • 枚举元素都通过静态代码块来进行初始化

  • 默认构造方法是私有的

  • 大部分方法都是final

通过枚举类型实现优点是:

  • 线程安全

  • 自由序列化

  • 实现更加简单、简洁

唯一缺点就是饿汉式的缺点:创建时机不可控

示例

1
2
3
public enum Singleton {
uniqueInstance;
}

懒汉式最佳实现-静态内部类

当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。

这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。

优点:

  • 线程安全

  • 节省资源(不需要过多同步开销)

  • 实现简单

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {

//私有构造方法
private Singleton() {}

private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

//对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

创建型-简单工厂(Simple Factory)

工厂模式是在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口

用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程

把实例化的操作单独放到一个类中,这个类就成为简单工厂类

让简单工厂类来决定应该用哪个具体子类来实例化,这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类

简单工厂模式适用于以下场景:

  1. 当需要创建的对象只有一个具体产品类时;
  2. 创建对象的逻辑较为简单,不需要涉及复杂的业务逻辑;
  3. 不需要频繁地添加新的产品种类。

示例:啥咖啡都有的咖啡店

简单工厂模式适用于只需要创建单一种类的产品的情况。如果你只需要某一种产品,而且不涉及到多个产品族的情况,可以选择简单工厂模式。简单工厂模式通过一个工厂类,根据传入的参数或条件来创建不同的产品。

根据咖啡店需求的咖啡类型,咖啡工厂提供对应的咖啡

咖啡类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义抽象产品Coffee
interface Coffee {
String serve();
}

// 定义具体产品AmericanCoffee
class AmericanCoffee implements Coffee {
@Override
public String serve() {
return "美式咖啡";
}
}

// 定义具体产品Espresso
class Espresso implements Coffee {
@Override
public String serve() {
return "意式浓缩咖啡";
}
}

咖啡工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 简单咖啡工厂类
class SimpleCoffeeFactory {
public static Coffee createCoffee(String type) {
switch (type) {
case "American":
return new AmericanCoffee();
case "Espresso":
return new Espresso();
default:
throw new IllegalArgumentException("不支持的咖啡类型:" + type);
}
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
// 使用简单工厂创建咖啡
Coffee coffee1 = SimpleCoffeeFactory.createCoffee("American");
System.out.println(coffee1.serve()); // 输出:美式咖啡

Coffee coffee2 = SimpleCoffeeFactory.createCoffee("Espresso");
System.out.println(coffee2.serve()); // 输出:意式浓缩咖啡
}
}

创建型-工厂方法(Factory Method)

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态,工厂方法模式保持了简单工厂模式的优点,而且遵守了开闭原则

在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则

我们可以定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类

工厂方法模式的主要角色:

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

示例:美式咖啡店

如果需要某一类产品下的几个产品分支,可以选择工厂方法模式。每个具体工厂类负责创建自己特定的产品,从而实现了产品的扩展和变化

现在,美式咖啡厂专供美式咖啡店,意式咖啡厂专供意式咖啡店

咖啡类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义抽象产品Coffee
interface Coffee {
String serve();
}

// 定义具体产品AmericanCoffee
class AmericanCoffee implements Coffee {
@Override
public String serve() {
return "美式咖啡";
}
}

// 定义具体产品Espresso
class Espresso implements Coffee {
@Override
public String serve() {
return "意式浓缩咖啡";
}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义抽象咖啡工厂
interface CoffeeFactory {
Coffee createCoffee();
}

// 具体咖啡工厂实现类
class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}

class ItalianCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new Espresso();
}
}

客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
// 创建具体咖啡工厂
CoffeeFactory coffeeFactory = new AmericanCoffeeFactory();

// 使用工厂创建咖啡
Coffee coffee = coffeeFactory.createCoffee();
System.out.println(coffee.serve()); // 输出:美式咖啡

// 切换到意式咖啡工厂
coffeeFactory = new ItalianCoffeeFactory();
coffee = coffeeFactory.createCoffee();
System.out.println(coffee.serve()); // 输出:意式浓缩咖啡
}
}

创建型-抽象工厂(Abstract Factory)

抽象工厂模式适用于需要创建多个相关产品族的情况。如果每个产品分支都需要对应的配套产品,可以选择抽象工厂模式。抽象工厂模式通过定义抽象工厂接口和多个工厂方法来创建不同产品族的产品,确保这些产品相互配合使用

当多个工厂同时生产不同种类的东西,而不是专职生产同种类产品时,那抽象工厂比工厂方法更加适用。换句话说,当工厂要生产一个带有配套产品的产品而不是单品时,抽象工厂更为合适。

示例:有甜点的美式咖啡店

和上个示例相比,现在我们的咖啡店不只是卖咖啡,还卖甜点。只不过美式咖啡厅卖美式甜点,意式咖啡店卖意式甜点。与此同时,我们的工厂现在是意式工厂和美式工厂。

咖啡类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义抽象产品Coffee
interface Coffee {
String serve();
}

// 定义具体产品AmericanCoffee
class AmericanCoffee implements Coffee {
@Override
public String serve() {
return "美式咖啡";
}
}

// 定义具体产品Espresso
class Espresso implements Coffee {
@Override
public String serve() {
return "意式浓缩咖啡";
}
}

甜品类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义抽象产品Dessert
interface Dessert {
String serve();
}

// 定义具体产品AmericanDessert
class AmericanDessert implements Dessert {
@Override
public String serve() {
return "美式甜点";
}
}

// 定义具体产品ItalianDessert
class ItalianDessert implements Dessert {
@Override
public String serve() {
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
26
27
28
29
30
31
// 定义抽象工厂CoffeeFactory
interface CoffeeFactory {
Coffee createCoffee();
Dessert createDessert();
}

// 定义具体工厂AmericanCoffeeFactory
class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}

@Override
public Dessert createDessert() {
return new AmericanDessert();
}
}

// 定义具体工厂ItalianCoffeeFactory
class ItalianCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new Espresso();
}

@Override
public Dessert createDessert() {
return new ItalianDessert();
}
}

客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) {
// 创建意式咖啡店工厂
CoffeeFactory italianFactory = new ItalianCoffeeFactory();
Coffee espresso = italianFactory.createCoffee();
Dessert italianDessert = italianFactory.createDessert();
System.out.println(espresso.serve()); // 输出:意式浓缩咖啡
System.out.println(italianDessert.serve()); // 输出:意式甜点

// 创建美式咖啡店工厂
CoffeeFactory americanFactory = new AmericanCoffeeFactory();
Coffee americano = americanFactory.createCoffee();
Dessert americanDessert = americanFactory.createDessert();
System.out.println(americano.serve()); // 输出:美式咖啡
System.out.println(americanDessert.serve()); // 输出:美式甜点
}
}

创建型-建造者模式(Builder)

将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。换言之,当某个对象的属性构建过程复杂,但属性注入的程序固定

以一台计算机为例,使用建造者模式建造可以:

  • 分离计算机部件的构造(由Builder来负责)和装配(由Director负责)。 计算机各个零件生产复杂,但装配各个部件的程序是一样的。
  • 由于实现了构建和装配的解耦。不同的计算机硬件构建器,相同的装配,也可以做出不同的计算机对象;相同的计算机硬件构建器,不同的装配顺序也可以做出不同的计算机对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将计算机部件和其组装过程分开,一步一步创建一个复杂的计算机对象。用户只需要指定想要哪个装机套餐就可以得到该计算机对象,而无须知道其各零件的具体构造细节。

含有指挥者的示例:

生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类

自行车及其生成器:

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
// 产品类 - 自行车
@Getter
@Setter
@ToString
class Bicycle {
private String frame;
private String seat;
}

// 抽象生成器 - 自行车生成器
interface BicycleBuilder {
void buildFrame();
void buildSeat();
Bicycle getBicycle();
}

// 具体生成器 - 碳纤维自行车生成器
@Getter
@Setter
class CarbonFiberBicycleBuilder implements BicycleBuilder {
private Bicycle bicycle;

public CarbonFiberBicycleBuilder() {
this.bicycle = new Bicycle();
}

@Override
public void buildFrame() {
bicycle.setFrame("Carbon Fiber Frame");
}

@Override
public void buildSeat() {
bicycle.setSeat("Rubber Seat");
}

@Override
public Bicycle getBicycle() {
return bicycle;
}
}

// 具体生成器 - 铝合金自行车生成器
@Getter
@Setter
class AluminumBicycleBuilder implements BicycleBuilder {
private Bicycle bicycle;

public AluminumBicycleBuilder() {
this.bicycle = new Bicycle();
}

@Override
public void buildFrame() {
bicycle.setFrame("Aluminum Frame");
}

@Override
public void buildSeat() {
bicycle.setSeat("Leather Seat");
}

@Override
public Bicycle getBicycle() {
return bicycle;
}
}

指挥者:

1
2
3
4
5
6
7
8
9
10
11
12
13
class BicycleDirector {
private BicycleBuilder bicycleBuilder;

public BicycleDirector(BicycleBuilder bicycleBuilder) {
this.bicycleBuilder = bicycleBuilder;
}

public Bicycle construct() {
bicycleBuilder.buildFrame();
bicycleBuilder.buildSeat();
return bicycleBuilder.getBicycle();
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Client {
public static void main(String[] args) {
BicycleDirector bicycleDirector = new BicycleDirector(new CarbonFiberBicycleBuilder());
Bicycle carbonFiberBicycle = bicycleDirector.construct();
System.out.println("Carbon Fiber Bicycle:");
System.out.println(carbonFiberBicycle);

System.out.println();

bicycleDirector = new BicycleDirector(new AluminumBicycleBuilder());
Bicycle aluminumBicycle = bicycleDirector.construct();
System.out.println("Aluminum Bicycle:");
System.out.println(aluminumBicycle);
}
}

不含指挥者的示例:

当系统结构相对简单且复杂对象的构建过程比较固定时,比方说我们造的自行车都是碳纤维的,那么可以将指挥者类与抽象生成器进行结合,从而简化系统结构。

自行车及其生成器:

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
// 产品类 - 自行车
@Getter
@Setter
@ToString
class Bicycle {
private String frame;
private String seat;
}

// 抽象生成器 - 自行车生成器
interface BicycleBuilder {
void buildFrame();
void buildSeat();
Bicycle getBicycle();
}

// 具体生成器 - 碳纤维自行车生成器
@Getter
@Setter
class CarbonFiberBicycleBuilder implements BicycleBuilder {
private Bicycle bicycle;

public CarbonFiberBicycleBuilder() {
this.bicycle = new Bicycle();
}

@Override
public void buildFrame() {
bicycle.setFrame("Carbon Fiber Frame");
}

@Override
public void buildSeat() {
bicycle.setSeat("Rubber Seat");
}

@Override
public Bicycle getBicycle() {
return bicycle;
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
BicycleBuilder bicycleBuilder = new CarbonFiberBicycleBuilder();

bicycleBuilder.buildFrame();
bicycleBuilder.buildSeat();
Bicycle bicycle = bicycleBuilder.getBicycle();

System.out.println("Carbon Fiber Bicycle:");
System.out.println(bicycle);
}
}

创建型-原型模式(Prototype)

原型模式适用于在需要大量创建相似对象时使用。它通过复制现有对象的方式来创建新对象,而无需重复进行初始化和构建过程

如果我们拥有一批构建好的不同种类的模板,且需要大量输出相同或相似的产品,那么可以使用原型模式把对应模板进行批量复制输出

这种方式特别适合于创建成本较高、创建过程复杂或者需要频繁创建的对象。通过复制现有的对象,我们可以节省创建对象的开销,并且可以方便地对克隆出的对象进行个性化定制。

示例:复制成品

假设我们有一个简单的图形类 Shape,它有一个 draw() 方法用于绘制图形。我们希望能够复制已有的图形对象,而无需知道具体的图形类型。

首先,我们需要创建一个抽象的原型接口 CloneableShape,它包含一个 clone() 方法用于复制图形对象。

1
2
3
4
public interface CloneableShape extends Cloneable {
@Override
public CloneableShape clone();
}

然后,我们实现具体的图形类,这里以矩形和圆形为例。它们都需要实现 CloneableShape 接口,并且在 clone() 方法中进行对象的复制。

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 CloneableShape {
private int width;
private int height;

public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}

public void draw() {
System.out.println("绘制矩形");
}

@Override
public CloneableShape clone() {
return new Rectangle(this.width, this.height);
}
}

public class Circle implements CloneableShape {
private int radius;

public Circle(int radius) {
this.radius = radius;
}

public void draw() {
System.out.println("绘制圆形");
}

@Override
public CloneableShape clone() {
return new Circle(this.radius);
}
}

接下来,我们可以创建一个图形缓存类 ShapeCache,用于存储已有的图形对象。在该类中,我们使用一个 HashMap 来存储图形对象,其中键为图形类型,值为对应的原型对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.HashMap;

public class ShapeCache {
private static HashMap<String, CloneableShape> shapeMap = new HashMap<>();

public static CloneableShape getShape(String shapeId) {
CloneableShape cachedShape = shapeMap.get(shapeId);
return (CloneableShape) cachedShape.clone();
}

public static void loadCache() {
shapeMap.put("rectangle", new Rectangle(10, 20));
shapeMap.put("circle", new Circle(30));
}
}

最后,我们可以使用原型模式来复制图形对象,而无需知道具体的图形类型。

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
ShapeCache.loadCache();

CloneableShape clonedShape1 = ShapeCache.getShape("rectangle");
clonedShape1.draw(); // 输出:绘制矩形

CloneableShape clonedShape2 = ShapeCache.getShape("circle");
clonedShape2.draw(); // 输出:绘制圆形
}
}

结构型-外观(Facade)

提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用

在外观模式中,子系统负责各自的功能实现,并且将其封装在具体的类中。外观类则作为一个集合器或者接口提供者,集中收集了子系统的操作,并且将其封装成简单易用的方法。

用户使用外观类时,只需要调用外观类提供的方法,而不需要关心底层子系统的复杂性和细节。外观类内部会根据用户的操作调用子系统的相应方法,完成一系列的操作。这样,外观类提供了一个简化的接口给用户,让用户可以更加方便地使用整个系统。

缺点:

  • 不符合开闭原则,修改很麻烦

示例:一键看电视

在下面代码示例中,可能会有人觉得外观模式和责任链模式目的是相似的,但是它们的目的和应用场景却是不同的。

外观模式更倾向于分类接口来方便调用,而责任链模式更倾向于让一串任务开始执行的开关

通过语音直接控制智能家电的开启和关闭:

  • 打开灯、打开电视、打开空调

  • 关闭灯、关闭电视、关闭空调

子系统:

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
// 子系统:灯
public class Light {
public void turnOn() {
System.out.println("灯已打开");
}

public void turnOff() {
System.out.println("灯已关闭");
}
}

// 子系统:电视
public class TV {
public void turnOn() {
System.out.println("电视已打开");
}

public void turnOff() {
System.out.println("电视已关闭");
}
}

// 子系统:空调
public class AirConditioner {
public void turnOn() {
System.out.println("空调已打开");
}

public void turnOff() {
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
// 外观类
public class SmartHomeFacade {
private Light light; // 灯子系统
private TV tv; // 电视子系统
private AirConditioner airConditioner; // 空调子系统

public SmartHomeFacade() {
light = new Light();
tv = new TV();
airConditioner = new AirConditioner();
}

/**
* 打开所有智能家电
*/
public void turnOnAllDevices() {
light.turnOn(); // 打开灯
tv.turnOn(); // 打开电视
airConditioner.turnOn(); // 打开空调
}

/**
* 关闭所有智能家电
*/
public void turnOffAllDevices() {
light.turnOff(); // 关闭灯
tv.turnOff(); // 关闭电视
airConditioner.turnOff(); // 关闭空调
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
public class VoiceControlDemo {
public static void main(String[] args) {
SmartHomeFacade smartHome = new SmartHomeFacade();

// 通过语音控制打开智能家电
smartHome.turnOnAllDevices();

// 通过语音控制关闭智能家电
smartHome.turnOffAllDevices();
}
}

结构型-适配器(Adapter)

适配器模式(Adapter pattern)可以将一个类的接口, 转换成客户期望的另一个接口。 适配器让原本接口不兼容的类可以合作无间。 对象适配器使用组合, 类适配器使用多重继承。我们一般用对象适配器

示例:TF卡转SD卡

现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来

SD卡接口和实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//SD卡的接口
public interface SDCard {
//读取SD卡方法
String readSD();
//写入SD卡功能
void writeSD(String msg);
}

//SD卡实现类
public class SDCardImpl implements SDCard {
public String readSD() {
return "sd card read a msg :hello word SD";
}

public void writeSD(String msg) {
System.out.println("sd card write msg : " + msg);
}
}

电脑类

1
2
3
4
5
6
7
8
9
public class Computer {

public String readSD(SDCard sdCard) {
if(sdCard == null) {
throw new NullPointerException("sd card null");
}
return sdCard.readSD();
}
}

TF卡接口和实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//TF卡接口
public interface TFCard {
//读取TF卡方法
String readTF();
//写入TF卡功能
void writeTF(String msg);
}

//TF卡实现类
public class TFCardImpl implements TFCard {

public String readTF() {
return "tf card read msg : hello word tf card";
}

public void writeTF(String msg) {
System.out.println("tf card write a msg : " + msg);
}
}

适配器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//创建适配器对象(SD兼容TF)
public class SDAdapterTF implements SDCard {

private TFCard tfCard;

public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}

public String readSD() {
System.out.println("adapter read tf card ");
return tfCard.readTF();
}

public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));

System.out.println("------------");

TFCard tfCard = new TFCardImpl();
SDAdapterTF adapter = new SDAdapterTF(tfCard);
System.out.println(computer.readSD(adapter));
}
}

结构型-桥接(Bridge)

桥接模式主要是为了解决两个维度的东西使用继承方式配对时造成的继承爆炸

现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系。但使用继承的话,后续不断的添加子类就会造成继承爆炸(子类过多)

颜色和形状是两个维度的东西。试想,在一个有多种可能会变化的维度的系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。

现在你大概理解了我们为什么要使用桥接,用正规的术语来描述使用场景,就是:

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

示例:不同系统播不同格式视频

需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式

在操作系统抽象类中不提供默认实现的原因是,父类无法确定子类应该如何具体实现播放操作。不同的操作系统版本可能会有特定的实现细节和环境差异,因此需要交给子类去实现

视频接口:

1
2
3
public interface VideoFile {
void decode(String fileName);
}

不同格式的视频实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// avi文件
public class AVIFile implements VideoFile {
@Override
public void decode(String fileName) {
System.out.println("avi视频文件:" + fileName);
}
}

// rmvb文件
public class REVBBFile implements VideoFile {
@Override
public void decode(String fileName) {
System.out.println("rmvb文件:" + fileName);
}
}

抽象的操作系统:

1
2
3
4
5
6
7
8
9
10
public abstract class OperatingSystemVersion {

protected VideoFile videoFile;

public OperatingSystemVersion(VideoFile videoFile) {
this.videoFile = videoFile;
}

public abstract void play(String fileName);
}

不同的操作系统子类:

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
// Windows系统
public class Windows extends OperatingSystemVersion {

public Windows(VideoFile videoFile) {
super(videoFile);
}

@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}

// Mac系统
public class Mac extends OperatingSystemVersion {

public Mac(VideoFile videoFile) {
super(videoFile);
}

@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}

测试类:

1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
OperatingSystem os = new Windows(new AVIFile());
os.play("三体");
}
}

为什么是抽象类对接口?

我们来重温一下接口和抽象类的区别:

抽象类和接口在设计上有不同的用途和特点,选择使用抽象类或接口取决于你的设计需求。

  1. 抽象类:

    • 抽象类可以包含成员变量、非抽象方法和抽象方法。
    • 抽象类可以提供默认实现,并且子类可以通过继承来复用这些实现。
    • 抽象类适合用于拥有共享代码和行为的相关类之间的继承关系。
  2. 接口:

    • 接口只能包含常量和抽象方法。接口中的方法没有默认实现,需要在实现时进行具体实现。
    • 类可以实现多个接口,从而具备多个接口定义的行为。这种多继承的特性使得接口在实现类的灵活性上更加强大。
    • 接口适合用于描述一组相似的行为,并且这些行为可以被不同的类实现。

因此,当你需要定义一组行为,并希望多个类实现这些行为时,使用接口是一个不错的选择。接口更加灵活并且支持类的多继承。

在代码片段中,VideoFile是一个接口,描述了视频文件的解码行为。OperatingSystemVersion是一个抽象类,提供了操作系统版本的通用行为,并将具体的视频文件解码行为委托给了实现了VideoFile接口的具体类。这样设计的好处是,可以通过定义不同的视频文件实现类来支持不同格式的视频文件解码,而不需要修改操作系统版本类。

总结来说,抽象类适用于共享代码和行为的继承关系,而接口适用于描述一组相似行为并让多个类实现。在特定的设计场景中,你可以选择使用抽象类、接口或二者的结合,以满足你的需求。

结构型 - 组合(Composite)

组合模式主要用来解决树形结构和嵌套结构的问题。它通过将对象组织成树状结构,使得我们可以统一处理单个对象和对象组合,从而简化对树形结构的操作和管理

组合模式主要包含三种角色:

  • 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
  • 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
  • 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

示例:软件菜单

如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称

1
2
3
4
5
6
7
8
9
- 主菜单
- 子菜单 1
- 菜单项 1.1
- 菜单项 1.2
- 子菜单 2
- 菜单项 2.1
- 菜单项 2.2
- 菜单项 2.3
- 子菜单 3

不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。

这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现。Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法。

举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,也可以根据自己的需要改写默认实现。

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 abstract class MenuComponent {

protected String name;
protected int level;

//添加菜单
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}

//移除菜单
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}

//获取指定的子菜单
public MenuComponent getChild(int i){
throw new UnsupportedOperationException();
}

//获取菜单名称
public String getName(){
return name;
}

public void print(){
throw new UnsupportedOperationException();
}
}

Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。

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
public class Menu extends MenuComponent {

private List<MenuComponent> menuComponentList;

public Menu(String name,int level){
this.level = level;
this.name = name;
menuComponentList = new ArrayList<MenuComponent>();
}

@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}

@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}

@Override
public MenuComponent getChild(int i) {
return menuComponentList.get(i);
}

@Override
public void print() {

for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}

MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MenuItem extends MenuComponent {

public MenuItem(String name,int level) {
this.name = name;
this.level = level;
}

@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
}
}

结构型 - 装饰(Decorator)

指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。简单来说,装饰者模式是为插件设计而生

示例:添加配菜

快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。使用继承方式实现的话扩展性差,且会造成继承爆炸。现在我们用装饰者模式实现

快餐接口:

1
2
3
4
5
6
7
8
9
10
11
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public abstract class FastFood {
private float price;
private String desc;

// 获取价格
public abstract float cost();
}

快餐实现类:

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 FriedRice extends FastFood {

public FriedRice() {
super(10, "炒饭");
}

@Override
public float cost() {
return getPrice();
}
}

//炒面
public class FriedNoodles extends FastFood {

public FriedNoodles() {
super(12, "炒面");
}

@Override
public float cost() {
return getPrice();
}
}

配菜抽象类

1
2
3
4
5
6
7
8
9
10
11
@Getter
@Setter
public abstract class Garnish extends FastFood {

private FastFood fastFood;

public Garnish(FastFood fastFood, float price, String desc) {
super(price, desc);
this.fastFood = fastFood;
}
}

配菜实现类:

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 Egg extends Garnish {

public Egg(FastFood fastFood) {
super(fastFood, 1, "鸡蛋");
}

    @Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

//培根配料
public class Bacon extends Garnish {

public Bacon(FastFood fastFood) {
super(fastFood, 2, "培根");
}

@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood food = new FriedRice();
//花费的价格
System.out.println(food.getDesc() + " " + food.cost() + "元");
System.out.println("========");

//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();
food1 = new Egg(food1);
//花费的价格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");
System.out.println("========");

//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}

结构型 - 享元(Flyweight)

我们可以将单例对象放到一个对象池中给大家共享,这样可以极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能

享元模式中的外部状态相对独立,且不影响内部状态

但同时,也有缺点:为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂

示例:俄罗斯方块

下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。

俄罗斯方块

俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。

1
2
3
4
5
6
7
public abstract class AbstractBox {
public abstract String getShape();

public void display(String color) {
System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
}
}

接下来就是定义不同的形状了,IBox类、LBox类、OBox类等。

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 IBox extends AbstractBox {

@Override
public String getShape() {
return "I";
}
}

public class LBox extends AbstractBox {

@Override
public String getShape() {
return "L";
}
}

public class OBox extends AbstractBox {

@Override
public String getShape() {
return "O";
}
}

提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。

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

private static HashMap<String, AbstractBox> map;

    public static final BoxFactory getInstance() {
return SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
private static final BoxFactory INSTANCE = new BoxFactory();
}

private BoxFactory() {
map = new HashMap<String, AbstractBox>();
AbstractBox iBox = new IBox();
AbstractBox lBox = new LBox();
AbstractBox oBox = new OBox();
map.put("I", iBox);
map.put("L", lBox);
map.put("O", oBox);
}

public AbstractBox getBox(String key) {
return map.get(key);
}
}

JDK源码示例:Integer

 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象

对于下面的代码

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
public static void main(String[] args) {
Integer i1 = 127;
Integer i2 = 127;
System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2)); // 输出true

Integer i3 = 128;
Integer i4 = 128;
System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4)); // 输出false
}
}

编译后有:

1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
Integer i1 = Integer.valueOf((int)127);
Integer i2 Integer.valueOf((int)127);
System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
Integer i3 = Integer.valueOf((int)128);
Integer i4 = Integer.valueOf((int)128);
System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
}
}

上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的就是 valueOf()

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
public final class Integer extends Number implements Comparable<Integer> {

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}
}

结构型 - 代理(Proxy)

代理模式的目的是为另一个对象提供一个替身或占位符以控制对这个对象的访问。

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

三种代理的对比:

  • 动态代理和静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

  • 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

JDK动态代理示例

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象

Java中提供了一个动态代理类ProxyProxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象:

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 卖票接口
public interface SellTickets {
void sell();
}

// 火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火车站卖票");
}
}

// 代理工厂,用来创建代理对象
public class ProxyFactory {
private TrainStation station = new TrainStation();

public SellTickets getProxyObject() {
// 使用Proxy获取代理对象
/*
newProxyInstance()方法参数说明:
ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
InvocationHandler h : 代理对象的调用处理程序
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
InvocationHandler中invoke方法参数说明:
proxy : 代理对象
method : 对应于在代理对象上调用的接口方法的 Method 实例
args : 代理对象调用接口方法时传递的实际参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
// 执行真实对象
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}

// 测试类
public class Client {
public static void main(String[] args) {
// 获取代理对象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}

请注意,ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具——Arthas查看代理类的结构:

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
package com.sun.proxy;

import com.itheima.proxy.dynamic.jdk.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}

public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final void sell() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}

从上面的类中,我们可以看到以下几个信息:

  • 代理类$Proxy0实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
  • 代理类$Proxy0将我们提供了的匿名内部类对象传递给了父类。

动态代理的执行流程是什么样的?

下面是摘取的重点代码:

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
//程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m3;

public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}

static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
}

public final void sell() {
this.h.invoke(this, m3, null);
}
}

//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
this.h = h;
}
}

//代理工厂类
public class ProxyFactory {

private TrainStation station = new TrainStation();

public SellTickets getProxyObject() {
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}


//测试访问类
public class Client {
public static void main(String[] args) {
//获取代理对象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}

执行流程如下:

  1. 在测试类中通过代理对象调用sell()方法
  2. 根据多态的特性,执行的是代理类$Proxy0中的sell()方法
  3. 代理类$Proxy0中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
  4. invoke方法通过反射执行了真实对象所属类TrainStation中的sell()方法

CGLIB动态代理

同样是上面的案例,我们再次使用CGLIB代理实现。

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

火车站:

1
2
3
4
5
6
public class TrainStation {

public void sell() {
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
public class ProxyFactory implements MethodInterceptor {

private TrainStation target = new TrainStation();

public TrainStation getProxyObject() {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer =new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(target.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TrainStation obj = (TrainStation) enhancer.create();
return obj;
}

/*
intercept方法参数说明:
o : 代理对象
method : 真实对象中的方法的Method实例
args : 实际参数
methodProxy :代理对象中的方法的method实例
*/
public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
return result;
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = factory.getProxyObject();

proxyObject.sell();
}
}

行为型 - 责任链(Chain Of Responsibility)

通过责任链模式, 你可以为某个请求创建一个对象链. 每个对象依序检查此请求并对其进行处理或者将它传给链中的下一个对象。

可以把责任链看做一条流水线,产品经过某个工序处理,或被中途丢弃,或被传到下一个工序处理。我们可以很方便的插入或去掉任意一道工序,而不影响其他工序的运行,就像链表一样。每道工序只需要接收一个固定的对象,也就是上下文

缺点:

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

示例:请假流程

现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行

上下文-请假条:

1
2
3
4
5
6
7
8
9
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LeaveRequest {
private String employeeName; // 员工姓名
private int days;
private boolean approved = false; // 默认未批准
private String reason; // 请假事由
}

审批者接口:

1
2
3
4
5
6
public interface Approver {
// 下一位审批者
void setNextApprover(Approver nextApprover);
// 上下文
void approveLeave(LeaveRequest request);
}

审批者实现类:

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
// 小组长
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TeamLeader implements Approver {
private Approver nextApprover;

public void approveLeave(LeaveRequest request) {
if (request.getDays() < 1) {
request.setApproved(true);
System.out.println("小组长同意" + request.getEmployeeName() + "的请假申请");
} else if (nextApprover != null) {
nextApprover.approveLeave(request);
}
}
}

// 部门经理
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DepartmentManager implements Approver {
private Approver nextApprover;

public void approveLeave(LeaveRequest request) {
if (request.getDays() >= 1 && request.getDays() <= 3) {
request.setApproved(true);
System.out.println("部门经理同意" + request.getEmployeeName() + "的请假申请");
} else if (nextApprover != null) {
nextApprover.approveLeave(request);
}
}
}

// 总经理
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GeneralManager implements Approver {
public void approveLeave(LeaveRequest request) {
if (request.getDays() > 3 && request.getDays() <= 7) {
request.setApproved(true);
System.out.println("总经理同意" + request.getEmployeeName() + "的请假申请");
} else {
request.setApproved(false);
System.out.println("请假超过7天,需要进一步审批");
}
}
}

请假流程控制类:

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 LeaveApprovalChain {
private Approver chain;

public LeaveApprovalChain() {
// 创建审批者实例
Approver teamLeader = new TeamLeader();
Approver departmentManager = new DepartmentManager();
Approver generalManager = new GeneralManager();

// 将责任链的头部设置为小组长
chain = teamLeader;

// 设置责任链的顺序
teamLeader.setNextApprover(departmentManager);
departmentManager.setNextApprover(generalManager);
}

public void processLeaveRequest(LeaveRequest request) {
chain.approveLeave(request);

if (!request.isApproved()) {
System.out.println(request.getEmployeeName() + "的请假申请被拒绝");
}
}
}

客户端调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {
public static void main(String[] args) {
LeaveRequest request1 = new LeaveRequest("张三", 0, "家庭原因"); // 张三请假0天
LeaveRequest request2 = new LeaveRequest("李四", 2, "病假"); // 李四请假2天
LeaveRequest request3 = new LeaveRequest("王五", 4, "探亲假"); // 王五请假4天
LeaveRequest request4 = new LeaveRequest("赵六", 8, "年休假"); // 赵六请假8天

LeaveApprovalChain chain = new LeaveApprovalChain();
chain.processLeaveRequest(request1);
chain.processLeaveRequest(request2);
chain.processLeaveRequest(request3);
chain.processLeaveRequest(request4);
}
}

运行客户端输出:

1
2
3
4
5
小组长同意张三的请假申请
部门经理同意李四的请假申请
总经理同意王五的请假申请
请假超过7天,需要进一步审批
赵六的请假申请被拒绝

行为型 - 策略(Strategy)

该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。

我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机……

作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。

上述例子的共同点是:我们可以自由选择策略来执行某个任务。

策略模式的主要角色如下:

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

示例:促销活动:

一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。

促销活动接口:

1
2
3
public interface Strategy {
void show();
}

具体策略角色-每个节日具体的促销活动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//为春节准备的促销活动A
public class StrategyA implements Strategy {
@Override
public void show() {
System.out.println("买一送一");
}
}

//为中秋准备的促销活动B
public class StrategyB implements Strategy {
    @Override
public void show() {
System.out.println("满200元减50元");
}
}

//为圣诞准备的促销活动C
public class StrategyC implements Strategy {
    @Override
public void show() {
System.out.println("满1000元加一元换购任意200元以下商品");
}
}

环境角色-推送者:

用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SalesMan {                        
//持有抽象策略角色的引用
private Strategy strategy;

public SalesMan(Strategy strategy) {
this.strategy = strategy;
}

//向客户展示促销活动
public void salesManShow(){
strategy.show();
}
}

客户端调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {
public static void main(String[] args) {
// 创建具体的促销活动对象
Strategy strategyA = new StrategyA(); // 春节促销活动
Strategy strategyB = new StrategyB(); // 中秋节促销活动
Strategy strategyC = new StrategyC(); // 圣诞节促销活动

// 创建销售员对象并设置不同的促销活动
SalesMan salesMan1 = new SalesMan(strategyA); // 绑定春节促销活动
SalesMan salesMan2 = new SalesMan(strategyB); // 绑定中秋节促销活动
SalesMan salesMan3 = new SalesMan(strategyC); // 绑定圣诞节促销活动

// 销售员向客户展示促销活动
salesMan1.salesManShow();
salesMan2.salesManShow();
salesMan3.salesManShow();
}
}

客户端输出:

1
2
3
买一送一
满200元减50元
满1000元加一元换购任意200元以下商品

行为型 - 模板方法(Template Method)

在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤。

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

优点:

  • 提高代码复用性

    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

  • 实现了反向控制

    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

示例:炒菜

注意:为防止恶意操作,一般模板方法都加上 final 关键词。

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。

抽象类:

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
public abstract class AbstractCook {

public final void cookProcess() {
//第一步:倒油
this.pourOil();
//第二步:热油
this.heatOil();
//第三步:倒蔬菜
this.pourVegetable();
//第四步:倒调味料
this.pourSauce();
//第五步:翻炒
this.fry();
}

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

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

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

//第四步:倒调味料是不一样
public abstract void pourSauce();


//第五步:翻炒是一样的,所以直接实现
public void fry(){
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
public class BaoCaiCook extends AbstractCook {

@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是包菜");
}

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

public class CaiXinCook extends AbstractCook {
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}

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

调用示例:

1
2
3
4
5
6
7
8
9
10
11
public class CookClient {
public static void main(String[] args) {
//炒手撕包菜
BaoCaiCook baoCai = new BaoCaiCook();
baoCai.cookProcess();

//炒蒜蓉菜心
CaiXinCook caiXin = new CaiXinCook();
caiXin.cookProcess();
}
}

调用示例输出:

1
2
3
4
5
6
7
8
9
10
11
倒油
热油
下锅的蔬菜是包菜
下锅的酱料是辣椒
炒啊炒啊炒到熟啊

倒油
热油
下锅的蔬菜是菜心
下锅的酱料是蒜蓉
炒啊炒啊炒到熟啊

行为型 - 命令模式(Command)

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。

命令模式包含以下主要角色:

  • 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

示例:饭店吃饭

  1. 点单

  2. 订单交给厨师

  3. 厨师备菜

将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。

  • 服务员: 就是调用者角色,由她来发起命令。

  • 资深大厨: 就是接收者角色,真正命令执行的对象。

  • 订单: 命令中包含订单。

pojo类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 订单类
@Getter
@Setter
@AllArgsConstructor
public class Order {
private String dishName;
}

// 服务员类
public class Waiter {
public void placeOrder(Command command) {
System.out.println("服务员接收到订单");
command.execute();
}
}

// 资深大厨类
public class SeniorChef {
public void cookDish(String dishName) {
System.out.println("资深大厨开始烹饪:" + dishName);
// 在这里执行具体的烹饪逻辑
System.out.println("资深大厨完成烹饪:" + dishName);
}
}

命令接口Command和具体命令类CookingCommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 命令接口
public interface Command {
void execute();
}

// 具体命令类
@AllArgsConstructor
public class CookingCommand implements Command {
private SeniorChef chef;
private Order order;

@Override
public void execute() {
chef.cookDish(order.getDishName());
}
}

测试输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CommandPatternExample {
public static void main(String[] args) {
Waiter waiter = new Waiter();
SeniorChef chef = new SeniorChef();

// 创建订单
Order order = new Order("红烧肉");

// 创建命令对象,并将订单和大厨传递进去
Command cookingCommand = new CookingCommand(chef, order);

// 服务员接收到订单,并发起命令
waiter.placeOrder(cookingCommand);
}
}

输出结果:

1
2
3
服务员接收到订单
资深大厨开始烹饪:红烧肉
资深大厨完成烹饪:红烧肉

行为型 - 观察者(Observer)

又被称为发布-订阅(Publish/Subscribe)模式。在对象之间定义一对多的依赖, 这样一来, 当一个对象改变状态, 依赖它的对象都会收到通知, 并自动更新。没错,就像公众号订阅。

示例:微信公众号

使用微信公众号时,当公众号有新内容更新,订阅者就会收到通知。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,一个公众号被多个微信用户订阅。

发布者接口Observable

1
2
3
4
5
6
7
8
9
10
public interface Observable {
// 添加订阅者
void addObserver(Observer observer);

// 删除订阅者
void removeObserver(Observer observer);

// 通知所有订阅者
void notifyObservers();
}

发布者实现类WechatAccount

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
// 微信公众号
@Getter
@Setter
public class WechatAccount implements Observable {
private String accountName;
private List<Observer> observers;
private String latestContent;

public WechatAccount(String accountName) {
this.accountName = accountName;
this.observers = new ArrayList<>();
}

@Override
public void addObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}

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

// 发布新内容方法
public void publishNewContent(String content) {
this.latestContent = content;
notifyObservers();
}
}

订阅者接口Observer

1
2
3
4
public interface Observer {
// 更新方法
void update(Observable observable);
}

订阅者实现类WechatUser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 微信用户
@Getter
public class WechatUser implements Observer {
private String userName;

public WechatUser(String userName) {
this.userName = userName;
}

@Override
public void update(Observable observable) {
if (observable instanceof WechatAccount) {
WechatAccount wechatAccount = (WechatAccount) observable;
String latestContent = wechatAccount.getLatestContent();
System.out.println("用户 " + userName + " 收到了公众号 " + wechatAccount.getAccountName() + " 的新内容:" + latestContent);
}
}
}

客户端示例:

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
public class ObserverPatternExample {
public static void main(String[] args) {
// 创建微信公众号
WechatAccount accountA = new WechatAccount("公众号A");
WechatAccount accountB = new WechatAccount("公众号B");

// 创建微信用户
WechatUser user1 = new WechatUser("用户A");
WechatUser user2 = new WechatUser("用户B");

// 用户A订阅公众号A
accountA.addObserver(user1);
// 用户A订阅公众号B
accountB.addObserver(user1);
// 用户B订阅公众号B
accountB.addObserver(user2);

// 发布新内容
accountA.publishNewContent("新内容A1");
accountB.publishNewContent("新内容B1");

// 用户A取消订阅公众号A
accountA.removeObserver(user1);

// 发布新内容
accountA.publishNewContent("新内容A2");
accountB.publishNewContent("新内容B2");
}
}

输出结果:

1
2
3
4
5
用户 用户A 收到了公众号 公众号A 的新内容:新内容A1
用户 用户A 收到了公众号 公众号B 的新内容:新内容B1
用户 用户B 收到了公众号 公众号B 的新内容:新内容B1
用户 用户A 收到了公众号 公众号A 的新内容:新内容A2
用户 用户B 收到了公众号 公众号B 的新内容:新内容B2

行为型 - 访问者(Visitor)

当你想要为一个对象的组合增加新的能力, 且封装并不重要时, 就使用访问者模式

示例:喂宠物

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食

被访问元素抽象类Animal

1
2
3
4
5
6
7
@Data
public abstract class Animal {
private String name;
private int age;

public abstract void accept(PetVisitor visitor);
}

被访问元素——宠物狗类Dog和宠物猫类Cat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Getter
@AllArgsConstructor
public class Dog extends Animal {
private String breed;

@Override
public void accept(PetVisitor visitor) {
visitor.visit(this);
}
}

// 具体的宠物猫类
@Getter
@AllArgsConstructor
public class Cat extends Animal {
private String color;

@Override
public void accept(PetVisitor visitor) {
visitor.visit(this);
}
}

访问者接口PetVisitor

1
2
3
4
public interface PetVisitor {
void visit(Dog dog);
void visit(Cat cat);
}

访问者实现类:

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
// 主人访问者
public class Owner implements PetVisitor {
@Override
public void visit(Dog dog) {
System.out.println("主人给狗狗 " + dog.getName() + " 喂食");
// 具体的喂食逻辑
}

@Override
public void visit(Cat cat) {
System.out.println("主人给猫咪 " + cat.getName() + " 喂食");
// 具体的喂食逻辑
}
}

// 其他人访问者
public class Others implements PetVisitor {
@Override
public void visit(Dog dog) {
System.out.println("其他人给狗狗 " + dog.getName() + " 喂食");
// 具体的喂食逻辑
}

@Override
public void visit(Cat cat) {
System.out.println("其他人给猫咪 " + cat.getName() + " 喂食");
// 具体的喂食逻辑
}
}

调用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class VisitorPatternExample {
public static void main(String[] args) {
// 创建具体的宠物对象
Animal dog = new Dog("旺财", "金毛");
Animal cat = new Cat("小花", "橘猫");

// 创建具体的访问者对象
PetVisitor owner = new Owner();
PetVisitor others = new Others();

// 被访问元素接受访问者的访问
dog.accept(owner);
cat.accept(others);
}
}

运行上述代码,输出将是:

1
2
主人给狗狗 旺财 喂食
其他人给猫咪 小花 喂食

行为型 - 状态(State)

允许对象在内部状态改变时改变它的行为

示例:自动售货机

自动售货机根据当前的状态来决定如何响应用户的操作。例如,在待机状态下,用户插入硬币可能会触发状态转换到选择商品的状态;而在售出商品状态下,用户取出商品后,状态会转换回待机状态。状态模式可以用于管理自动售货机的各种状态,并根据不同状态执行相应的操作。

自动售货机类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
public class VendingMachine {
private VendingMachineState state;

public void insertCoin() {
state.insertCoin();
}

public void selectProduct() {
state.selectProduct();
}
// 出货
public void dispenseProduct() {
state.dispenseProduct();
}
}

售货机状态接口:

1
2
3
4
5
public interface VendingMachineState {
void insertCoin();
void selectProduct();
void dispenseProduct();
}

状态实现类:

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
// 待机状态类
@RequiredArgsConstructor
class IdleState implements VendingMachineState {
private VendingMachine vendingMachine;

@Override
public void insertCoin() {
// 执行插入硬币操作
System.out.println("已插入硬币");
vendingMachine.setState(new SelectProductState(vendingMachine));
}

@Override
public void selectProduct() {
System.out.println("请先插入硬币");
}

@Override
public void dispenseProduct() {
System.out.println("请先选择商品");
}
}

// 选择商品状态类
@RequiredArgsConstructor
class SelectProductState implements VendingMachineState {
private VendingMachine vendingMachine;

@Override
public void insertCoin() {
System.out.println("已插入硬币,请选择商品");
}

@Override
public void selectProduct() {
// 执行选择商品操作
System.out.println("已选择商品");
vendingMachine.setState(new SoldOutState(vendingMachine));
}

@Override
public void dispenseProduct() {
System.out.println("请先选择商品");
}
}

// 售出商品状态类
@RequiredArgsConstructor
class SoldOutState implements VendingMachineState {
private VendingMachine vendingMachine;

@Override
public void insertCoin() {
System.out.println("商品已售罄");
}

@Override
public void selectProduct() {
System.out.println("商品已售罄");
}

@Override
public void dispenseProduct() {
System.out.println("商品已售罄");
}
}

调用示例:

1
2
3
4
5
6
7
8
9
10
public class VendingMachineExample {
public static void main(String[] args) {
VendingMachine vendingMachine = new VendingMachine();
vendingMachine.setState(new IdleState(vendingMachine));

vendingMachine.insertCoin();
vendingMachine.selectProduct();
vendingMachine.dispenseProduct();
}
}

输出:

1
2
3
已插入硬币
已选择商品
请先选择商品

行为型 - 解释器(Interpreter)

使用解释器模式为语言创建解释器,通常由语言的语法和语法分析来定义

示例:正则表达式引擎

正则表达式是一种强大的文本模式匹配工具,常用于字符串处理、文本搜索和替换等任务。正则表达式引擎使用解释器模式来解析和执行正则表达式。

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
// 抽象表达式接口
interface Expression {
/**
* 解释给定的输入是否符合表达式。
*
* @param input 输入字符串
* @return 如果输入符合表达式则返回true,否则返回false
*/
boolean interpret(String input);
}

// 终结符表达式类 - 字符表达式
@Getter
@AllArgsConstructor
class CharacterExpression implements Expression {
private final char character;

/**
* 解释给定的输入是否包含字符表达式中的字符。
*
* @param input 输入字符串
* @return 如果输入包含字符表达式中的字符则返回true,否则返回false
*/
@Override
public boolean interpret(String input) {
return input.contains(Character.toString(character));
}
}

// 非终结符表达式类 - 或表达式
@AllArgsConstructor
class OrExpression implements Expression {
private final Expression expression1;
private final Expression expression2;

/**
* 解释给定的输入是否符合或表达式的条件。
*
* @param input 输入字符串
* @return 如果输入符合或表达式的条件则返回true,否则返回false
*/
@Override
public boolean interpret(String input) {
return expression1.interpret(input) || expression2.interpret(input);
}
}

// 客户端代码
public class RegularExpressionEngine {
public static void main(String[] args) {
// 构建正则表达式:b|c
Expression expression = new OrExpression(
new CharacterExpression('b'),
new CharacterExpression('c')
);

// 输入字符串
String input = "abcd";

// 使用正则表达式引擎解析和执行正则表达式
boolean isMatch = expression.interpret(input);
System.out.println("Input: " + input);
System.out.println("Match result: " + isMatch); // Output: true
}
}

行为型 - 迭代器(Iterator)

提供一种方法顺序访问一个聚合对象中的各个元素, 而又不暴露其内部的表示

直接用for循环替代吧,没啥好说的

行为型 - 中介者(Mediator)

中介者模式(Mediator Pattern)用于解耦一组对象之间的交互关系。它通过引入一个中介者对象,将各个对象之间的交互逻辑集中处理。中介者模式的重点在于对象之间的通信和协调。

中介者vs外观

  • 中介者模式(Mediator Pattern)用于解耦一组对象之间的交互关系。它通过引入一个中介者对象,将各个对象之间的交互逻辑集中处理。中介者模式的重点在于对象之间的通信和协调。

  • 外观模式(Facade Pattern)主要用于简化复杂系统的接口。它提供了一个统一的接口,隐藏系统的复杂性,使得客户端可以更方便地访问子系统。外观模式的重点在于封装和简化接口。

  • 简单来说,中介者更像是一个中转站,而外观模式则是起点(入口)

示例:GUI开发

在GUI中,有多个组件(如按钮、文本框、下拉列表等),它们之间需要进行交互和通信。使用中介者模式可以将这些交互逻辑集中处理,并避免组件之间的紧耦合。

中介者接口Mediator,它包含了组件之间的交互方法:

1
2
3
public interface Mediator {
void notify(Component sender, String event);
}

中介者实现类GUIManager,实现中介者接口,负责协调组件之间的交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@AllArgsConstructor
public class GUIManager implements Mediator {
private Button button;
private TextBox textBox;
private ComboBox comboBox;

@Override
public void notify(Component sender, String event) {
if (sender == button && event.equals("click")) {
// 处理按钮点击事件的逻辑
String selectedValue = comboBox.getSelectedValue();
textBox.setText("Selected: " + selectedValue);
} else if (sender == textBox && event.equals("change")) {
// 处理文本框内容变化事件的逻辑
String text = textBox.getText();
comboBox.filterOptions(text);
}
}
}

组件类接口及其实现:

在中介者模式中,将Component定义为一个接口是为了强调组件的行为和协议,而不关注具体实现细节。

使用接口的好处是可以实现多重继承,即一个类可以同时实现多个接口。这样,在需要与其他组件进行交互时,组件类可以通过实现Component接口来获得中介者的引用,并实现中介者模式所需的方法。接口的使用还可以帮助实现松耦合,使得组件之间更加灵活和可替换。

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
public interface Component {
void setMediator(Mediator mediator);
void notify(String event);
}

public class Button implements Component {
private Mediator mediator;

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
public void notify(String event) {
mediator.notify(this, event);
}

public void click() {
notify("click");
}
}

public class TextBox implements Component {
private Mediator mediator;

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
public void notify(String event) {
mediator.notify(this, event);
}

public void change() {
notify("change");
}
}

public class ComboBox implements Component {
private Mediator mediator;

@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}

@Override
public void notify(String event) {
mediator.notify(this, event);
}

public void filterOptions(String text) {
// 根据文本过滤选项
}

public String getSelectedValue() {
// 获取选中的值
return null;
}
}

测试示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MediatorExample {
public static void main(String[] args) {
Button button = new Button();
TextBox textBox = new TextBox();
ComboBox comboBox = new ComboBox();

Mediator mediator = new GUIManager(button, textBox, comboBox);

button.setMediator(mediator);
textBox.setMediator(mediator);
comboBox.setMediator(mediator);

// 模拟按钮点击事件
button.click();

// 模拟文本框内容变化事件
textBox.change();
}
}

在这个示例中,各个组件通过中介者接口进行交互,而不是直接相互引用。中介者负责协调和处理组件之间的交互逻辑,从而实现了解耦和集中管理的效果。这样,当需要新增、修改或删除交互逻辑时,只需修改中介者类,而不需要修改多个组件类,提高了代码的可维护性和可扩展性。

行为型 - 备忘录(Memento)

当你需要让对象返回之前的状态时,可以使用备忘录模式

示例:恢复游戏存档

Originator(发起人)——游戏类:包含了当前游戏的状态数据,并提供了保存和恢复状态的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@AllArgsConstructor
public class Game {
private int level;
private String checkpoint;

public void play() {
// 在游戏中玩游戏并更新游戏状态
level++;
checkpoint = "Check Point " + level;
System.out.println("Playing game at Level " + level);
}

public GameState save() {
return new GameState(level, checkpoint);
}

public void restore(GameState gameState) {
this.level = gameState.getLevel();
this.checkpoint = gameState.getCheckpoint();
System.out.println("Restoring game at Level " + level + ", Check Point: " + checkpoint);
}
}

Memento(备忘录)——游戏状态类:它存储了游戏的状态数据。

1
2
3
4
5
6
@Data
public class GameState {
private final int level;
private final String checkpoint;
// 其他游戏状态相关的属性
}

Caretaker(管理者)——游戏管理类:负责保存和提供备忘录对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GameCareTaker {
private List<GameState> gameStateList = new ArrayList<>();

public void saveState(GameState gameState) {
gameStateList.add(gameState);
}

public GameState restoreState(int index) {
if (index >= 0 && index < gameStateList.size()) {
return gameStateList.get(index);
}
return null;
}
}

调用示例:

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
public class GameSaveDemo {
public static void main(String[] args) {
// 创建游戏对象
Game game = new Game();
// 创建游戏管理对象
GameCareTaker careTaker = new GameCareTaker();

// 进行游戏,修改状态
game.play();
// 保存当前游戏状态到存档
careTaker.saveState(game.save());

// 再次进行游戏,修改状态
game.play();
// 保存当前游戏状态到存档
careTaker.saveState(game.save());

// 恢复第一个存档
GameState savedState1 = careTaker.restoreState(0);
if (savedState1 != null) {
game.restore(savedState1);
game.printState(); // 打印恢复后的游戏状态
}

// 恢复第二个存档
GameState savedState2 = careTaker.restoreState(1);
if (savedState2 != null) {
game.restore(savedState2);
game.printState(); // 打印恢复后的游戏状态
}
}
}