设计模式

设计模式

单例模式

单例模式(Singleton Pattern)在设计模式中属于创建型模式。该模式涉及到一个单一的类,该类负责创建自己的实例对象,同时确保只有单个实例对象被创建,即一个类只有一个实例对象

注意:

  • 单例类只能有一个实例
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一实例

介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用实例:Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。

优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
  • 避免对资源的多重占用

使用场景:

  • WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

单例模式的几种实现方式

单例模式的实现有多种方式,如下所示:

懒汉式(线程不安全版本)

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static Singleton instance;
private Singleton (){}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

接下来介绍的几种实现方式都支持多线程,但是在性能上有所差异。

懒汉式(线程安全版本)

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

1
2
3
4
5
6
7
8
9
10
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

饿汉式

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
基于 classloader 机制在类加载时就创建实例,避免了多线程的同步问题。但也因此可能会造成资源浪费,如果该单例类的实例对象占用较多资源或需要延迟初始化,那么懒汉式单例模式可能更适合

1
2
3
4
5
6
7
public class Singleton {  
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {  
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

登记式/静态内部类

是否 Lazy 初始化:

是否多线程安全:

实现难度:一般

描述:****这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。如果实例化 instance 很消耗资源,想需要的时候再加载,即延迟加载,此时该方式可以考虑使用。

1
2
3
4
5
6
7
8
9
public class Singleton {  
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

枚举

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

1
2
3
4
5
public enum Singleton {  
INSTANCE;
public void whateverMethod() {
}
}

使用枚举方式实现单例模式是一种比较简单的方式,因为 Java 枚举类型本身就具有单例的特性。下面是使用枚举方式实现单例模式的代码示例:

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

// 添加其他属性和方法
private String name;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

在上述代码中,我们定义了一个名为 Singleton 的枚举类型,并在其中定义了一个枚举常量 INSTANCE,它就是我们所需要的单例对象。

使用枚举方式实现单例模式的原理是,在 Java 中,每个枚举常量都是唯一的,因此我们可以在枚举类型中直接将单例对象作为一个枚举常量来实现。在上述代码中,我们还可以添加其他的属性和方法来扩展单例对象的功能。例如,我们添加了一个名为 name 的属性和对应的 getter 和 setter 方法。使用枚举方式实现单例模式的优点是代码简单、易于理解和维护,并且能够保证线程安全和反序列化安全。此外,该方式还能避免使用传统的单例模式实现中可能出现的反射攻击、序列化攻击等安全问题

不使用枚举方式会产生什么安全问题?

当不使用枚举方式来实现单例模式时,可能会面临一些安全问题,例如在多线程环境下可能存在的竞态条件(race condition)和反射攻击。以下是一个简单的示例代码,展示了如何使用双重检查锁定方式创建单例对象,并说明了可能出现的安全问题:

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

private Singleton() {
// 私有构造函数
}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

双重检查锁定方式虽然能够在多线程环境下延迟初始化单例对象并保证性能,但是如果未使用volatile修饰静态成员变量,由于指令重排序的存在,可能会导致其他线程在 instance 还没有完全初始化时就访问了它,从而产生未定义行为。此外,当使用反射机制强行调用私有构造函数来创建多个实例时,也可能会破坏单例模式的约束,从而导致系统功能异常。因此,推荐在实现单例模式时使用枚举方式或者其他线程安全的单例模式实现,以避免可能存在的安全问题。

为什么由于指令重排序的存在,可能会导致其他线程在 instance 还没有完全初始化时就访问了它,从而产生未定义行为?

指令重排序是指在编译器或处理器的优化过程中,为了提高程序性能,可能会对指令的执行顺序进行重新排序。这种重排序在单线程环境下不会影响程序的正确性,但在多线程环境下可能会导致可见性和有序性问题。以下是一个示例代码,展示了使用双重检查锁定方式实现的单例模式,在多线程环境下可能出现的指令重排序问题:

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

private Singleton() {
// 私有构造函数
}

public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 实例化对象
}
}
}
return instance;
}
}

在上述代码中,第一次检查 instance 是否为 null 是为了避免不必要的同步开销,而第二次检查是为了确保只有一个线程能够创建实例对象。但是,由于指令重排序的存在,可能会导致以下情况发生:

  1. 线程 A 进入 getInstance() 方法,此时 instance 为 null。
  2. 线程 A 进入同步块,并执行 instance = new Singleton(),但是这个过程实际分三步执行

** 2.1 为 instance 分配内存空间**

** 2.2 初始化instance**

** 2.3 将 instance 指向分配的内存地址**

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 A 执行了 1 和 3,此时 线程 B 调用 getInstance() 后发现 instance 不为空,因此返回 instance ,但此时 instance 还未被初始化,可能会产生未定义行为

为了避免这个问题,可以使用 volatile 关键字修饰 instance,以确保可见性和有序性。修改后的代码如下:

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

private Singleton() {
// 私有构造函数
}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

通过使用 volatile 关键字修饰 instance,可以禁止指令重排序,并确保在多线程环境下正确地返回已经完全初始化的单例对象。

为什么在不使用枚举方式创建单例模式时,当使用反射机制强行调用私有构造函数来创建多个实例时,也可能会破坏单例模式的约束,从而导致系统功能异常?

在不使用枚举方式创建单例模式时,通过反射机制强行调用私有构造函数来创建多个实例可能会破坏单例模式的约束,导致系统功能异常。这是因为反射机制能够绕过类的访问控制,包括私有构造函数,从而使得即使私有构造函数已经被设计成只能被单例类自身调用,也可以被外部调用

以下是一个简单的示例代码,演示了如何使用反射机制来破坏单例模式:

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

private Singleton() {
// 私有构造函数
}

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
public class Main {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();

try {
// 使用反射获取私有构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
// 打破访问限制
constructor.setAccessible(true);

// 通过反射调用私有构造函数,创建第二个实例
Singleton instance2 = constructor.newInstance();

System.out.println(instance1);
System.out.println(instance2);
} catch (Exception e) {
e.printStackTrace();
}
}
}

在上述代码中,我们使用反射机制获取了 Singleton 类的私有构造函数,并设置其可访问性,然后通过 newInstance() 方法创建了第二个实例。这样就绕过了单例模式的约束,在运行时创建了多个实例,从而破坏了单例模式的设计意图

由于单例模式的核心思想是确保一个类只有一个实例,并提供全局访问点,如果出现了多个实例,可能会导致系统功能异常或数据不一致的问题。因此,当设计单例模式时,需要考虑到可能存在的反射攻击,可以考虑使用枚举方式实现单例模式,因为枚举类型在 Java 中天然地具有单例特性,并且不会受到反射攻击的影响。

经验之谈一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

建造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的

介绍

意图:****将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示

主要解决:主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定

何时使用:****一些基本部件不会变,而其组合经常变化的时候

如何解决:****将变与不变分离开

应用实例: 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的”套餐”。

优点:

  • 分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示
  • 可以更好地控制构建过程,隐藏具体构建细节
  • 代码复用性高,可以在不同的构建过程中重复使用相同的建造者。

缺点:

  • 如果产品的属性较少,建造者模式可能会导致代码冗余
  • 建造者模式增加了系统的类和对象数量

使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

建造者模式在创建复杂对象时非常有用,特别是当对象的构建过程涉及多个步骤或参数时。它可以提供更好的灵活性和可维护性,同时使得代码更加清晰可读。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

实现

我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。

我们将创建一个表示食物(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。

然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo 类使用 MealBuilder 来创建一个 Meal。

步骤 1

创建一个表示食物条目和食物包装的接口。

1
2
3
4
5
public interface Item {
public String name();
public Packing packing();
public float price();
}
1
2
3
public interface Packing {
public String pack();
}

步骤 2

创建实现 Packing 接口的实体类。

1
2
3
4
5
6
7
public class Wrapper implements Packing {

@Override
public String pack() {
return "纸盒装";
}
}
1
2
3
4
5
6
7
public class Bottle implements Packing {

@Override
public String pack() {
return "瓶装";
}
}

步骤 3

创建实现 Item 接口的抽象类,该类提供了默认的功能。

1
2
3
4
5
6
7
8
9
10
public abstract class Burger implements Item {

@Override
public Packing packing() {
return new Wrapper();
}

@Override
public abstract float price();
}
1
2
3
4
5
6
7
8
9
10
public abstract class ColdDrink implements Item {

@Override
public Packing packing() {
return new Bottle();
}

@Override
public abstract float price();
}

步骤 4

创建扩展了 Burger 和 ColdDrink 的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
public class VegBurger extends Burger {

@Override
public float price() {
return 25.0f;
}

@Override
public String name() {
return "素食汉堡";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class ChickenBurger extends Burger {

@Override
public float price() {
return 50.5f;
}

@Override
public String name() {
return "鸡肉汉堡";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Coke extends ColdDrink {

@Override
public float price() {
return 30.0f;
}

@Override
public String name() {
return "可口可乐";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Pepsi extends ColdDrink {

@Override
public float price() {
return 35.0f;
}

@Override
public String name() {
return "百事可乐";
}
}

步骤 5

创建一个 Meal 类(套餐),带有上面定义的 Item 对象。

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

public class Meal {
private List<Item> items = new ArrayList<Item>();

public void addItem(Item item){
items.add(item);
}

public float getCost(){
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}

public void showItems(){
for (Item item : items) {
System.out.print("Item : "+item.name());
System.out.print(", Packing : "+item.packing().pack());
System.out.println(", Price : "+item.price());
}
}
}

步骤 6

创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。

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

public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}

public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}

步骤 7

BuiderPatternDemo 使用 MealBuilder 来演示建造者模式(Builder Pattern)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();

Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("素食汉堡 和 可口可乐的套餐 Meal");
vegMeal.showItems();
System.out.println("总花费: " +vegMeal.getCost());

Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\n鸡肉汉堡 和 百事可乐套餐 Meal");
nonVegMeal.showItems();
System.out.println("总花费: " +nonVegMeal.getCost());
}
}

步骤 8

执行程序,输出结果:

1
2
3
4
5
6
7
8
9
10
素食汉堡 和 可口可乐的套餐 Meal
Item : 素食汉堡, Packing : 纸盒装, Price : 25.0
Item : 可口可乐, Packing : 瓶装, Price : 30.0
总花费: 55.0


鸡肉汉堡 和 百事可乐套餐 Meal
Item : 鸡肉汉堡, Packing : 纸盒装, Price : 50.5
Item : 百事可乐, Packing : 瓶装, Price : 35.0
总花费: 85.5

原型模式

原型模式(Prototype Pattern)用于创建重复的对象,属于创建型模式

当直接创建对象的代价比较大时,则采用这种模式用于创建当前对象的克隆。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

介绍

意图:****用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

主要解决:****在运行期建立和删除原型

关键代码: 1.实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone()

2.原型模式同样用于隔离类对象的使用者具体类型之间的耦合关系,它同样要求这些”易变类”拥有稳定的接口或属于同一个抽象类的子类

实现

我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。

PrototypePatternDemo 类使用 ShapeCache 类来获取 Shape 对象。

步骤 1

创建一个实现了 Cloneable 接口的抽象类

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 abstract class Shape implements Cloneable {

private String id;
protected String type;

abstract void draw();

public String getType(){
return type;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}

步骤 2

创建扩展了上面抽象类的实体类

1
2
3
4
5
6
7
8
9
10
11
public class Rectangle extends Shape {

public Rectangle(){
type = "长方形";
}

@Override
public void draw() {
System.out.println("这是长方形的画图方法");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Square extends Shape {

public Square(){
type = "正方形";
}

@Override
public void draw() {
System.out.println("这是正方形的画图方法");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Circle extends Shape {

public Circle(){
type = "圆形";
}

@Override
public void draw() {
System.out.println("这是圆形的画图方法");
}
}

步骤 3

创建一个类,从数据库获取实体类,并把它们存储在一个 Hashtable 中。

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

public class ShapeCache {

private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();

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

// 对每种形状都运行数据库查询,并创建该形状
// shapeMap.put(shapeKey, shape);
// 例如,我们要添加三种形状
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);

Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);

Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(),rectangle);
}
}

步骤 4

PrototypePatternDemo 使用 ShapeCache 类来获取存储在 Hashtable 中的形状的克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();

Shape clonedShape1 = (Shape) ShapeCache.getShape("1");
clonedShape1.draw();
System.out.println("Shape : " + clonedShape1.getType());

Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
clonedShape2.draw();
System.out.println("Shape : " + clonedShape2.getType());

Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
clonedShape3.draw();
System.out.println("Shape : " + clonedShape3.getType());
}
}

步骤 5

执行程序,输出结果:

1
2
3
4
5
6
这是圆形的画图方法
Shape : 圆形
这是正方形的画图方法
Shape : 正方形
这是长方形的画图方法
Shape : 长方形

适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,属于结构型模式,结合了两个独立接口的功能

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

我们通过下面的实例来演示适配器模式的使用。其中,音频播放器设备只能播放 mp3 文件,通过使用一个更高级的音频播放器来播放 vlc 和 mp4 文件。

介绍

意图:****将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

主要解决:****主要解决在软件系统中,常常要对”现存的类”存在的局限性,即不能完成某些任务的情况,通过引入新的类实现功能上的补充,而且要求调用者对新引入的类无感知

何时使用: 1.系统需要使用现有的类,而此类的接口不符合系统的需要

2.通过接口转换,将一个类插入另一个类系中

如何解决:****继承或依赖(推荐)

关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。

优点: 1.可以让任何两个没有关联的类一起运行。 2.提高了类的复用。 3.增加了类的透明度。 4.灵活性好

缺点: 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

使用场景:****有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式

注意事项:****适配器不是在详细设计时添加的,而是解决正在服役的项目的问题,在项目运行期间需要适当定制化地修改类结构

实现

我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件(最初的类-功能并不强大,出现了不能满足我们需求的现状)。

我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。

我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。

AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,调用者不需要知道能播放所需格式音频的实际类(即VlcPlayer或者Mp4Player)。AdapterPatternDemo 类使用 AudioPlayer 类来播放各种格式。

步骤 1

为媒体播放器和更高级的媒体播放器创建接口。

1
2
3
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
1
2
3
4
public interface AdvancedMediaPlayer { 
public void playVlc(String fileName);
public void playMp4(String fileName);
}

步骤 2

创建实现了 AdvancedMediaPlayer 接口的实体类。

1
2
3
4
5
6
7
8
9
10
11
public class VlcPlayer implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
System.out.println("播放VLC文件. 文件名为: "+ fileName);
}

@Override
public void playMp4(String fileName) {
// VLC播放器播放不了MP4,所以此处什么也不做 只是为了实现接口
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Mp4Player implements AdvancedMediaPlayer{

@Override
public void playVlc(String fileName) {
// MP4播放器播放不了VLC,所以此处什么也不做 只是为了实现接口
}

@Override
public void playMp4(String fileName) {
System.out.println("播放MP4文件. 文件名为: "+ fileName);
}
}

步骤 3

创建实现了 MediaPlayer 接口的适配器类。

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

AdvancedMediaPlayer advancedMusicPlayer;

public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc") ){
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}

@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}

步骤 4

创建实现了 MediaPlayer 接口的实体类。

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 AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;

@Override
public void play(String audioType, String fileName) {

//播放 mp3 音乐文件的内置支持
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("播放MP3文件. 文件名为: "+ fileName);
}
//mediaAdapter 提供了播放其他文件格式的支持
else if(audioType.equalsIgnoreCase("vlc")
|| audioType.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
}
else{
System.out.println("无效的播放文件. "+
audioType + " 格式不支持");
}
}
}

步骤 5

使用 AudioPlayer 来播放不同类型的音频格式。

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

audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}

步骤 6

执行程序,输出结果:

1
2
3
4
播放MP3文件. 文件名为: beyond the horizon.mp3
播放MP4文件. 文件名为: alone.mp4
播放VLC文件. 文件名为: far far away.vlc
无效的播放文件. avi 格式不支持

桥接模式

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化,属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦

这种模式涉及到一个作为桥接的****接口,使得实体类的功能独立于接口实现类,这两种类型的类可被结构化改变而互不影响。桥接模式的目的是将抽象与实现分离,使它们可以独立地变化,该模式通过将一个对象的抽象部分与它的实现部分分离,使它们可以独立地改变。它通过组合的方式,而不是继承的方式,将抽象和实现的部分连接起来

我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。

介绍

意图:将抽象部分与实现部分分离,使它们都可以独立的变化。

主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活

何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。

如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。

关键代码:抽象类依赖实现类。

优点: 1.抽象和实现的分离。 2.优秀的扩展能力。 3.实现细节对客户透明。

缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

使用场景:

1.如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,****避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

2.对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

3.一个类存在两个独立变化的维度,且这两个维度都需要进行扩展

注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。

以下是桥接模式的几个关键角色:

  • 抽象(Abstraction):定义抽象类,通常包含对实现接口的引用。
  • 扩展抽象(Refined Abstraction):对抽象的扩展,可以是抽象类的子类或具体实现类
  • 实现(Implementor):定义实现接口,提供基本操作的接口。
  • 具体实现(Concrete Implementor):实现接口的具体类

实现

我们有一个作为桥接实现的 DrawAPI 接口和实现了 DrawAPI 接口的实体类 RedCircle_、_GreenCircle_。_Shape 是一个抽象类,将使用 DrawAPI 的对象。BridgePatternDemo 类使用 Shape 类来画出不同颜色的圆。

步骤 1

创建桥接实现接口。

1
2
3
public interface DrawAPI {
public void drawCircle(int radius, int x, int y);
}

步骤 2

创建实现了 DrawAPI 接口的实体桥接实现类。

1
2
3
4
5
6
7
8
9
10
11
/**
* @author 不吃辣的Chris
* @create 2024-02-17-21:34
*/
public class RedCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("画一个圆形,颜色为red,半径为:"
+ radius +", x为: " +x+", y为 "+ y +"]");
}
}
1
2
3
4
5
6
7
8
9
10
11
/**
* @author 不吃辣的Chris
* @create 2024-02-17-21:34
*/
public class GreenCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("画一个圆形,颜色为green,半径为:"
+ radius +", x为: " +x+", y为 "+ y +"]");
}
}

步骤 3

使用 DrawAPI 接口创建抽象类 _Shape_。

1
2
3
4
5
6
7
8
9
10
11
/**
* @author 不吃辣的Chris
* @create 2024-02-17-21:35
*/
public abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}

步骤 4

创建实现了 Shape 抽象类的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author 不吃辣的Chris
* @create 2024-02-17-21:35
*/
public class Circle extends Shape {
private int x, y, radius;

public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}

public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}

步骤 5

使用 ShapeDrawAPI 类画出不同颜色的圆。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author 不吃辣的Chris
* @create 2024-02-17-21:35
*/
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(100,100, 10, new RedCircle());
Shape greenCircle = new Circle(50,20, 6, new GreenCircle());

redCircle.draw();
greenCircle.draw();
}
}

步骤 6

执行程序,输出结果:

1
2
画一个圆形,颜色为red,半径为:10, x为: 100, y为 100]
画一个圆形,颜色为green,半径为:6, x为: 50, y为 20]

过滤器模式

过滤器模式(Filter Pattern)允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准

实现

我们将创建一个 Person 对象、Criteria 接口和实现了该接口的实体类,来过滤 Person 对象的列表。CriteriaPatternDemo 类使用 Criteria 对象,基于各种标准和它们的结合来过滤 Person 对象的列表。

步骤 1

创建一个类,在该类上应用标准。

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 Person {
// 名字
private String name;
// 性别
private String gender;
// 婚姻状况
private String maritalStatus;

public Person(String name,String gender,String maritalStatus){
this.name = name;
this.gender = gender;
this.maritalStatus = maritalStatus;
}

public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getMaritalStatus() {
return maritalStatus;
}
}

步骤 2

为标准(Criteria)创建一个接口。

1
2
3
4
5
import java.util.List;

public interface Criteria {
public List<Person> meetCriteria(List<Person> persons);
}

步骤 3

创建实现了 Criteria 接口的实体类。

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

public class CriteriaMale implements Criteria {

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> malePersons = new ArrayList<Person>();
for (Person person : persons) {
if(person.getGender().equalsIgnoreCase("男")){
malePersons.add(person);
}
}
return malePersons;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.List;

public class CriteriaFemale implements Criteria {

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> femalePersons = new ArrayList<Person>();
for (Person person : persons) {
if(person.getGender().equalsIgnoreCase("女")){
femalePersons.add(person);
}
}
return femalePersons;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.List;

public class CriteriaSingle implements Criteria {

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> singlePersons = new ArrayList<Person>();
for (Person person : persons) {
if(person.getMaritalStatus().equalsIgnoreCase("单身")){
singlePersons.add(person);
}
}
return singlePersons;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.List;

public class AndCriteria implements Criteria {

private Criteria criteria;
private Criteria otherCriteria;

public AndCriteria(Criteria criteria, Criteria otherCriteria) {
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstCriteriaPersons = criteria.meetCriteria(persons);
return otherCriteria.meetCriteria(firstCriteriaPersons);
}
}
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
import java.util.List;

public class OrCriteria implements Criteria {

private Criteria criteria;
private Criteria otherCriteria;

public OrCriteria(Criteria criteria, Criteria otherCriteria) {
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}

@Override
public List<Person> meetCriteria(List<Person> persons) {
List<Person> firstCriteriaItems = criteria.meetCriteria(persons);
List<Person> otherCriteriaItems = otherCriteria.meetCriteria(persons);

for (Person person : otherCriteriaItems) {
if(!firstCriteriaItems.contains(person)){
firstCriteriaItems.add(person);
}
}
return firstCriteriaItems;
}
}

步骤4

使用不同的标准(Criteria)和它们的结合来过滤 Person 对象的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.ArrayList; 
import java.util.List;

public class CriteriaPatternDemo {
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>();

persons.add(new Person("Robert","男", "单身"));
persons.add(new Person("John","男", "已婚"));
persons.add(new Person("Laura","女", "已婚"));
persons.add(new Person("Diana","女", "单身"));
persons.add(new Person("Mike","男", "单身"));
persons.add(new Person("Bobby","男", "单身"));

Criteria male = new CriteriaMale();
Criteria female = new CriteriaFemale();
Criteria single = new CriteriaSingle();
Criteria singleMale = new AndCriteria(single, male);
Criteria singleOrFemale = new OrCriteria(single, female);

System.out.println("男生们: ");
printPersons(male.meetCriteria(persons));

System.out.println("\n女生们: ");
printPersons(female.meetCriteria(persons));

System.out.println("\n单身男生们: ");
printPersons(singleMale.meetCriteria(persons));

System.out.println("\n单身的人 或者 女生: ");
printPersons(singleOrFemale.meetCriteria(persons));
}

public static void printPersons(List<Person> persons){
for (Person person : persons) {
System.out.println("Person : [ 名字 : " + person.getName()
+", 性别 : " + person.getGender()
+", 婚姻状况 : " + person.getMaritalStatus()
+" ]");
}
}
}

步骤 5

执行程序,输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
男生们: 
Person : [ 名字 : Robert, 性别 : 男, 婚姻状况 : 单身 ]
Person : [ 名字 : John, 性别 : 男, 婚姻状况 : 已婚 ]
Person : [ 名字 : Mike, 性别 : 男, 婚姻状况 : 单身 ]
Person : [ 名字 : Bobby, 性别 : 男, 婚姻状况 : 单身 ]

女生们:
Person : [ 名字 : Laura, 性别 : 女, 婚姻状况 : 已婚 ]
Person : [ 名字 : Diana, 性别 : 女, 婚姻状况 : 单身 ]

单身男生们:
Person : [ 名字 : Robert, 性别 : 男, 婚姻状况 : 单身 ]
Person : [ 名字 : Mike, 性别 : 男, 婚姻状况 : 单身 ]
Person : [ 名字 : Bobby, 性别 : 男, 婚姻状况 : 单身 ]

单身的人 或者 女生:
Person : [ 名字 : Robert, 性别 : 男, 婚姻状况 : 单身 ]
Person : [ 名字 : Diana, 性别 : 女, 婚姻状况 : 单身 ]
Person : [ 名字 : Mike, 性别 : 男, 婚姻状况 : 单身 ]
Person : [ 名字 : Bobby, 性别 : 男, 婚姻状况 : 单身 ]
Person : [ 名字 : Laura, 性别 : 女, 婚姻状况 : 已婚 ]

组合模式

组合模式(Composite Pattern),依据树形结构来组合对象,用来表示部分以及整体层次。属于结构型模式。这种模式创建了一个包含自己对象组的类****,该类提供了修改相同对象组的方式

我们通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构。

介绍

意图:将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

何时使用: 需要表示对象的部分-整体层次结构(树形结构)

使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。

组合模式的核心角色包括:

  • 组件(Component):
    • 定义了组合中所有对象的通用接口,可以是抽象类或接口。它声明了用于访问和管理子组件的方法,包括添加、删除、获取子组件等。
  • 叶子节点(Leaf):
    • 表示组合中的叶子节点对象,叶子节点没有子节点。它实现了组件接口的方法,但通常不包含子组件。
  • 复合节点(Composite):
    • 表示组合中的复合对象,复合节点可以包含子节点,可以是叶子节点,也可以是其他复合节点。它实现了组件接口的方法,包括管理子组件的方法。
  • 客户端(Client):
    • 通过组件接口与组合结构进行交互,客户端不需要区分叶子节点和复合节点,可以一致地对待整体和部分。

实现

我们有一个类 Employee_,该类被当作组合模型类。_CompositePatternDemo 类使用 Employee 类来添加部门层次结构,并打印所有员工。

步骤 1

创建 Employee 类,该类带有 Employee 对象的列表。

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

public class Employee {
private String name;
private String dept;
private int salary;
private List<Employee> subordinates;

//构造函数
public Employee(String name,String dept, int sal) {
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList<Employee>();
}

public void add(Employee e) {
subordinates.add(e);
}

public void remove(Employee e) {
subordinates.remove(e);
}

public List<Employee> getSubordinates(){
return subordinates;
}

public String toString(){
return ("Employee :[ 名字 : "+ name
+", 岗位 : "+ dept + ", 薪水 :"
+ salary+" ]");
}
}

步骤 2

使用 Employee 类来创建和打印员工的层次结构。

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 class CompositePatternDemo {
public static void main(String[] args) {
Employee CEO = new Employee("John","CEO", 30000);

// 头部销售员
Employee headSales = new Employee("Robert","头部销售员", 20000);
// 头部营销员
Employee headMarketing = new Employee("Michel","头部营销员", 20000);

// 普通销售员
Employee salesExecutive1 = new Employee("Richard","普通销售员", 10000);
Employee salesExecutive2 = new Employee("Rob","普通销售员", 10000);

// 普通营销员
Employee clerk1 = new Employee("Laura","普通营销员", 10000);
Employee clerk2 = new Employee("Bob","普通营销员", 10000);

CEO.add(headSales);
CEO.add(headMarketing);

headSales.add(salesExecutive1);
headSales.add(salesExecutive2);

headMarketing.add(clerk1);
headMarketing.add(clerk2);

//打印该组织的所有员工
System.out.println(CEO);
System.out.println();
for (Employee headEmployee : CEO.getSubordinates()) {
System.out.println(headEmployee);
for (Employee employee : headEmployee.getSubordinates()) {
System.out.println(employee);
}
System.out.println();
}
}
}

步骤 3

执行程序,输出结果为:

1
2
3
4
5
6
7
8
9
Employee :[ 名字 : John, 岗位 : CEO, 薪水 :30000 ]

Employee :[ 名字 : Robert, 岗位 : 头部销售员, 薪水 :20000 ]
Employee :[ 名字 : Richard, 岗位 : 普通销售员, 薪水 :10000 ]
Employee :[ 名字 : Rob, 岗位 : 普通销售员, 薪水 :10000 ]

Employee :[ 名字 : Michel, 岗位 : 头部营销员, 薪水 :20000 ]
Employee :[ 名字 : Laura, 岗位 : 普通营销员, 薪水 :10000 ]
Employee :[ 名字 : Bob, 岗位 : 普通营销员, 薪水 :10000 ]

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构,属于结构型模式,它是作为现有的类的一个包装。

该模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。我们通过下面的实例来演示装饰器模式的用法。其中,我们将把不同的形状装饰上相同的颜色,同时又不改变形状类

介绍

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能

缺点:多层装饰比较复杂。

使用场景: 1.扩展一个类的功能。 2.动态增加功能,动态撤销。

装饰器模式包含以下几个核心角色:

  • 抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。
  • 具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。
  • 抽象装饰器(Decorator):继承自抽象组件,它包含了一个抽象组件对象,并定义了与抽象组件相同的接口,同时可以通过组合方式持有其他装饰器对象。
  • 具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。

装饰器模式通过嵌套包装多个装饰器对象,可以实现多层次的功能增强。每个具体装饰器类都可以选择性地增加新的功能,同时保持对象接口的一致性。

实现

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 _ShapeDecorator_,并把 Shape 对象作为它的实例变量。

RedShapeDecorator 是实现了 ShapeDecorator 的实体类。

DecoratorPatternDemo 类使用 RedShapeDecorator 来装饰 Shape 对象。

步骤 1

创建一个接口,对应上述抽象组件:

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

步骤 2

创建实现接口的实体类,对应上述具体组件。

1
2
3
4
5
6
7
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("形状: 长方形");
}
}
1
2
3
4
5
6
7
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("形状: 圆形");
}
}

步骤 3

创建实现了 Shape 接口的抽象装饰类,对应上述抽象装饰器。

1
2
3
4
5
6
7
8
9
10
11
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;

public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}

public void draw(){
decoratedShape.draw();
}
}

步骤 4

创建扩展了 ShapeDecorator 类的实体装饰类,对应上述具体装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RedShapeDecorator extends ShapeDecorator {

public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}

@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}

private void setRedBorder(Shape decoratedShape){
System.out.println("边框颜色: 红色");
}
}

步骤 5

使用 RedShapeDecorator 来装饰 Shape 对象。

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

Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
//Shape redCircle = new RedShapeDecorator(new Circle());
//Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("===================未加边框的圆形===================");
circle.draw();

System.out.println("\n===================红色边框的圆形===================");
redCircle.draw();

System.out.println("\n===================红色边框的长方形===================");
redRectangle.draw();
}
}

步骤 6

执行程序,输出结果:

1
2
3
4
5
6
7
8
9
10
===================未加边框的圆形===================
形状: 圆形

===================红色边框的圆形===================
形状: 圆形
边框颜色: 红色

===================红色边框的长方形===================
形状: 长方形
边框颜色: 红色

装饰器模式和代理模式有什么区别?

装饰器模式和代理模式是两种常见的设计模式,它们都属于结构型模式,但在用途和实现方式上有一些区别。

  1. 装饰器模式(Decorator Pattern):
    • 用途:装饰器模式的主要作用是动态地给一个对象添加一些额外的职责,而不需要在子类中静态地增加这些功能。它可以在不改变原始对象的基础上,通过给对象”包裹”一层装饰器来动态扩展其功能。
  2. 代理模式(Proxy Pattern):
    • 用途:代理模式的主要作用是控制对对象的访问,可以在访问对象时进行一些控制或者间接访问。代理模式可以用于实现对远程对象、创建开销大的对象或者保护对象的访问。
    • 实现方式:代理模式通过创建一个代理类来替代原始对象,并在代理类中实现对原始对象的控制访问。代理类通常会持有对原始对象的引用,并在调用原始对象之前或之后执行一些额外的逻辑。

总的来说,装饰器模式注重在不改变原始对象的基础上建立一个模板,给被传入的对象添加相同的功能,而代理模式注重对对象间接访问,不同代理类添加的功能不同

装饰器模式是制定了一个装饰模板,只要放进这个模板的被装饰对象,都会得到这个装饰模板带来的相同的功能上的增强,同时不影响被装饰对象本身(例如上面不管是放进装饰器RedShapeDecorator中的是圆形Circle还是长方形Rectangle,都会得到加上红色边框的装饰,也叫功能上的增强)。而同一个被代理类 的 不同代理类 对 被代理类 的增强逻辑是各不相同的,不存在一个“增强模板”(装饰模板)一说。

外观模式

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口,属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用

介绍

意图:****为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用

主要解决:****降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口

何时使用: 1.客户端不需要知道系统内部的复杂联系,整个系统只需提供一个”接待员”即可。 2.定义系统的入口

优点:****封装多个类的功能至一个类,只需要一个类就可以实现对多个类中方法的调用

缺点:****不符合开闭原则,如果要改东西很麻烦,继承重写都不合适

使用场景: 1.为复杂的模块或子系统提供外界访问的模块。 2.子系统相对独立。

外观模式涉及以下核心角色:

  • 外观(Facade):
    • 提供一个简化的接口,封装了系统的复杂性。外观模式的客户端通过与外观对象交互,而无需直接与系统的各个组件打交道。
  • 子系统(Subsystem):
    • 由多个相互关联的类组成,负责系统的具体功能。外观对象通过调用这些子系统来完成客户端的请求。
  • 客户端(Client):
    • 使用外观对象来与系统交互,而不需要了解系统内部的具体实现。

实现

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。下一步是定义一个外观类 _ShapeMaker_。

ShapeMaker 类使用实体类来代表用户对这些类的调用。FacadePatternDemo 类使用 ShapeMaker 类来显示结果。

步骤 1

创建一个接口:

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

步骤 2

创建实现接口的实体类,对应上述的子系统:

1
2
3
4
5
6
7
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("画一个长方形");
}
}
1
2
3
4
5
6
7
public class Square implements Shape {

@Override
public void draw() {
System.out.println("画一个正方形");
}
}
1
2
3
4
5
6
7
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("画一个圆形");
}
}

步骤 3

创建一个外观类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;

public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}

public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}

步骤 4

创建一个客户端,使用该外观类画出各种类型的形状:

1
2
3
4
5
6
7
8
9
public class FacadePatternDemo {
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();

shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
}

步骤 5

执行程序,输出结果:

1
2
3
画一个圆形
画一个长方形
画一个正方形

享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能,属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。

介绍

主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用: 1.系统中有大量对象。 2.这些对象消耗大量内存。3.有共有特性可以抽取

如何解决:唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

关键代码:用 HashMap 存储这些对象。

优点:减少对象的创建,降低系统的内存,使效率提高。

缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱

使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。

注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。

享元模式包含以下几个核心角色:

  • 享元工厂(Flyweight Factory):
    • 负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象。
  • 抽象享元(Flyweight):
    • 定义了具体享元和非共享享元的接口,通常包含了设置外部状态的方法。
  • 具体享元(Concrete Flyweight):
    • 实现了抽象享元接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递
  • 客户端(Client):
    • 使用享元工厂获取享元对象,并通过设置外部状态来操作享元对象。客户端通常不需要关心享元对象的具体实现。

实现

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类 _Circle_。下一步是定义工厂类 _ShapeFactory_。

ShapeFactory 有一个 CircleHashMap_,其中键名为 Circle 对象的颜色。无论何时接收到请求,都会创建一个特定颜色的圆。_ShapeFactory 检查它的 HashMap 中的 circle 对象,如果找到 Circle 对象,则返回该对象,否则将创建一个存储在 hashmap 中以备后续使用的新对象,并把该对象返回到客户端。

FlyWeightPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(_red / green / blue/ black / white_),以便获取它所需对象的颜色。

步骤 1

创建一个接口。

1
2
3
4
5
public interface Shape {
void draw();

public void setColor(String color);
}

步骤 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
public class Circle implements Shape {
private String color;
private int x;
private int y;
private int radius;

public Circle(String color){
this.color = color;
}

public void setX(int x) {
this.x = x;
}

public void setY(int y) {
this.y = y;
}

public void setRadius(int radius) {
this.radius = radius;
}

@Override
public void setColor(String color) {
this.color = color;
}

@Override
public void draw() {
System.out.println("画圆 [颜色 : " + color
+", x轴坐标 : " + x +", y轴坐标 :" + y +", 半径 :" + radius);
}
}

步骤 3

创建一个工厂,生成基于给定信息的实体类的对象。

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

public class ShapeFactory {
private static final HashMap<String, Shape> circleMap = new HashMap<>();

public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);

if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("创建的圆的颜色为 : " + color);
}
return circle;
}
}

步骤 4

使用该工厂,通过传递颜色信息来获取实体类的对象。

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 FlyweightPatternDemo {
private static final String colors[] =
{ "红色", "绿色", "蓝色", "白色", "黑色" };
public static void main(String[] args) {

for(int i=0; i < 20; ++i) {
Circle circle =
(Circle)ShapeFactory.getCircle(getRandomColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}

System.out.println("===============实践演示未注意内外部状态的区分使用导致的问题===============");
Circle circle1 = (Circle)ShapeFactory.getCircle("青色");
circle1.setRadius(10);
circle1.setX(1);
circle1.setY(2);
circle1.draw();

circle1.setColor("紫色");
circle1.draw();
Circle circle2 = (Circle)ShapeFactory.getCircle("青色");
circle2.draw();

}
private static String getRandomColor() {
return colors[(int)(Math.random()*colors.length)];
}
private static int getRandomX() {
return (int)(Math.random()*100 );
}
private static int getRandomY() {
return (int)(Math.random()*100);
}
}

步骤 5

执行程序,输出结果:

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
创建的圆的颜色为 : 红色
画圆 [颜色 : 红色, x轴坐标 : 61, y轴坐标 :19, 半径 :100
创建的圆的颜色为 : 绿色
画圆 [颜色 : 绿色, x轴坐标 : 23, y轴坐标 :2, 半径 :100
画圆 [颜色 : 绿色, x轴坐标 : 30, y轴坐标 :63, 半径 :100
创建的圆的颜色为 : 黑色
画圆 [颜色 : 黑色, x轴坐标 : 48, y轴坐标 :84, 半径 :100
画圆 [颜色 : 红色, x轴坐标 : 14, y轴坐标 :61, 半径 :100
画圆 [颜色 : 黑色, x轴坐标 : 44, y轴坐标 :13, 半径 :100
画圆 [颜色 : 红色, x轴坐标 : 46, y轴坐标 :43, 半径 :100
画圆 [颜色 : 绿色, x轴坐标 : 58, y轴坐标 :49, 半径 :100
创建的圆的颜色为 : 白色
画圆 [颜色 : 白色, x轴坐标 : 14, y轴坐标 :13, 半径 :100
画圆 [颜色 : 黑色, x轴坐标 : 44, y轴坐标 :28, 半径 :100
画圆 [颜色 : 白色, x轴坐标 : 13, y轴坐标 :10, 半径 :100
创建的圆的颜色为 : 蓝色
画圆 [颜色 : 蓝色, x轴坐标 : 15, y轴坐标 :93, 半径 :100
画圆 [颜色 : 黑色, x轴坐标 : 87, y轴坐标 :39, 半径 :100
画圆 [颜色 : 白色, x轴坐标 : 61, y轴坐标 :53, 半径 :100
画圆 [颜色 : 蓝色, x轴坐标 : 32, y轴坐标 :75, 半径 :100
画圆 [颜色 : 红色, x轴坐标 : 8, y轴坐标 :9, 半径 :100
画圆 [颜色 : 红色, x轴坐标 : 51, y轴坐标 :69, 半径 :100
画圆 [颜色 : 黑色, x轴坐标 : 95, y轴坐标 :16, 半径 :100
画圆 [颜色 : 白色, x轴坐标 : 49, y轴坐标 :46, 半径 :100
画圆 [颜色 : 红色, x轴坐标 : 45, y轴坐标 :60, 半径 :100
===============实践演示未注意内外部状态的区分使用导致的问题===============
创建的圆的颜色为 : 青色
画圆 [颜色 : 青色, x轴坐标 : 1, y轴坐标 :2, 半径 :10
画圆 [颜色 : 紫色, x轴坐标 : 1, y轴坐标 :2, 半径 :10
画圆 [颜色 : 紫色, x轴坐标 : 1, y轴坐标 :2, 半径 :10

责任链模式

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦,属于行为型模式

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推

介绍

意图:****避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

应用实例:****SpringSecurity中的过滤器链

优点: 1.降低耦合度。它将请求的发送者和接收者解耦。 2.简化了对象。使得对象不需要知道链的结构。 3.增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4.增加新的请求处理类很方便。

缺点: 1.不能保证请求一定被接收。 2.系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3.可能不容易观察运行时的特征,有碍于除错。

使用场景: 1.有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2.在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3.可动态指定一组对象处理请求。

主要涉及到以下几个核心角色:

  • 抽象处理者(Handler):
    • 定义一个处理请求的接口,通常包含一个处理请求的方法(如 handleRequest)和一个指向下一个处理者的引用(后继者)。
  • 具体处理者(ConcreteHandler):
    • 实现了抽象处理者接口,负责处理请求。如果能够处理该请求,则直接处理;否则,将请求传递给下一个处理者。
  • 客户端(Client):
    • 创建处理者对象,并将它们连接成一条责任链。通常,客户端只需要将请求发送给责任链的第一个处理者,无需关心请求的具体处理过程

实现

我们创建抽象类 _AbstractLogger_,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 _AbstractLogger_。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

步骤 1

创建抽象的记录器类。

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 abstract class AbstractLogger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;

protected int level;

//责任链中的下一个元素
protected AbstractLogger nextLogger;

public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}

public void logMessage(int level, String message){
if(this.level <= level){
write(message);
}
if(nextLogger !=null){
nextLogger.logMessage(level, message);
}
}

abstract protected void write(String message);

}

步骤 2

创建扩展了该记录器类的实体类。

1
2
3
4
5
6
7
8
9
10
11
public class ErrorLogger extends AbstractLogger {

public ErrorLogger(int level){
this.level = level;
}

@Override
protected void write(String message) {
System.out.println("错误打印日志: " + message);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class FileLogger extends AbstractLogger {

public FileLogger(int level){
this.level = level;
}

@Override
protected void write(String message) {
System.out.println("Debug打印日志: " + message);
}
}

1
2
3
4
5
6
7
8
9
10
11
public class ConsoleLogger extends AbstractLogger {

public ConsoleLogger(int level){
this.level = level;
}

@Override
protected void write(String message) {
System.out.println("控制台打印日志: " + message);
}
}

步骤 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
public class ChainPatternDemo {

private static AbstractLogger getChainOfLoggers(){

AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);

errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);

return errorLogger;
}

public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();

loggerChain.logMessage(AbstractLogger.INFO, "这是一条普通日志信息");
System.out.println("==================================");
loggerChain.logMessage(AbstractLogger.DEBUG,
"这是一条debug日志信息");
System.out.println("==================================");
loggerChain.logMessage(AbstractLogger.ERROR,
"这是一条错误日志信息");
}
}

步骤 4

执行程序,输出结果:

1
2
3
4
5
6
7
8
控制台打印日志: 这是一条普通日志信息
==================================
Debug打印日志: 这是一条debug日志信息
控制台打印日志: 这是一条debug日志信息
==================================
错误打印日志: 这是一条错误日志信息
Debug打印日志: 这是一条错误日志信息
控制台打印日志: 这是一条错误日志信息

命令模式

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令

介绍

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

如何解决:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。

优点: 1.降低了系统耦合度。 2.新的命令可以方便地添加至系统。

缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

使用场景:认为是命令的地方都可以使用命令模式,比如GUI 中每一个按钮都是一条命令

主要涉及到以下几个核心角色:

  • 命令(Command):
    • 定义了执行操作的接口,通常包含一个 execute 方法,用于调用具体的操作。
  • 具体命令(ConcreteCommand):
    • 实现了命令接口,负责执行具体的操作。它通常包含了对接收者的引用,通过调用接收者的方法来完成请求的处理。
  • 接收者(Receiver):
    • 知道如何执行与请求相关的操作,实际执行命令的对象。
  • 调用者/请求者(Invoker):
    • 发送命令的对象,它包含了一个命令对象并能触发命令的执行。调用者并不直接处理请求,而是通过将请求传递给具体命令对象来实现。
  • 客户端(Client):
    • 创建具体命令对象并设置其接收者,将命令对象交给调用者执行。

实现

我们首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。

Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo 类使用 Broker 类来演示命令模式。

步骤 1

创建一个命令接口:

1
2
3
public interface Order {
void execute();
}

步骤 2

创建一个请求类,对应上述关键角色中的接收者:

1
2
3
4
5
6
7
8
9
10
11
12
public class Stock {

private String name = "汽车玩具";
private int quantity = 10;

public void buy(){
System.out.println("Stock [ 购买的物品为: "+name+", 数量为: " + quantity +" ] 购买行为");
}
public void sell(){
System.out.println("Stock [ 销售的物品为: "+name+", 数量为: " + quantity +" ] 销售行为");
}
}

步骤 3

创建实现了 Order 接口的实体类,对应上述关键角色中的具体命令:

1
2
3
4
5
6
7
8
9
10
11
public class BuyStock implements Order {
private Stock abcStock;

public BuyStock(Stock abcStock){
this.abcStock = abcStock;
}

public void execute() {
abcStock.buy();
}
}
1
2
3
4
5
6
7
8
9
10
11
public class SellStock implements Order {
private Stock abcStock;

public SellStock(Stock abcStock){
this.abcStock = abcStock;
}

public void execute() {
abcStock.sell();
}
}

步骤 4

创建命令调用类,对应上述关键角色中的调用者/请求者:

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

public class Broker {
private List<Order> orderList = new ArrayList<Order>();

public void takeOrder(Order order){
orderList.add(order);
}

public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}

步骤 5

使用 Broker 类来接受并执行命令,对应上述关键角色中的客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CommandPatternDemo {
public static void main(String[] args) {
Stock abcStock = new Stock();

BuyStock buyStockOrder = new BuyStock(abcStock);
SellStock sellStockOrder = new SellStock(abcStock);

Broker broker = new Broker();
broker.takeOrder(buyStockOrder);
broker.takeOrder(sellStockOrder);

broker.placeOrders();
}
}

步骤 6

执行程序,输出结果:

1
2
Stock [ 购买的物品为: 汽车玩具, 数量为: 10 ] 购买行为
Stock [ 销售的物品为: 汽车玩具, 数量为: 10 ] 销售行为

解释器模式

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等

介绍

意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

主要解决:对于一些固定文法构建一个解释句子的解释器。

何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

优点: 1.可扩展性比较好,灵活。 2.增加了新的解释表达式的方式。 3.易于实现简单文法。

缺点: 1.可利用场景比较少。 2.对于复杂的文法比较难维护。 3.解释器模式会引起类膨胀。

实现

我们将创建一个接口 Expression 和实现了 Expression 接口的实体类定义作为上下文中主要解释器的 TerminalExpression 类,其他的类 OrExpression、AndExpression 用于创建组合式表达式

InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。

步骤 1

创建一个表达式接口。

1
2
3
public interface Expression {
public boolean interpret(String context);
}

步骤 2

创建实现了上述接口的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TerminalExpression implements Expression {

private String data;

public TerminalExpression(String data){
this.data = data;
}

@Override
public boolean interpret(String context) {
if(context.contains(data)){
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class OrExpression implements Expression {

private Expression expr1 = null;
private Expression expr2 = null;

public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}

@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AndExpression implements Expression {

private Expression expr1 = null;
private Expression expr2 = null;

public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}

@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}

步骤 3

InterpreterPatternDemo 使用 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
public class InterpreterPatternDemo {

//规则:Robert 或 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}

//规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}

public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();

System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married women? "
+ isMarriedWoman.interpret("Married Julie"));
}
}

步骤 4

执行程序,输出结果:

1
2
John is male? true
Julie is a married women? true

迭代器模式

迭代器模式(Iterator Pattern)是用于顺序访问集合对象的元素的一种设计模式,属于行为型模式

介绍

主要解决:不同的方式来遍历对象中的存放的多个元素。

关键代码:定义接口,接口中包含 用于判断是否存在下一个元素的 hasNext 方法 和 获取下一个元素的next 方法

应用实例:JAVA 中的 iterator。

优点: 1.它支持以不同的方式遍历一个聚合对象。 2.迭代器简化了聚合类。 3.在同一个聚合上可以有多个遍历。 4.在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

使用场景: 1.访问一个聚合对象的内容而无须暴露它的内部表示。 2.需要为聚合对象提供多种遍历方式。 3.为遍历不同的聚合结构提供一个统一的接口。

注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据

实现

我们将创建一个 Iterator 接口和一个返回迭代器的 Container 接口,实现了 Container 接口的实体类可以存储数据,同时也提供了一个方法返回一个迭代器,用于遍历自身存储的数据。最后创建一个用于测试的类IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names。

步骤 1

创建接口:

1
2
3
4
public interface Iterator {
public boolean hasNext();
public Object next();
}
1
2
3
public interface Container {
public Iterator getIterator();
}

步骤 2

创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 _NameIterator_。

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 NameRepository implements Container {
public String[] names = {"张三" , "李四" ,"王五" , "赵六"};

@Override
public Iterator getIterator() {
return new NameIterator();
}

private class NameIterator implements Iterator {

int index;

@Override
public boolean hasNext() {
if(index < names.length){
return true;
}
return false;
}

@Override
public Object next() {
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}

步骤 3

使用 NameRepository 来获取迭代器,并打印名字。

1
2
3
4
5
6
7
8
9
10
11
public class IteratorPatternDemo {

public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
Iterator iter = namesRepository.getIterator();
while (iter.hasNext()){
String name = (String)iter.next();
System.out.println("名字 : " + name);
}
}
}

步骤 4

执行程序,输出结果:

1
2
3
4
名字 : 张三
名字 : 李四
名字 : 王五
名字 : 赵六

中介者模式

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性的一种设计模式,属于行为型模式。该模式提供了一个通常用于处理不同类之间的通信的中介类,并支持松耦合,使代码易于维护

介绍

意图:****用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互

主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。

何时使用:多个类相互耦合,形成了网状结构。

如何解决:将上述网状结构分离为星型结构。

关键代码:对象之间的通信封装到一个类中单独处理。

应用实例: 1.中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2.机场调度系统。

优点: 1.降低了类的复杂度,将一对多转化成了一对一。 2.各个类之间的解耦

缺点:****中介者会庞大,变得复杂难以维护

使用场景: 1.系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2.想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

注意事项:****不应当在职责混乱的时候使用

实现

我们通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 ChatRoom 和 User。User 对象使用 ChatRoom 方法来分享他们的消息。

MediatorPatternDemo,我们的演示类使用 User 对象来显示他们之间的通信。

步骤 1

创建中介者类。

1
2
3
4
5
6
7
8
import java.util.Date;

public class ChatRoom {
public static void showMessage(User user, String message){
System.out.println(new Date().toString()
+ " [" + user.getName() +"] : " + message);
}
}

步骤 2

创建 user 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class User {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

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

public void sendMessage(String message){
ChatRoom.showMessage(this,message);
}
}

步骤 3

使用 User 对象来显示他们之间的通信。

1
2
3
4
5
6
7
8
9
public class MediatorPatternDemo {
public static void main(String[] args) {
User chris = new User("Chris");
User alice = new User("Alice");

chris.sendMessage("Hi! Alice!");
alice.sendMessage("Hello! Chris!");
}
}

步骤 4

执行程序,输出结果:

1
2
Thu Feb 29 22:03:34 CST 2024 [Chris] : Hi! Alice!
Thu Feb 29 22:03:34 CST 2024 [Alice] : Hello! Chris!

备忘录模式

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象,属于行为型模式

介绍

主要解决:****备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态

何时使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有”后悔药”可吃。

如何解决:通过一个备忘录类专门存储对象状态。

关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。

优点: 1.给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2.实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:消耗资源:如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景: 1.需要保存/恢复数据的相关状态场景。 2.提供一个可回滚的操作。

实现

备忘录模式使用三个类 Memento、Originator 和 CareTaker。Originator 的状态从 Memento 对象中获取。Caretaker 对象负责保存历史状态记录,恢复对象的某个历史状态。

MementoPatternDemo,我们的演示类使用 CareTaker 和 Originator 对象来显示对象的状态恢复。

步骤 1

创建 Memento 类。

1
2
3
4
5
6
7
8
9
10
11
public class Memento {
private String state;

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

public String getState(){
return state;
}
}

步骤 2

创建 Originator 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Originator {
private String state;

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

public String getState(){
return state;
}

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

public void getStateFromMemento(Memento Memento){
state = Memento.getState();
}
}

步骤 3

创建 CareTaker 类。

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

public class CareTaker {
private List<Memento> mementoList = new ArrayList<Memento>();

public void add(Memento state){
mementoList.add(state);
}

public Memento get(int index){
return mementoList.get(index);
}
}

步骤 4

使用 CareTaker 和 Originator 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("状态1");
originator.setState("状态2");
careTaker.add(originator.saveStateToMemento());
originator.setState("状态3");
careTaker.add(originator.saveStateToMemento());
originator.setState("状态4");

System.out.println("当前状态: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("第一次保存的状态: " + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("第二次保存的状态: " + originator.getState());
}
}

步骤 5

验证输出。

1
2
3
当前状态: 状态4
第一次保存的状态: 状态2
第二次保存的状态: 状态3

观察者模式

观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新,属于行为型模式。当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。

介绍

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。

优点: 1.观察者和被观察者是抽象耦合的。 2.建立一套触发机制。

缺点: 1.如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会时间开销较大。 2.如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3.观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

观察者模式包含以下几个核心角色:

  • 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
  • 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
  • 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
  • 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。

观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。

实现

观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。

ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。

步骤 1

创建 Subject 类:

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

public class Subject {

private List<Observer> observers = new ArrayList<Observer>();
private int state;

public int getState() {
return state;
}

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

public void attach(Observer observer){
observers.add(observer);
}

public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}

步骤 2

创建 Observer 类。

1
2
3
4
public abstract class Observer {
protected Subject subject;
public abstract void update();
}

步骤 3

创建实体观察者类。

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

public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

@Override
public void update() {
// 十进制转二进制
System.out.println( "二进制字符串格式的state: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OctalObserver extends Observer{

public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

@Override
public void update() {
// 十进制转八进制
System.out.println( "八进制字符串格式的state: "
+ Integer.toOctalString( subject.getState() ) );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HexaObserver extends Observer{

public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}

@Override
public void update() {
// 十进制转十六进制
System.out.println( "十六进制字符串格式的state: "
+ Integer.toHexString( subject.getState() ).toUpperCase() );
}
}

步骤 4

使用 Subject 和实体观察者对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();

new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);

System.out.println("第一次状态更新,更新为: 15");
subject.setState(15);
System.out.println("第二次状态更新,更新为: 10");
subject.setState(10);
}
}

步骤 5

执行程序,输出结果:

1
2
3
4
5
6
7
8
9
第一次状态更新,更新为: 15
十六进制字符串格式的state: F
八进制字符串格式的state: 17
二进制字符串格式的state: 1111

第二次状态更新,更新为: 10
十六进制字符串格式的state: A
八进制字符串格式的state: 12
二进制字符串格式的state: 1010

状态模式

状态模式(State Pattern)中,类的行为基于其状态改变,属于行为型模式

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的上下文 context 对象,****客户端只需要与上下文对象进行交互,而不需要了解具体状态对象的切换和行为实现细节

介绍

主要解决:****对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为

何时使用:代码中包含大量与对象状态有关的条件语句。

如何解决:将各种具体的状态类抽象出来。

关键代码:通常命令模式的接口中只有一个方法,而状态模式的接口中有一个或者多个方法。状态模式一般和对象的状态有关,实现类的方法有不同的功能,一般是改变实例变量的状态值。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。

应用实例: 打篮球的时候运动员可以有正常状态、不正常状态和超常状态

优点:1.封装了转换规则

2.枚举可能的状态,在枚举状态之前需要确定状态种类

3.将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为

4.允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块

5.可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数

缺点: 1.状态模式的使用必然会增加系统类和对象的个数

2.状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱

3.状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码

使用场景: 1.行为随状态改变而改变的场景。 2.条件、分支语句的代替者。

注意事项:行为受状态约束的时候使用状态模式,而且状态不超过 5 个

实现

我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。

StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。

步骤 1

创建一个接口。

1
2
3
public interface State {
public void doAction(Context context);
}

步骤 2

创建实现接口的实体类。

1
2
3
4
5
6
7
8
9
10
11
public class StartState implements State {

public void doAction(Context context) {
System.out.println("播放器处于开始状态");
context.setState(this);
}

public String toString(){
return "开始状态";
}
}
1
2
3
4
5
6
7
8
9
10
11
public class StopState implements State {

public void doAction(Context context) {
System.out.println("播放器处于结束状态");
context.setState(this);
}

public String toString(){
return "结束状态";
}
}

步骤 3

创建 Context 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Context {
private State state;

public Context(){
state = null;
}

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

public State getState(){
return state;
}
}

步骤 4

使用 Context 来查看当状态 State 改变时的行为变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
System.out.println();

StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}

步骤 5

执行程序,输出结果:

1
2
3
4
5
播放器处于开始状态
开始状态

播放器处于结束状态
结束状态

空对象模式

在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查,可以在数据不可用的时候提供默认的行为。在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

实现

我们将创建一个定义操作的 AbstractCustomer 抽象类,和扩展了 AbstractCustomer 类的实体类。工厂类 CustomerFactory 基于客户传递的名字来返回 RealCustomer 或 NullCustomer 对象。

NullPatternDemo,我们的演示类使用 CustomerFactory 来演示空对象模式的用法。

步骤 1

创建一个抽象类。

1
2
3
4
5
public abstract class AbstractCustomer {
protected String name;
public abstract boolean isNil();
public abstract String getName();
}

步骤 2

创建扩展了上述类的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RealCustomer extends AbstractCustomer {

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

@Override
public String getName() {
return name;
}

@Override
public boolean isNil() {
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class NullCustomer extends AbstractCustomer {

@Override
public String getName() {
return "不存在该用户";
}

@Override
public boolean isNil() {
return true;
}
}

步骤 3

创建 CustomerFactory 类。

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

public static final String[] names = {"Chris", "Joe", "Julie"};

public static AbstractCustomer getCustomer(String name){
for (int i = 0; i < names.length; i++) {
if (names[i].equalsIgnoreCase(name)){
return new RealCustomer(name);
}
}
return new NullCustomer();
}
}

步骤 4

使用 CustomerFactory,基于客户传递的名字,来获取 RealCustomer 或 NullCustomer 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NullPatternDemo {
public static void main(String[] args) {

AbstractCustomer customer1 = CustomerFactory.getCustomer("Chris");
AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");

System.out.println(customer1.getName());
System.out.println(customer2.getName());
System.out.println(customer3.getName());
System.out.println(customer4.getName());
}
}

步骤 5

执行程序,输出结果:

1
2
3
4
Chris
不存在该用户
Julie
不存在该用户

访问者模式

访问者模式(Visitor Pattern)中使用了一个访问者类,改变了元素类的执行算法,通过这种方式,元素的执行算法可以随着访问者改变而改变,属于行为型模式

介绍

意图:主要将数据结构数据操作分离。

主要解决:****稳定的数据结构和易变的操作耦合问题

何时使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中。

如何解决:在被访问的类里面加一个对外提供接待访问者的接口。

关键代码:****在数据基础类里面有一个方法接受访问者,将自身引用传入访问者

优点: 1.符合单一职责原则。 2.优秀的扩展性。 3.灵活性。

缺点: 1.具体元素对访问者公布细节,违反了迪米特原则。 2.具体元素变更比较困难。 3.违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

使用场景: 1.对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,也****不希望在增加新操作时修改这些类

实现

我们将创建一个定义接受操作的 ComputerPart 接口。Keyboard_、_Mouse_、_MonitorComputer 是实现了 ComputerPart 接口的实体类。我们将定义另一个接口 ComputerPartVisitor_,它定义了访问者类的操作,_Computer 使用实体访问者来执行相应的动作。

步骤 1

定义一个表示元素的接口。

1
2
3
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}

步骤 2

创建扩展了上述类的实体类。

1
2
3
4
5
6
7
public class Keyboard  implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
1
2
3
4
5
6
7
public class Monitor  implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
1
2
3
4
5
6
7
public class Mouse  implements ComputerPart {

@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Computer implements ComputerPart {

ComputerPart[] parts;

public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}


@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}

步骤 3

定义一个表示访问者的接口。

1
2
3
4
5
6
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}

步骤 4

创建实现了上述类的实体访问者。

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

@Override
public void visit(Computer computer) {
System.out.println("显示电脑");
}

@Override
public void visit(Mouse mouse) {
System.out.println("显示鼠标");
}

@Override
public void visit(Keyboard keyboard) {
System.out.println("显示键盘");
}

@Override
public void visit(Monitor monitor) {
System.out.println("显示显示器");
}
}

步骤 5

使用 ComputerPartDisplayVisitor 来显示 Computer 的组成部分。

1
2
3
4
5
6
7
public class VisitorPatternDemo {
public static void main(String[] args) {

ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}

步骤 6

执行程序,输出结果:

1
2
3
4
显示鼠标
显示键盘
显示显示器
显示电脑

策略模式

策略模式(Strategy Pattern)中一个类的行为或其算法可以在运行时更改,属于行为型模式。策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

介绍

意图:****定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换

主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。

优点: 1.算法可以自由切换。 2.避免使用多重条件判断。 3.扩展性良好。

缺点: 1.策略类会增多。 2.所有策略类都需要对外暴露。

使用场景: 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

策略模式包含以下几个核心角色:

  • 环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
  • 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法
  • 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现

策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略

实现

我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。

StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。

步骤 1

创建一个接口。

1
2
3
public interface Strategy {
public int doOperation(int num1, int num2);
}

步骤 2

创建实现接口的实体类。

1
2
3
4
5
6
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
1
2
3
4
5
6
public class OperationSubtract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
1
2
3
4
5
6
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}

步骤 3

创建 Context 类。

1
2
3
4
5
6
7
8
9
10
11
public class Context {
private Strategy strategy;

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

public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}

步骤 4

使用 Context 来查看当它改变策略 Strategy 时的行为变化。

1
2
3
4
5
6
7
8
9
10
11
12
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}

步骤 5

执行程序,输出结果:

1
2
3
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

模板模式

模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板,其子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行,属于行为型模式

介绍

意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的具体实现

优点: 1.封装不变部分,扩展可变部分。 2.提取公共代码,便于维护。 3.行为架构,即主流程由父类控制,流程涉及到的多个步骤具体实现细节由子类定义

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景: 1.有多个子类共有的方法,且逻辑相同。 2.重要的、复杂的方法,可以考虑作为模板方法。

注意事项:为防止恶意操作,一般模板方法都加上 final 关键词,防止子类修改该方法的行为,使用 final 关键字修饰的方法是不能被子类重写的。

实现

我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。

TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。

步骤 1

创建一个抽象类,它的模板方法被设置为 final。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();

//模板
public final void play(){

//初始化游戏
initialize();

//开始游戏
startPlay();

//结束游戏
endPlay();
}
}

步骤 2

创建扩展了上述类的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Cricket extends Game {

@Override
void endPlay() {
System.out.println("板球比赛结束!");
}

@Override
void initialize() {
System.out.println("进行板球比赛准备活动!");
}

@Override
void startPlay() {
System.out.println("板球比赛开始!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Football extends Game {

@Override
void endPlay() {
System.out.println("足球比赛结束!");
}

@Override
void initialize() {
System.out.println("进行足球比赛准备活动!");
}

@Override
void startPlay() {
System.out.println("足球比赛开始!");
}
}

步骤 3

使用 Game 的模板方法 play() 来演示游戏的定义方式。

1
2
3
4
5
6
7
8
9
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}

步骤 4

执行程序,输出结果:

1
2
3
4
5
6
7
进行板球比赛准备活动!
板球比赛开始!
板球比赛结束!

进行足球比赛准备活动!
足球比赛开始!
足球比赛结束!

MVC 模式

MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发

  • Model(模型) - 模型代表一个存取数据的对象。
  • View(视图) - 视图代表模型包含的数据的可视化。
  • Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图,它使视图与模型分离开。

实现

我们将创建一个作为模型的 Student 对象。StudentView 是一个把学生详细信息输出到控制台的视图类,StudentController 是负责存储数据到 Student 对象中的控制器类,并相应地更新视图 _StudentView_。

_MVCPatternDemo_,我们的演示类使用 StudentController 来演示 MVC 模式的用法。

步骤 1

创建模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Student {
private String rollNo;
private String name;
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

步骤 2

创建视图:

1
2
3
4
5
6
7
public class StudentView {
public void printStudentDetails(String studentName, String studentRollNo){
System.out.println("学生: ");
System.out.println("名称: " + studentName);
System.out.println("编号: " + studentRollNo);
}
}

步骤 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
public class StudentController {
private Student model;
private StudentView view;

public StudentController(Student model, StudentView view){
this.model = model;
this.view = view;
}

public void setStudentName(String name){
model.setName(name);
}

public String getStudentName(){
return model.getName();
}

public void setStudentRollNo(String rollNo){
model.setRollNo(rollNo);
}

public String getStudentRollNo(){
return model.getRollNo();
}

public void updateView(){
view.printStudentDetails(model.getName(), model.getRollNo());
}
}

步骤 4

使用 StudentController 方法来演示 MVC 设计模式的用法:

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 MVCPatternDemo {
public static void main(String[] args) {

//从数据库获取学生记录
Student model = retrieveStudentFromDatabase();

//创建一个视图:把学生详细信息输出到控制台
StudentView view = new StudentView();

StudentController controller = new StudentController(model, view);

controller.updateView();

//更新模型数据
controller.setStudentName("Jack");
System.out.println();
controller.updateView();
}

private static Student retrieveStudentFromDatabase(){
Student student = new Student();
student.setName("Chris");
student.setRollNo("12");
return student;
}
}

步骤 5

执行程序,输出结果:

1
2
3
4
5
6
7
学生: 
名称: Chris
编号: 12

学生:
名称: Jack
编号: 12

工厂模式

工厂模式(Factory Pattern)提供了一种创建对象的最佳方式,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,属于创建型模式

介绍

何时使用:我们明确地计划不同条件下创建不同实例时。

应用实例: 用户需要一辆SUV类型的汽车,可以告诉厂家“我需要一辆类型为SUV的汽车”,厂家直接从工厂里面提满足条件的汽车给用户,用户不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。

优点: 1.一个调用者想创建一个对象,只要知道其名称就可以了。 2.扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3.屏蔽产品的具体实现,调用者只关心产品的接口

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

使用场景: 1.日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2.数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3.设计一个连接服务器的框架,需要三个协议:”POP3”、”IMAP”、”HTTP”,这三个类可以共同实现同一个接口。

注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

实现

我们将创建一个 Shape 接口和实现 Shape 接口的实现类,下一步是定义工厂类 _ShapeFactory_。

_FactoryPatternDemo_,我们的演示类使用 ShapeFactory 来获取 Shape 对象。测试类将向 ShapeFactory 传递信息(_CIRCLE / RECTANGLE / SQUARE_),以便获取它所需对象的类型。

步骤 1

创建一个接口:

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

步骤 2

创建实现接口的实体类。

1
2
3
4
5
6
7
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("画长方形");
}
}
1
2
3
4
5
6
7
public class Square implements Shape {

@Override
public void draw() {
System.out.println("画正方形");
}
}
1
2
3
4
5
6
7
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("画圆形");
}
}

步骤 3

创建一个工厂,生成基于给定信息的实体类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ShapeFactory {

//使用 getShape 方法获取形状类型的对象
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}

步骤 4

使用该工厂,通过传递类型信息来获取实体类的对象。

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 FactoryPatternDemo {

public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();

//获取 Circle 的对象,并调用它的 draw 方法
Shape shape1 = shapeFactory.getShape("CIRCLE");

//调用 Circle 的 draw 方法
shape1.draw();

//获取 Rectangle 的对象,并调用它的 draw 方法
Shape shape2 = shapeFactory.getShape("RECTANGLE");

//调用 Rectangle 的 draw 方法
shape2.draw();

//获取 Square 的对象,并调用它的 draw 方法
Shape shape3 = shapeFactory.getShape("SQUARE");

//调用 Square 的 draw 方法
shape3.draw();
}
}

步骤 5

执行程序,输出结果:

1
2
3
画圆形
画长方形
画正方形