Java 设计模式

设计模式


一、软件设计的原则


1. 开闭原则

定义:软件实体应当 对扩展开放,对修改关闭。


2. 里氏替换原则

定义:继承 必须保证 超类 所拥有的性质,在 子类 中仍然成立。
即 子类 在继承 父类 时,除了添加 新的方法 来新增功能外,尽量避免重写 父类 方法,因为这会导致整个继承体系的复用性变差。


3. 依赖倒置原则

定义:高层模块 不应该依赖 低层模块,两者都应该依赖其 抽象;抽象 不应该依赖 细节,细节 应该依赖 抽象。
其核心思想是要 面向接口编程,而不是 面向实现编程。
这样可以 降低耦合性,提高系统稳定性,提高代码的 可读性 和 可维护性。


4. 单一职责原则

定义:一个类应该 有且仅有一个引起它变化的原则,否则类应该被拆分。
其核心思想是 控制类的粒度大小、提高类的内聚性。


5. 接口隔离原则

定义:一个类 对 另一个类 的依赖,应该建立在 最小的接口上。
其核心思想 是要为 每个特定的功能 建立 对应的接口,而不是在一个接口中试图去包含所有功能。
既要 保证相对独立,也要 避免过多接口所导致的臃肿。


6. 迪米特法则(最少知道原则)

定义:如果 两个软件实体 不需要直接通讯,那么就应该 避免直接互相调用。
而是通过第三方转发该调用,从而 降低耦合度,保证模块的相对独立。


7. 合成复用原则(组合复用原则)

定义:应该优先使用 组合、聚合 等关联关系来实现复用,其次才是考虑使用 继承关系。


8. 总结

总结

  1. 开闭原则 是总纲,它告诉我们 要对扩展开放,对修改关闭;
  2. 里氏替换原则 告诉我们 不要破坏继承体系;
  3. 依赖倒置原则 告诉我们 要面向接口编程;
  4. 单一职责原则 告诉我们 实现类要职责单一;
  5. 接口隔离原则 告诉我们 在设计接口的时候要精简单一;
  6. 迪米特法则 告诉我们 要降低耦合度;
  7. 合成复用原则 告诉我们 要优先使用组合或者聚合关系复用,少用继承关系复用。

二、创建型


1. 单例模式


1.1 饿汉式单例

饿汉式单例 是最简单一种单例模式。
它在类初始化时,就完成相关单例对象的创建。
可以通过 静态代码块 或 静态内部类 的方式来进行实现。


1.1.1 静态代码块方式
public class HungrySingleton implements Serializable {

    private static final HungrySingleton instance;

    static {
        instance = new HungrySingleton();
    }

    // 确保构造器私有
    private HungrySingleton() {}

    // 获取单例对象
    public static HungrySingleton getInstance() {
        return instance;
    }
}

1.1.2 静态内部类方式
public class StaticInnerClassHungrySingleton {

    private static class InnerClass {
        private static final StaticInnerClassHungrySingleton instance = new StaticInnerClassHungrySingleton();
    }

    // 确保构造器私有
    private StaticInnerClassHungrySingleton() {}

    // 获取单例对象
    public static StaticInnerClassHungrySingleton getInstance() {
        return InnerClass.instance;
    }

}

饿汉式单例 的优点在于其 不存在线程安全问题,对象的唯一性由虚拟机在类初始化创建时保证;
其缺点在于如果 对象的创建比较消耗资源,并且单例对象不一定会被使用时就会造成资源的浪费。


1.2 懒汉式单例

懒汉式单例 的思想在于 在需要使用单例对象时 才进行创建。
如果对象存在 则直接返回,如果对象不存在 则创建后返回。


1.2.1 synchronized 方式
public class LazySingletonUnsafe {

    private static LazySingletonUnsafe instance = null;

    private LazySingletonUnsafe() {
    }

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

需要注意的是 上面的代码在单线程环境下是没有问题的,但是在多线程环境下是线程不安全的。
原因在于下面的创建代码是非原子性的:

if (instance == null) {
    instance = new LazySingletonUnsafe();
}

想要保证 创建操作的原子性,可以通过 synchronized 关键字来进行实现:

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

此时该方法是线程安全的,但是性能却存在问题。
因为 synchronized 修饰的是静态方法,其锁住的是整个类对象,这意味着所有想要获取该 单例对象 的线程都必须要等待内部锁的释放。

假设 单例对象 已经创建完成,并有 100 个线程并发获取该单例对象,则这 100 个线程都需要等待,显然这会降低系统的吞吐量。
因此更好的方式是采用 双重检查锁的机制 来实现懒汉式单例:


1.2.2 双重检查锁的方式
public class DoubleCheckLazySingletonSafe {

    // 使用volatile来禁止指令重排序 
    private static volatile DoubleCheckLazySingletonSafe instance = null;

    private DoubleCheckLazySingletonSafe() {
    }

    // 双重检查
    public static DoubleCheckLazySingletonSafe getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckLazySingletonSafe.class) {
                if (instance == null) {
                    instance = new DoubleCheckLazySingletonSafe();
                }
            }
        }
        return instance;
    }
}

还是沿用上面的举例,假设单例对象已经创建完成,并有 100 个线程并发获取该 单例对象,此时 instance == null 判断肯定是 false,所以所有线程都会直接获得该 单例对象,而不会进入 synchronized 同步代码块,这减小了锁的锁定范围,用更小的锁粒度获得了更好的性能。
但内部的 if 代码块仍然需要使用 synchronized 关键字修饰,从而保证整个 if 代码块的原子性。

需要注意的是 这里的 instance 需要使用 volatile 关键修饰,用于禁止对象在创建过程中出现 指令重排序

  • 通常对象的创建分为以下三步:
  1. 给对象分配内存空间;
  2. 调用对象的构造器方法,并执行初始化操作;
  3. 将变量指向相应的内存地址。

如果没有禁止 指令重排序,则 2、3 步可能会发生指令重排序,这在单线程下是没有问题的,也符合 As-If-Serial 原则,但是如果在多线程下就会出现线程不安全的问题。

// 2. 由于线程1已经将变量指向内存地址,所以其他线程判断instance不为空,进而直接获取,但instance可能尚未初始化完成
if (instance == null) { 
    synchronized (DoubleCheckLazySingletonSafe.class) {
        if (instance == null) {
            // 1. 假设线程1已经给对象分配了内存空间并将变量instance指向了相应的内存地址,但尚未初始化完成
            instance = new DoubleCheckLazySingletonSafe();
        }
    }
}
return instance;

由于重排序的存在,其他线程可能拿到的是一个尚未初始化完成的 instance,此时就可能会导致异常,所以需要禁止其出现 指令重排序。


1.3 使用 序列化 破坏单例

饿汉式单例双重检查锁的懒汉式单例 都是线程安全的,都能满足日常的开发需求。
但如果你是类库的开发者,为了防止自己类库中的单例在调用时被有意或无意地破坏,你还需要考虑 单例模式 的写法安全。
其中 序列化反射攻击 是两种常见的破坏单例的方式,示例如下:

public class SerializationDamage {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("SingletonFile"));
        outputStream.writeObject(instance);
        
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("SingletonFile")));
        HungrySingleton newInstance = (HungrySingleton) inputStream.readObject();
        
        System.out.println(instance == newInstance); // false
    }
}

将 HungrySingleton 实现 Serializable 接口后,使用上面的代码将对象 序列化 写入文件,然后再 反序列 获取。
你会发现两次得到的不是一个对象,这就代表 单例模式 受到了 序列化 和 反序列化 的破坏
想要解决这个问题,需要在对应的 单例类 中定义 readResolve() 方法:

public class HungrySingleton implements Serializable {
    
    // ...
    private Object readResolve() {
        return instance;
    }
    // ...
}

此时在 反序列化 时,该方法就会被调用来 返回单例对象,对应的 ObjectInputStream 类的源码如下:

  // 在本用例中,readObject在内部最终调用的是readOrdinaryObject方法
private Object readOrdinaryObject(boolean unshared) throws IOException {
	// ...
 	if (obj != null 
 			&& handles.lookupException(passHandle) == null 
 			&& desc.hasReadResolveMethod()) // 如果对应的对象中有readResolve方法
 	{
            // 则通过反射调用该方法来获取对应的单例对象
  		Object rep = desc.invokeReadResolve(obj);
      	// ...
     	handles.setObject(passHandle, obj = rep);
  	}
  	return obj;
}
1.4 使用 反射 破坏单例

使用 反射 也可以破坏单例模式。
并且由于 Java 的反射功能过于强大,这种破坏几乎是无法规避的,示例如下:

public class ReflectionDamage {

    public static void main(String[] args) throws Exception {
        Constructor<HungrySingleton> constructor = HungrySingleton.class.getDeclaredConstructor();
        // 获取私有构造器的访问权限
        constructor.setAccessible(true);
        
        HungrySingleton hungrySingleton = constructor.newInstance();
        HungrySingleton instance = HungrySingleton.getInstance();
        
        System.out.println(hungrySingleton == instance); // false
    }
}

即便在创建 单例对象 时将构造器声明为私有,此时仍然可以通过 反射 修改权限来获取,此时 单例模式 就被破坏了。
如果你采用的是 饿汉式单例,此时可以通过如下的代码来规避这种破坏。

public class HungrySingleton implements Serializable {

    private static final HungrySingleton instance;

    static {
        instance = new HungrySingleton();
    }

    // 由于instance在类创建时就已经初始化完成,所以当使用反射调用构造器时就会抛出自定义的RuntimeException异常
    private HungrySingleton() {
        if (instance != null) {
            throw new RuntimeException("单例模式禁止反射调用");
        }
    }

    // ...
}

以上是 饿汉式单例 防止反射攻击的办法,如果你使用的是 懒汉式单例,此时由于无法知道对象何时会被创建,并且反射功能能够获取到任意 字段、方法、构造器 的访问权限,所以此时没有任何方法能够规避掉反射攻击。

那么有没有一种单例模式能够在 保证线程安全,还能够 防止序列化 和 反射 功能呢?
在 Java 语言中,可以通过 枚举式单例 来实现。


1.5 枚举式单例

使用 枚举 实现单例的示例如下:

public enum EnumInstance {

    INSTANCE;

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public static EnumInstance getInstance() {
        return INSTANCE;
    }
}

想要实现一个 单例枚举,对应的单例类必须要使用 enum 修饰,其余的 字段声明(如:field)、方法声明(如:setField)都和正常的类一样。
首先 枚举类 是线程安全的,这点可以使用反编译工具 Jad 对类的 class 文件进行反编译来验证:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumInstance.java

package com.qs.design_pattern.创建型.单例模式.枚举式单例;

// 不可变的类
public final class EnumInstance extends Enum
{

    public static EnumInstance[] values()
    {
        return (EnumInstance[])$VALUES.clone();
    }

    public static EnumInstance valueOf(String name)
    {
        return (EnumInstance)Enum.valueOf(com/qs/design_pattern/创建型/枚举式单例/EnumInstance, name);
    }

    // 私有构造器,枚举类没有无参构造器,Enum中只定义了Enum(String name, int ordinal) 构造器
    private EnumInstance(String s, int i)
    {
        super(s, i);
    }

    // 自定义的方法
    public String getField()
    {
        return field;
    }

    public void setField(String field)
    {
        this.field = field;
    }

    public static EnumInstance getInstance()
    {
        return INSTANCE;
    }

    // 静态不可变的实例对象
    public static final EnumInstance INSTANCE;
    // 自定义字段
    private String field;
    private static final EnumInstance $VALUES[];

    // 在静态代码中进行初始化
    static 
    {
        INSTANCE = new EnumInstance("INSTANCE", 0);
        $VALUES = (new EnumInstance[] {
            INSTANCE
        });
    }
}

通过 反编译工具 可以看到其和 饿汉式单例 模式类似,因此它也是线程安全的。
另外它也能 防止 序列化攻击 和 反射攻击


public class EnumInstanceTest {

    public static void main(String[] args) throws Exception {
        // 序列化攻击
        EnumInstance instance = EnumInstance.getInstance();
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("EnumSingletonFile"));
        outputStream.writeObject(instance);
        
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("EnumSingletonFile")));
        EnumInstance newInstance = (EnumInstance) inputStream.readObject();
        System.out.println(instance == newInstance);
        
        // 反射攻击,Enum类中只有一个两个参数的构造器:Enum(String name, int ordinal)
        Constructor<EnumInstance> constructor = EnumInstance.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        EnumInstance enumInstance = constructor.newInstance("name", 0);
        System.out.println(instance == enumInstance);
    }
}

对于 序列化 与 反序列化,枚举类单例 能保证两次拿到的都是同一个实例。
对于 反射攻击,枚举类单例会抛出明确的异常。

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at com.qs.design_pattern.创建型.单例模式.枚举式单例.EnumInstanceTest.main(EnumInstanceTest.java:18)

2. 简单工厂模式


2.1 定义

对于调用者来说,它无需知道对象的具体创建细节。
只需要将自己所需对象的类型告诉工厂,然后由工厂自动创建并返回。

2.2 示例

23_simple_factory.png


2.2.1 产品抽象类
public abstract class Phone {
    public abstract void call(String phoneNum);
}

2.2.2 具体的产品
public class HuaweiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("华为手机拨打电话:" + phoneNum);
    }
}

public class XiaomiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("小米手机拨打电话:" + phoneNum);
    }
}

2.2.3 手机工厂
public class PhoneFactory {
    public Phone getPhone(String type) {
        if ("xiaomi".equalsIgnoreCase(type)) {
            return new XiaomiPhone();
        } else if ("huawei".equalsIgnoreCase(type)) {
            return new HuaweiPhone();
        }
        return null;
    }
}

2.2.4 调用工厂类获取具体的实例
public class ZTest {
    public static void main(String[] args) {
        PhoneFactory phoneFactory = new PhoneFactory();
        phoneFactory.getPhone("xiaomi").call("123");
        phoneFactory.getPhone("huawei").call("321");
    }
}

2.3 优缺点

简单工厂 的优点在于其向用户 屏蔽了对象创建过程,使得用户可以不必关注具体的创建细节;
其缺陷在于违背了开闭原则。

简单工厂模式 中,如果想要增加新的产品,就需要修改 简单工厂 中的判断逻辑,这就违背了 开闭原则,因此其并不属于 GOF 经典的 23 种设计模式。
在 Java 语言中,可以通过 泛型 来尽量规避这一缺陷,此时可以将创建产品的方法修改为如下所示:

public Phone getPhone(Class<? extends Phone> phoneClass) {
    try {
        return (Phone) Class.forName(phoneClass.getName()).newInstance();
    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}

3. 工厂模式


3.1 定义

定义一个用于 创建对象 的工厂接口,但具体实例化哪一个工厂则由子类来决定。


3.2 示例

23_factory_method.png


3.2.1 产品抽象类
public abstract class Phone {
    public abstract void call(String phoneNum);
}

3.2.2 产品实现类
public class HuaweiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("华为手机拨打电话:" + phoneNum);
    }
}

public class XiaomiPhone extends Phone {
    public void call(String phoneNum) {
        System.out.println("小米手机拨打电话:" + phoneNum);
    }
}

3.2.3 工厂接口
public interface Factory {
    Phone produce();
}

3.2.4 工厂实现类
public class HuaweiPhoneFactory implements Factory {
    @Override
    public Phone produce() {
        return new HuaweiPhone();
    }
}

public class XiaomiPhoneFactory implements Factory {
    @Override
    public Phone produce() {
        return new XiaomiPhone();
    }
}

3.2.5 由调用者来决定实例化哪一个工厂对象
public class ZTest {
    public static void main(String[] args) {
        XiaomiPhoneFactory xiaomiPhoneFactory = new XiaomiPhoneFactory();
        xiaomiPhoneFactory.produce().call("123");
        
        HuaweiPhoneFactory huaweiPhoneFactory = new HuaweiPhoneFactory();
        huaweiPhoneFactory.produce().call("456");
    }
}

3.3 优点

工厂模式 的优点在于 良好的封装性 和 可扩展性
如果想要增加新的产品(如:OppoPhone),只需要增加对应的工厂类即可;
同时和 简单工厂 一样,它也向用户屏蔽了不相关的细节,使得系统的 耦合度得以降低


4. 抽象工厂模式


4.1 定义

提供一个 创建一系列 相关或相互依赖对象 的接口,而无需指定它们具体的实现类。
抽象工厂模式 是 工厂模式 的升级版本,它适用于存在多个产品的情况。

接着上面的例子,假设每个工厂不仅生产手机,而且还需要生产对应的充电器,这样才能算一个可以出售的产品,相关的代码示例如下:

4.2 示例

23_abstract_factory.png


4.2.1 充电器抽象类
public abstract class Charger {
    public abstract void Charge(Phone phone);
}

4.2.2 充电器实现类
public class HuaweiCharger extends Charger {
    @Override
    public void Charge(Phone phone) {
        System.out.println("华为充电器给" + phone + "充电");
    }
}

public class XiaomiCharger extends Charger {
    @Override
    public void Charge(Phone phone) {
        System.out.println("小米充电器给" + phone + "充电");
    }
}

4.2.3 工厂接口
public interface Factory {
    
    Phone producePhone();
    
    Charger produceCharger();
}

4.2.4 工厂实现类
public class HuaweiPhoneFactory implements Factory {

    @Override
    public Phone producePhone() {
        return new HuaweiPhone();
    }
    @Override
    public Charger produceCharger() {
        return new HuaiweiCharger();
    }
}

public class XiaomiPhoneFactory implements Factory {

    @Override
    public Phone producePhone() {
        return new XiaomiPhone();
    }
    @Override
    public Charger produceCharger() {
        return new XiaomiCharger();
    }
}

4.2.5 调用具体的工厂实现类
public class ZTest {

    public static void main(String[] args) {
        Factory xiaomiPhoneFactory = new XiaomiPhoneFactory();
        xiaomiPhoneFactory
        	.produceCharger() // 生产充电器
        	.charge(xiaomiPhoneFactory.producePhone()); // 充电
        
        Factory huaweiPhoneFactory = new HuaweiPhoneFactory();
        huaweiPhoneFactory
        	.produceCharger()
        	.charge(huaweiPhoneFactory.producePhone());
    }
}

4.3 优缺点

抽象工厂模式 继承了 工厂模式 的优点,能用于存在多个产品的情况,但其对应的产品族必须相对固定。
假设我们现在认为 手机 + 充电器 + 耳机 才算一个可以对外出售的产品,则上面所有的工厂类都需要更改,但显然不是所有的手机都有配套的耳机,手机 + 充电器 这个产品族是相对固定的。


5. 构建者模式


5.1 定义

将一个 复杂对象的构造 与它的表示分离,使同样的构建过程可以创建不同的表示。
它将一个 复杂对象 的创建过程分解为多个简单的步骤,然后一步一步的组装完成。

5.2 示例

23_builder.png


5.2.1 产品实体类
public class Phone {
    /*处理器*/
    private String processor;
    /*摄像头*/
    private String camera;
    /*屏幕*/
    private String screen;
}

5.2.2 建造者抽象类
public abstract class Builder {

    protected Phone phone = new Phone();
    
    /*安装处理器*/
    public abstract void addProcessor();
    /*组装摄像头*/
    public abstract void addCamera();
    /*安装屏幕*/
    public abstract void addScreen();
  
    public Phone produce() {
        return phone;
    }
}

5.2.3 建造者实现类
public class HuaweiBuilder extends Builder {
    @Override
    public void addProcessor() {
        phone.setProcessor("海思麒麟处理器");
    }

    @Override
    public void addCamera() {
        phone.setCamera("莱卡摄像头");
    }

    @Override
    public void addScreen() {
        phone.setScreen("OLED");
    }
}

public class XiaomiBuilder extends Builder {

    @Override
    public void addProcessor() {
        phone.setProcessor("高通骁龙处理器");
    }

    @Override
    public void addCamera() {
        phone.setCamera("索尼摄像头");
    }

    @Override
    public void addScreen() {
        phone.setScreen("OLED");
    }
}

5.2.4 管理者类

定义管理者类(也称为导演类),由它来驱使 具体的构建者 按照指定的顺序完成构建过程:

public class Manager {

    private final Builder builder;

    public Manager(Builder builder) {
        this.builder = builder;
    }

    public Phone buy() {
        builder.addCamera();
        builder.addProcessor();
        builder.addScreen();
        return builder.produce();
    }
}

5.2.5 调用管理者类获取产品
public class ZTest {

    public static void main(String[] args) {
        Phone huawei = new Manager(new HuaweiBuilder()).buy();
        System.out.println(huawei);
        
        Phone xiaomi = new Manager(new XiaomiBuilder()).buy();
        System.out.println(xiaomi);
    }
}
// 输出:
Phone(processor=海思麒麟处理器, camera=莱卡摄像头, screen=OLED)
Phone(processor=高通骁龙处理器, camera=索尼摄像头, screen=OLED)

5.3 优点

建造者模式 的优点在于 将复杂的构建过程 拆分为多个独立的单元;
在保证 拓展性 的基础上也保证了良好的 封装性,使得客户端不必知道产品的具体创建流程。


6. 原型模式


6.1 定义

用原型实例 指定创建对象的种类,并通过拷贝这些原型创建新的对象。


6.2 示例

6.2.1 在 Java 语言中可以通过 clone() 方法来实现原型模式
public class Phone implements Cloneable {

    private final String type;

    Phone(String type) {
        System.out.println("构造器被调用");
        this.type = type;
    }

    public void call() {
        System.out.println(type + "拨打电话");
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("克隆方法被调用");
        return super.clone();
    }
}

6.2.2 使用克隆来创建对象
Phone phone = new Phone("3G手机");
Phone clonePhone = (Phone) phone.clone();
clonePhone.call();

6.2.3 深拷贝 和 浅拷贝

在使用 clone 方法时需要注意区分 深拷贝 和 浅拷贝
即如果待拷贝的对象中 含有引用类型的变量,也需要对其进行拷贝,示例如下:

public class SmartPhone implements Cloneable {

    private final String type;
    private Date productionDate;

    SmartPhone(String type, Date productionDate) {
        this.type = type;
        this.productionDate = productionDate;
    }

    public void call() {
        System.out.println(type + "拨打电话");
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        SmartPhone smartPhone = (SmartPhone) super.clone();
        // 对引用对象进行拷贝
        smartPhone.productionDate = (Date) smartPhone.productionDate.clone();
        return smartPhone;
    }
}

6.2.4 测试
public static void main(String[] args) throws CloneNotSupportedException {
    SmartPhone phone = new SmartPhone("5G手机", new Date());
    SmartPhone clonePhone = (SmartPhone) phone.clone();
    clonePhone.call();

    System.out.println(phone == clonePhone);
    System.out.println(phone.equals(clonePhone));

    /*
    5G手机拨打电话
    false
    true
     */
}

6.3 适用场景

原型模式 是直接 在内存中进行 二进制流的拷贝,被拷贝对象的构造函数并不会被执行,因此其性能表现非常优秀。
如果对象的创建需要消耗非常多的资源,此时应该考虑使用 原型模式


三、结构型


1. 代理模式


1.1 定义

为 目标对象 提供一个 代理对象 以控制外部环境对其的访问,此时外部环境应访问该 代理对象,而不是 目标对象。
通过 代理模式,可以在不改变 目标对象 的情况下,实现功能的扩展。

在 Java 语言中,根据 代理对象 生成的时间点的不同,可以分为 静态代理动态代理
其中 动态代理 根据实现方式的不同,又可以分为 JDK代理Cglib代理

1.2 静态代理

1.2.1 此时 代理对象 和 目标对象 需要实现相同的接口
public interface IService {
    void compute();
}

1.2.2 目标对象
public class ComputeService implements IService {
    @Override
    public void compute() {
        System.out.println("业务处理");
    }
}

1.2.3 在 代理对象 中注入 目标对象 的实例
public class ProxyService implements IService {

    private final IService target;

    public ProxyService(IService target) {
        this.target = target;
    }

    @Override
    public void compute() {
        System.out.println("权限校验");
        target.compute();
        System.out.println("资源回收");
    }
}

1.2.4 调用时候应该访问 代理对象,而不是 目标对象
ProxyService proxyService = new ProxyService(new ComputeService());
proxyService.compute();

1.3 JDK 代理

除了使用 静态代理 外,还可以利用 JDK 中的 Proxy类 和 反射功能 来实现对 目标对象 的代理。

ComputeService target = new ComputeService();

IService proxyInstance = (IService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(), // 目标对象类加载器
    target.getClass().getInterfaces(), // 代理类要实现的接口列表
    (proxy, method, args1) -> {
        System.out.println("权限校验");
        Object invoke = method.invoke(target, args1);
        System.out.println("资源回收");
        return invoke;
    });

proxyInstance.compute();

静态代理 和 JDK 动态代理 都 要求 目标对象 必须实现一个或者多个接口。
如果 目标对象 不存在 任何接口,此时可以使用 Cglib 方式对其进行代理。


1.4 Cglib 代理

1.4.1 要想使用 Cglib 代理,必须导入相关的依赖
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

1.4.2 此时 目标对象 不需要实现任何接口
public class ComputeService {
    public void compute() {
        System.out.println("业务处理");
    }
}

1.4.3 使用 Cglib 进行代理
public class Proxy implements MethodInterceptor {

    private final Object target;

    public Proxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        // 创建用于生成生成动态子类的工具类
        Enhancer enhancer = new Enhancer();
        // 指定动态生成类的父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调
        enhancer.setCallback(this);
        // 动态生成子类
        return enhancer.create();
    }

   /**
    * 我们只需要实现此处的拦截逻辑,其他代码都是相对固定的
    */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws InvocationTargetException, IllegalAccessException {
        System.out.println("权限校验");
        Object invoke = method.invoke(target, args);
        System.out.println("资源回收");
        return invoke;
    }
}

1.4.4 访问代理对象
Proxy proxy = new Proxy(new ComputeService());
ComputeService service = (ComputeService) proxy.getProxyInstance();
service.compute();

2. 适配器模式


2.1 定义

将一个类的接口 转换成 客户希望的另外一个接口,从而使得原本由于 接口不兼容 而无法一起工作的类可以一起工作。


2.2 示例

将 220V 的电流,通过 适配器 转换为对应规格的电流给手机充电。

23_adapter.png


2.2.1 电源类
public class PowerSupply {

    private final int output = 220;

    public int output220V() {
        System.out.println("电源电压:" + output);
        return output;
    }
}

2.2.2 手机电压规格
public interface Target {
    int output5V();
}

2.2.3 适配器需要 继承自源类,并实现目标类接口
public class ChargerAdapter extends PowerSupply implements Target {
    @Override
    public int output5V() {
        int output = output220V();
        System.out.println("充电头适配转换");
        output = output / 44;
        System.out.println("输出电压:" + output);
        return output;
    }
}

2.2.4 测试
public class ZTest {
    public static void main(String[] args) {
        Target target = new ChargerAdapter();
        target.output5V();
    }
}

// 输出:
电源电压:220
充电头适配转换
输出电压:5

3. 桥接模式


3.1 定义

将 抽象部分 与它的 实现部分 分离,使它们都可以独立地变化。
它使用 组合关系 来代替 继承关系,从而降低了 抽象 和 实现 这两个可变维度的 耦合度。


3.2 优点
  • 抽象和实现分离;
  • 优秀的扩展能力;
  • 实现细节对客户端透明,客户可以通过各种聚合来实现不同的需求。

3.3 示例

将一个 图形的形状和颜色 进行分离,从而可以通过组合来实现的不同的效果。

23_bridge.png


3.3.1 颜色的抽象和实现
public interface Color {
    String getDesc();
}

public class Blue implements Color {
    @Override
    public String getDesc() {
        return "蓝色";
    }
}

public class Red implements Color {
    @Override
    public String getDesc() {
        return "红色";
    }
}

public class Yellow implements Color {
    @Override
    public String getDesc() {
        return "黄色";
    }
}

3.3.2 图形的抽象和实现
public abstract class Shape {

    private Color color;

    public Shape setColor(Color color) {
        this.color = color;
        return this;
    }

    public Color getColor() {
        return color;
    }

    public abstract void getDesc();
}


public class Round extends Shape {
    @Override
    public void getDesc() {
        System.out.println(getColor().getDesc() + "圆形");
    }
}

public class Square extends Shape {
    @Override
    public void getDesc() {
        System.out.println(getColor().getDesc() + "正方形");
    }
}

3.3.3 通过聚合的方式来进行调用
new Square().setColor(new Red()).getDesc();
new Square().setColor(new Blue()).getDesc();

new Round().setColor(new Blue()).getDesc();
new Round().setColor(new Yellow()).getDesc();

/*
红色正方形
蓝色正方形
蓝色圆形
黄色圆形
 */

4. 组合模式


4.1 定义

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

4.2 优点
  • 单个对象 和 组合对象 具有一致性,这简化了客户端代码;
  • 可以在 组合体类 加入新的对象,而无需改变其源码。

4.3 示例

模拟 Linux 文件系统。

23_composite.png


4.3.1 组件类,定义 文件夹 和 文件 的所有操作
public abstract class Component {

    private String name;

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

    public void add(Component component) {
        throw new UnsupportedOperationException("不支持添加操作");
    }

    public void remove(Component component) {
        throw new UnsupportedOperationException("不支持删除操作");
    }


    public void vim(String content) {
        throw new UnsupportedOperationException("不支持使用vim编辑器打开");
    }

    public void cat() {
        throw new UnsupportedOperationException("不支持查看操作");
    }

    public void print() {
        throw new UnsupportedOperationException("不支持打印操作");
    }

}

4.3.2 文件夹类
public class Folder extends Component {

    private final List<Component> componentList = new ArrayList<>();

    public Folder(String name) {
        super(name);
    }

    @Override
    public void add(Component component) {
        componentList.add(component);
    }

    @Override
    public void remove(Component component) {
        componentList.remove(component);
    }

    @Override
    public void print() {
        System.out.println(getName());
        componentList.forEach(x -> System.out.println("    " + x.getName()));
    }
}

4.3.3 文件类
public class File extends Component {

    private String content;

    public File(String name) {
        super(name);
    }

    @Override
    public void vim(String content) {
        this.content = content;
    }

    @Override
    public void cat() {
        System.out.println(content);
    }

    @Override
    public void print() {
        System.out.println(getName());
    }
}

4.3.4 通过组合来实现层级结构
// 目录
Folder rootDir = new Folder("ROOT目录");
Folder nginx = new Folder("Nginx安装目录");
Folder tomcat = new Folder("Tomcat安装目录");

// 文件
File startup = new File("startup.bat");

rootDir.add(nginx);
rootDir.add(tomcat);
rootDir.add(startup);

rootDir.print();
startup.vim("java -jar");
startup.cat();
nginx.cat();

5. 装饰模式


5.1 定义

在 不改变现有对象结构 的情况下,动态地 给该对象增加一些职责或功能。


5.2 优点
  • 采用 装饰模式 扩展对象的功能,比采用 继承方式 更加灵活。
  • 可以通过设计多个不同的 装饰类,来创造出多个不同行为的组合。

5.3 示例

在购买手机后,你可能还会购买 屏幕保护膜、手机壳 等来进行装饰。

23_decorator.png


5.3.1 手机抽象类及其实现
public abstract class Phone {
    public abstract int getPrice();
    public abstract String  getDesc();
}

public class MiPhone extends Phone {
    
    @Override
    public int getPrice() {
        return 1999;
    }
    
    @Override
    public String getDesc() {
        return "MiPhone";
    }
}

5.3.2 装饰器抽象类
public abstract class Decorator extends Phone {

    private final Phone phone;

    public Decorator(Phone phone) {
        this.phone = phone;
    }

    @Override
    public int getPrice() {
        return phone.getPrice();
    }

    @Override
    public String getDesc() {
        return phone.getDesc();
    }
}

5.3.3 手机壳装饰器
public class ShellDecorator extends Decorator {

    public ShellDecorator(Phone phone) {
        super(phone);
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 200;
    }

    @Override
    public String getDesc() {
        return super.getDesc() + " + 手机壳";
    }
}

5.3.4 屏幕保护膜装饰器
public class FilmDecorator extends Decorator {

    public FilmDecorator(Phone phone) {
        super(phone);
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 100;
    }

    @Override
    public String getDesc() {
        return super.getDesc() + " + 钢化膜";
    }
}

5.3.5 调用装饰器对目标对象进行装饰
public class ZTest {

    public static void main(String[] args) {
        ShellDecorator decorator = new ShellDecorator(new FilmDecorator(new MiPhone()));
        System.out.println(decorator.getDesc() + " : " + decorator.getPrice());
    }
}

// 输出: MiPhone + 钢化膜 + 手机壳 : 2299

6. 外观模式


6.1 定义

在现在流行的 微服务架构模式 下,我们通常会将 一个大型的系统 拆分为 多个独立的服务。
此时需要提供一个 一致性的接口 来给外部系统进行调用,这就是 外观模式 的一种体现。


6.2 优点
  • 降低了 子系统 和 客户端 之间的耦合度;
  • 对 客户端 屏蔽了 系统内部 的实现细节。

6.3 示例

模仿 电商购物下单,此时内部需要调用 支付子系统、仓储子系统、物流子系统,而这些细节对用户都是屏蔽的。

23_facade.png


6.3.1 安全检查系统
public class EnvInspectionService {
    public boolean evInspection() {
        System.out.println("支付环境检查...");
        return true;
    }
}

6.3.2 支付子系统
public class AccountService {
    public boolean balanceCheck() {
        System.out.println("账户余额校验...");
        return true;
    }
}

6.3.3 物流子系统
public class LogisticsService {
    public void ship(Phone phone) {
        System.out.println(phone.getName() + "已经发货,请注意查收...");
    }
}

6.3.4 下单系统(外观门面)
public class OrderService {

    private final EnvInspectionService inspectionService = new EnvInspectionService();
    private final AccountService accountService = new AccountService();
    private final LogisticsService logisticsService = new LogisticsService();

    public void order(Phone phone) {
        if (inspectionService.evInspection()) {
            if (accountService.balanceCheck()) {
                System.out.println("支付成功");
                logisticsService.ship(phone);
            }
        }
    }
}

6.3.5 用户只需要访问外观门面,调用下单接口即可
Phone phone = new Phone("XXX手机");

OrderService orderService = new OrderService();
orderService.order(phone);

// 输出:
支付环境检查...
账户余额校验...
支付成功
XXX手机已经发货,请注意查收...

7. 享元模式


7.1 定义

运用 共享技术 来有效地支持 大量细粒度对象的复用,线程池、缓存技术 都是其代表性的实现。

享元模式 中存在以下两种状态:

  • 内部状态,即不会随着环境的改变而改变状态,它在对象初始化时就已经确定;
  • 外部状态,指可以随环境改变而改变的状态。

通过 享元模式,可以避免在系统中创建大量重复的对象,进而可以节省系统的内存空间。


7.2 示例

这里以创建 PPT 模板为例,相同类型的 PPT 模板不再重复创建。

23_flyweight.png


7.2.1 PPT 抽象类
public abstract class PowerPoint {
    /*版权*/
    private final String copyright;
    /*标题*/
    private String title;

    /*这里的版权信息是一种内部状态,它在PPT对象第一次创建时就已经确定*/
    public PowerPoint(String copyright) {
        this.copyright = copyright;
    }

    /*PPT标题是一种外部状态,它可以由外部环境根据不同的需求进行更改*/
    public void setTitle(String title) {
        this.title = title;
    }

    abstract void create();

    @Override
    public String toString() {
        return "编号:" + hashCode() + ": PowerPoint{" +
            "copyright='" + copyright + '\'' +
            ", title='" + title + '\'' +
            '}';
    }
}

7.2.2 PPT 实现类
public class BusinessPPT extends PowerPoint {
    public BusinessPPT(String copyright) {
        super(copyright);
    }
    @Override
    void create() {
        System.out.println("商务类PPT模板");
    }
}

public class SciencePPT extends PowerPoint {
    public SciencePPT(String copyright) {
        super(copyright);
    }
    @Override
    void create() {
        System.out.println("科技类PPT模板");
    }
}

public class ArtPPT extends PowerPoint {
    public ArtPPT(String copyright) {
        super(copyright);
    }
    @Override
    void create() {
        System.out.println("艺术类PPT模板");
    }
}

7.2.3 通过工厂模式来进行创建和共享
public class PPTFactory {

    private HashMap<String, PowerPoint> hashMap = new HashMap<>();

    public PowerPoint getPPT(Class<? extends PowerPoint> clazz) {
        try {
            String name = clazz.getName();
            if (hashMap.keySet().contains(name)) {
                return hashMap.get(name);
            }
            Constructor<?> constructor = Class.forName(name).getConstructor(String.class);
            PowerPoint powerPoint = (PowerPoint) constructor.newInstance("PPT工厂版本所有");
            hashMap.put(name, powerPoint);
            return powerPoint;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

7.2.4 调用工厂类来创建或获取享元对象
public class ZTest {
    public static void main(String[] args) {
        PPTFactory pptFactory = new PPTFactory();
        
        PowerPoint ppt01 = pptFactory.getPPT(BusinessPPT.class);
        ppt01.setTitle("第一季度工作汇报");
        System.out.println(ppt01);
        
        PowerPoint ppt02 = pptFactory.getPPT(BusinessPPT.class);
        ppt02.setTitle("第二季度工作汇报");
        System.out.println(ppt02);
        
        PowerPoint ppt03 = pptFactory.getPPT(SciencePPT.class);
        ppt03.setTitle("科技展汇报");
        System.out.println(ppt03);
    }
}

// 输出:
编号:1744347043: PowerPoint{copyright='PPT工厂版本所有', title='第一季度工作汇报'}
编号:1744347043: PowerPoint{copyright='PPT工厂版本所有', title='第二季度工作汇报'}
编号:662441761: PowerPoint{copyright='PPT工厂版本所有', title='科技展汇报'}

四、行为型


1. 观察者模式


1.1 定义

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


1.2 优点

降低了 目标对象 和 观察者 之间的耦合。


1.3 示例

假设多个用户都关注某一个商家,当商家发出降价等通知时,所有用户都应该收到。

23_observer.png


1.3.1 被观察者接口 及 商家实现类
public interface Observable {
    // 接收观察者
    void addObserver(Observer observer);

    // 移除观察者
    void removeObserver(Observer observer);
    
    // 通知观察者
    void notifyObservers(String message);
}
public class Business implements Observable {
    private final List<Observer> observerList = new ArrayList<>();

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

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

    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observerList) {
            observer.receive(message);
        }
    }
}

1.3.2 观察者接口 及 用户实现类
public interface Observer {
    void receive(String message);
}
public class User implements Observer {

    private String name;

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

    @Override
    public void receive(String message) {
        System.out.println(getName() + "收到消息:" + message);
    }
}

1.3.3 测试商户发送消息
Business business = new Business();

business.addObserver(new User("用户1"));
business.addObserver(new User("用户2"));
business.addObserver(new User("用户3"));

business.notifyObservers("商品促销通知");

// 输出:
用户1收到消息:商品促销通知
用户2收到消息:商品促销通知
用户3收到消息:商品促销通知

2. 责任链模式


2.1 定义

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


2.2 优点
  • 降低了对象之间的耦合度
  • 增强了系统的可扩展性。可以根据需求增加新的处理类,满足开闭原则
  • 增强了系统的灵活性。当工作流程发生变化,可以动态地新增、删除成员或修改其调动次序;
  • 每个对象只需保持一个指向其后继者的引用,而无需保持其他所有处理者的引用,从而可以避免了过多的条件判断语句;
  • 每个类只需要处理自己该处理的工作,不能处理的则传递给下一个对象完成,符合类的单一职责原则

2.3 示例

假设一个正常的流程,根据请假天数的不同,需要不同的领导共同审批。

23_chain_of_responsibility.png


2.3.1 申请单
public class Application {
	/*标题*/
    private String title;
    /*请假天数*/
    private int dayNum;
}

2.3.2 抽象的领导类
public abstract class Leader {

    protected Leader leader;

    // 责任链模式的核心:其需要持有一个后继者
    public Leader setNextLeader(Leader leader) {
        this.leader = leader;
        return leader;
    }

    public abstract void approval(Application application);

}

2.3.3 3天以下的请假只需要 组长 审核即可
public class GroupLeader extends Leader {
    @Override
    public void approval(Application application) {
        System.out.println(application.getTitle() + "被组长审批通过");
        if (application.getDayNum() >= 3) {
            leader.approval(application);
        }
    }
}

2.3.4 3天以上5天以下的请假则需要 组长和部门经理 共同审核
public class DepartManager extends Leader {
    @Override
    public void approval(Application application) {
        System.out.println(application.getTitle() + "被部门经理审批通过");
        if (application.getDayNum() >= 5) {
            leader.approval(application);
        }
    }
}

2.2.5 5天以上的请假则还需要 总经理 审核
public class President extends Leader {
    @Override
    public void approval(Application application) {
        System.out.println(application.getTitle() + "被总经理审批通过");
    }
}

2.3.6 组建责任链并测试
// 组长
Leader groupLeader = new GroupLeader();
// 部门经理
Leader departManager = new DepartManager();
// 总经理
Leader president = new President();

groupLeader
	.setNextLeader(departManager)
	.setNextLeader(president);

groupLeader.approval(new Application("事假单", 3));
groupLeader.approval(new Application("婚假单", 10));

// 输出:
事假单被组长审批通过
事假单被部门经理审批通过
    
婚假单被组长审批通过
婚假单被部门经理审批通过
婚假单被总经理审批通过

3. 模板方法模式


3.1 定义

定义一个 操作的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下,重定义该算法的某些特定步骤。

它通常包含以下角色:

  • 抽象父类 (Abstract Class):负责给出一个算法的轮廓和骨架。它由 一个模板方法 和 若干个基本方法 构成:
  1. 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
  2. 基本方法:可以是具体的方法也可以是抽象方法,还可以是钩子方法(在抽象类中已经实现,包括用于判断的逻辑方法 和 需要子类重写的空方法 两种)。
  • 具体子类 (Concrete Class):实现抽象类中所定义的 抽象方法 和 钩子方法。

3.2 优点
  • 父类 定义了公共的行为,可以实现代码的复用;
  • 子类 可以通过扩展来增加相应的功能,符合开闭原则

3.3 示例

手机一般都有 电池、摄像头 等模块,但不是所有手机都有 NFC 模块。
如果采用 模板模式 构建,则相关代码如下:

23_template.png


3.3.1 抽象的父类
public abstract class Phone {

    // 模板方法-组装
    public void assembling() {
   		// 组装摄像头
        addCamera();
        // 安装电池
        addBattery();
        if (needAddNFC()) {
        	// 增加NFC功能
            addNFC();
        }
        // 打包
        packaged();
    }

    // 具体方法
    private void addCamera() {
        System.out.println("组装摄像头");
    }

    private void addBattery() {
        System.out.println("安装电池");
    }

    private void addNFC() {
        System.out.println("增加NFC功能");
    }

    // 钩子方法
    abstract boolean needAddNFC();

    // 抽象方法
    abstract void packaged();
}

3.3.2 具体的子类
public class OlderPhone extends Phone {

    @Override
    boolean needAddNFC() {
        return false;
    }

    @Override
    void packaged() {
        System.out.println("附赠一个手机壳");
    }
}

public class SmartPhone extends Phone {

    @Override
    boolean needAddNFC() {
        return true;
    }

    @Override
    void packaged() {
        System.out.println("附赠耳机一副");
    }
}

3.3.3 测试与输出结果如下
Phone olderPhone = new OlderPhone();
olderPhone.assembling();
// 输出:
组装摄像头
安装电池
附赠一个手机壳
    
Phone smartPhone = new SmartPhone();
smartPhone.assembling();
// 输出:
组装摄像头
安装电池
增加NFC功能
附赠耳机一副

4. 策略模式


4.1 定义

定义一系列的算法,并将它们 独立封装 后再提供给客户端使用,从而使得算法的变化不会影响到客户端的使用。
策略模式 实际上是一种无处不在的模式,比如根据在 controller 层接收到的参数不同,调用不同的 service 进行处理,这也是 策略模式 的一种体现。

4.2 示例

假设公司需要 根据营业额的不同 来 选择不同的员工激励策略。

23_strategy.png


4.2.1 策略接口 及其 实现类
public interface Strategy {
    void execute();
}

public class TravelStrategy implements Strategy {
    @Override
    public void execute() {
        System.out.println("集体旅游");
    }
}

public class BonusStrategy implements Strategy {
    @Override
    public void execute() {
        System.out.println("奖金激励");
    }
}

public class WorkOvertimeStrategy implements Strategy {
    @Override
    public void execute() {
        System.out.println("奖励加班");
    }
}

4.2.2 公司类
public class Company {

    private Strategy strategy;

    public Company setStrategy(Strategy strategy) {
        this.strategy = strategy;
        return this;
    }

    public void execute() {
        strategy.execute();
    }
}

4.2.3 客户端使用时,需要根据不同的营业额选择不同的激励策略
public static void main(String[] args) {
    // 营业额
    int turnover = Integer.parseInt(args[0]);
    
    Company company = new Company();
    if (turnover > 1000) {
    	// 奖金策略
        company.setStrategy(new BonusStrategy()).execute();
    } else if (turnover > 100) {
    	// 旅游策略
        company.setStrategy(new TravelStrategy()).execute();
    } else {
    	// 加班策略
        company.setStrategy(new WorkOvertimeStrategy()).execute();
    }
}

5. 状态模式


5.1 定义

对有状态的对象,把 复杂的判断逻辑 提取到不同的 状态对象 中,允许 状态对象 在其内部状态发生改变时改变其行为。


5.2 优点
  • 状态模式 将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足 单一职责原则
  • 将 不同的状态 引入到独立的对象中,使得状态转换变得更加明确,且减少对象间的相互依赖;
  • 有利于程序的扩展,通过定义新的子类很容易地 增加新的状态和转换。

5.3 示例

假设我们正在开发一个 播放器,它有如下图所示四种基本的状态:播放状态、关闭状态、暂停状态、加速播放状态。
这四种状态间可以相互转换,但存在一定的限制,比如在关闭或者暂停状态下,都不能加速视频。
采用 状态模式 来实现该 播放器 的相关代码如下:

23_state.png


5.3.1 定义状态抽象类
public class State {

    private Player player;

    public void setPlayer(Player player) {
        this.player = player;
    }

    public void play() {
        player.setState(Player.PLAY_STATE);
    }

    public void pause() {
        player.setState(Player.PAUSE_STATE);
    }

    public void close() {
        player.setState(Player.CLOSE_STATE);
    }

    public void speed() {
        player.setState(Player.SPEED_STATE);
    }
}

5.3.2 定义四种状态的具体实现类,并限制它们之间的转换关系
public class PlayState extends State {

}
public class CloseState extends State {
    @Override
    public void pause() {
        System.out.println("操作失败:视频已处于关闭状态,无需暂停");
    }
    @Override
    public void speed() {
        System.out.println("操作失败:视频已处于关闭状态,无法加速");
    }
}
public class PauseState extends State {
    @Override
    public void speed() {
        System.out.print("操作失败:暂停状态下不支持加速");
    }
}
public class SpeedState extends State {
    @Override
    public void play() {
        System.out.println("系统提示:你当前已处于加速播放状态");
    }
}

5.3.3 组装播放器
public class Player {

    private State state;

    public final static PlayState PLAY_STATE = new PlayState();
    public final static PauseState PAUSE_STATE = new PauseState();
    public final static CloseState CLOSE_STATE = new CloseState();
    public final static SpeedState SPEED_STATE = new SpeedState();

    public State getState() {
        return state;
    }

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

    Player() {
        // 假设播放器初始状态为关闭
        this.state = new CloseState();
        this.state.setPlayer(this);
    }

    public void play() {
        System.out.println("播放视频");
        state.play();
    }

    public void pause() {
        System.out.println("暂停视频");
        state.pause();
    }

    public void close() {
        System.out.println("关闭视频");
        state.close();
    }

    public void speed() {
        System.out.println("视频加速");
        state.speed();
    }

}

5.3.4 调用我们自定义的播放器
Player player = new Player();
player.speed();
player.play();
player.speed();
player.play();
player.pause();
player.close();
player.speed();

// 输出:
视频加速
操作失败:视频已处于关闭状态,无法加速

播放视频
视频加速
播放视频
系统提示:你当前已处于加速播放状态

暂停视频
关闭视频
视频加速
操作失败:视频已处于关闭状态,无法加速

6. 中介者模式


6.1 定义

定义一个 中介对象 来封装 一系列对象 之间的交互。
使 原有对象 之间的耦合松散,且可以独立地改变它们之间的交互。


6.2 优点

降低了对象之间的 耦合度


6.3 示例

6.3.1 中介类

这里以房屋中介为例,定义一个抽象的中介类,负责接收客户以及在客户间传递消息。

abstract class Mediator {
    public abstract void register(Person person);

    public abstract void send(String from, String message);
}

6.3.2 房屋中介

具体的房屋中介,它会将卖方的出售消息广播给所有人。

public class HouseMediator extends Mediator {

    private final List<Person> personList = new ArrayList<>();

    @Override
    public void register(Person person) {
        if (!personList.contains(person)) {
            personList.add(person);
            person.setMediator(this);
        }
    }

    @Override
    public void send(String from, String message) {
        System.out.println(from + "发送消息:" + message);
        for (Person person : personList) {
            String name = person.getName();
            if (!name.equals(from)) {
                person.receive(message);
            }
        }
    }
}

6.3.3 用户类

定义用户类,它可以是买方也可以是卖方,它们都是中介的客户。

public class Person {

    private String name;
    private Mediator mediator;

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

    public void send(String message) {
        mediator.send(this.name, message);
    }

    public void receive(String message) {
        System.out.println(name + "收到消息:" + message);
    }
}

6.3.4 最后买卖双方通过中介人就可以进行沟通
public class ZTest {
    public static void main(String[] args) {
        HouseMediator houseMediator = new HouseMediator();
        
        Person seller = new Person("卖方");
        Person buyer = new Person("买方");
        houseMediator.register(seller);
        houseMediator.register(buyer);
        
        buyer.send("价格多少");
        seller.send("10万");
        buyer.send("太贵了");
    }
}

// 输出:
买方发送消息:价格多少
卖方收到消息:价格多少
卖方发送消息:10万
买方收到消息:10万
买方发送消息:太贵了
卖方收到消息:太贵了

7. 迭代器模式


7.1 定义

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


7.2 示例

7.2.1 书籍类

假设现在对 书架 中的 所有书籍 进行遍历,首先定义 书籍类。

public class Book {
    private String name;
    public Book(String name) {
        this.name = name;
    }
}

7.2.2 书架接口 及其 实现类

定义 书柜接口 及其 实现类。

public interface Bookshelf {
    void addBook(Book book);
    void removeBook(Book book);
    BookIterator iterator();
}

public class BookshelfImpl implements Bookshelf {

    private final List<Book> bookList = new ArrayList<>();

    @Override
    public void addBook(Book book) {
        bookList.add(book);
    }

    @Override
    public void removeBook(Book book) {
        bookList.remove(book);
    }

    @Override
    public BookIterator iterator() {
        return new BookIterator(bookList);
    }
}

7.2.3 定义迭代器接口 及其 实现类
public interface Iterator<E> {
    E next();
    boolean hasNext();
}

public class BookIterator implements Iterator<Book> {

    private final List<Book> bookList;
    private int position = 0;

    public BookIterator(List<Book> bookList) {
        this.bookList = bookList;
    }

    @Override
    public Book next() {
        return bookList.get(position++);
    }

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

7.2.4 调用自定义的迭代器进行遍历
BookshelfImpl bookshelf = new BookshelfImpl();
bookshelf.addBook(new Book("Java书籍"));
bookshelf.addBook(new Book("Python书籍"));
bookshelf.addBook(new Book("Go书籍"));

BookIterator iterator = bookshelf.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

8. 访问者模式


8.1 定义

表示一个作用于 某对象结构中 各个元素的操作,它使得用户可以在不改变 各个元素 所对应类的前提下定义对这些元素的操作。


8.2 优缺点

优点:

  • 扩展性好:能够在 不修改对象结构中的元素 的情况下,为 对象结构中的元素 添加新的功能。
  • 复用性好:可以通过 访问者 来定义整个对象结构通用的功能,从而提高系统的复用程度。
  • 灵活性好:访问者模式 将数据结构与作用于结构上的操作解耦,使得操作可相对自由地演化而不影响系统的数据结构。

缺点:

  • 增加新的元素类很复杂;
  • 实现相对繁琐。

8.3 示例

通常 不同级别的员工 对于 公司档案的访问权限是不同的。
为方便理解,如下图所示假设只有 公开 和 加密 两种类型的档案,并且只有 总经理 和 部门经理 才能进入档案室。

23_visitor.png


8.3.1 定义档案类 及其 实现类
public interface Archive {
    // 接受访问者
    void accept(Visitor visitor);
}

public class PublicArchive implements Archive {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

public class SecretArchive implements Archive {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

8.3.2 定义访问者接口 及其 实现类
public interface Visitor {
    // 访问公开档案
    void visit(PublicArchive publicArchive);
    // 访问加密档案
    void visit(SecretArchive secretArchive);
}
public class DepartManager implements Visitor {

    @Override
    public void visit(PublicArchive publicArchive) {
        System.out.println("所有公开档案");
    }

    @Override
    public void visit(SecretArchive secretArchive) {
        System.out.println("三级以下权限的加密档案");
    }
}
public class President implements Visitor {

    @Override
    public void visit(PublicArchive publicArchive) {
        System.out.println("所有公开档案");
    }

    @Override
    public void visit(SecretArchive secretArchive) {
        System.out.println("所有加密档案");
    }
}

8.3.3 通过 公司类 来管理访问
public class Company {

    private final List<Archive> archives = new ArrayList<>();

    // 接收档案
    void add(Archive archive) {
        archives.add(archive);
    }

    // 移除档案
    void remove(Archive archive) {
        archives.remove(archive);
    }

    // 接待访问者
    void accept(Visitor visitor) {
        for (Archive archive : archives) {
            archive.accept(visitor);
        }
    }

}

8.3.4 测试及输出结果
Company company = new Company();
company.add(new SecretArchive());
company.add(new PublicArchive());

company.accept(new DepartManager());
// 输出:
三级以下权限的加密档案
所有公开档案

company.accept(new President());
// 输出:
所有加密档案
所有公开档案

9. 备忘录模式


9.1 定义

在不破坏 封装性 的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便在需要时能将该对象恢复到原有状态。


9.2 优缺点

优点 是能够用于异常情况下的恢复,能够提高系统的安全性;
缺点 是需要额外的存储空间来保存历史状态。


9.3 示例

编辑器的撤销功能、数据库的快照功能 都是 备忘录模式 的一种典型实现。
这里我们以 Git 保存历史版本信息 为例,进行演示。


9.3.1 文章类
public class Article {
    private String title;
    private String content;
}

9.3.2 备忘录对象

根据业务需求的不同,你可能只需要保存数据的部分字段,或者还需要额外增加字段(如保存时间等),所以在保存时需要将 目标对象 转换为 备忘录对象。

// 备忘录对象
public class Memorandum {

    private final String title;
    private final String content;
    private Date createTime;

    // 根据目标对象来创建备忘录对象
    public Memorandum(Article article) {
        this.title = article.getTitle();
        this.content = article.getContent();
        this.createTime = new Date();
    }

    public Article toArticle() {
        return new Article(this.title, this.content);
    }

}

9.3.3 管理者类
public class GitRepository {

    private final List<Memorandum> repository = new ArrayList<>();

    // 保存当前数据
    public void save(Article article) {
        Memorandum memorandum = new Memorandum(article);
        repository.add(memorandum);
    }

    // 获取指定版本类的数据
    public Article get(int version) {
        Memorandum memorandum = repository.get(version);
        return memorandum.toArticle();
    }

    // 撤销当前操作
    public Article back() {
        return repository.get(repository.size() - 1).toArticle();
    }
}

9.3.4 测试类
GitRepository repository = new GitRepository();
Article article = new Article("Java手册", "版本一");
repository.save(article);

article.setContent("版本二");
repository.save(article);

article.setContent("版本三");
repository.save(article);

System.out.println(repository.back());
System.out.println(repository.get(0));

10. 解释器模式

给分析对象定义一种语言,并定义该语言的文法表示,然后设计一个 解析器 来解释语言中的语句,用编译语言的方式来分析应用中的实例。
解释器模式 的实现比较复杂,在通常的开发也较少使用,这里就不提供示例了。


五、参考资料

  1. Erich Gamma / Richard Helm / Ralph Johnson / John Vlissides . 设计模式(可复用面向对象软件的基础). 机械工业出版社 . 2000-9
  2. 秦小波 . 设计模式之禅(第2版). 机械工业出版社 . 2014-2-25
  3. Java 设计模式
  4. Java 设计模式精讲 Debug 方式 + 内存分析

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑士梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值