设计模式-原型模式

159 阅读4分钟

案例背景

假设我们有一个游戏系统,游戏中需要频繁创建 敌人对象。每个敌人对象包含以下属性:

  • 名称(name
  • 血量(health
  • 攻击力(attackPower
  • 防御力(defensePower

敌人的初始属性是通过配置文件加载的,创建敌人对象时需要从配置文件中读取数据并初始化。

代码实现
敌人类:Enemy
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Enemy {

    /**
     * 名称
     */
    private String name;

    /**
     * 血量
     */
    private int health;

    /**
     * 攻击力
     */
    private int attackPower;

    /**
     * 防御力
     */
    private int defensePower;

    @Override
    public String toString() {
        return "{" +
                "name='" + name + ''' +
                ", health=" + health +
                ", attackPower=" + attackPower +
                ", defensePower=" + defensePower +
                '}';
    }
}
数据来源加载类
/**
 * 从配置信息中加载敌人信息
 */
public class EnemyConfig {

    public static Enemy loadEnemyFromConfig() {
        // 模拟数据由配置文件 或 数据库加载
        return new Enemy("暗黑巨魔"100105);
    }

}
测试代码
public class EnemyTest {

    @Test
    public void getEnemy() {
        // 第一次获取敌人对象 加载数据
        Enemy enemyA = EnemyConfig.loadEnemyFromConfig();

        // 第二次获取敌人对象 加载数据
        Enemy enemyB = EnemyConfig.loadEnemyFromConfig();
  
        System.out.println("敌军一号: " + enemyA);
        System.out.println("敌军二号: " + enemyB);
    }

}
存在的问题
  1. 重复初始化

    • 每次创建敌人对象时,都需要从配置文件中加载数据并初始化,导致重复的 I/O 操作和初始化逻辑。
  2. 性能问题

    • 如果配置文件加载过程耗时较长(如读取数据库或远程接口),频繁创建敌人对象会导致性能下降。
  3. 代码冗余

    • 初始化逻辑分散在多个地方,难以维护和扩展。

原型模式重构

1. 原型模式的主要内容

定义

原型模式是一种 创建型设计模式,它通过复制现有对象来创建新对象,而不是通过调用构造函数。原型模式的核心思想是 克隆

核心角色

  1. 原型接口(Prototype)

    • 定义克隆方法的接口。
  2. 具体原型(Concrete Prototype)

    • 实现原型接口,提供克隆方法的具体实现。
  3. 客户端(Client)

    • 使用原型对象创建新对象。

2. 使用场景

原型模式适用于以下场景:

  1. 对象创建成本高

    • 当对象的创建过程非常复杂或耗时,且需要频繁创建相似对象时。
    • 例如:数据库连接、线程池。
  2. 对象状态相似

    • 当需要创建的对象与现有对象状态相似时。
    • 例如:游戏中的敌人、文档模板。
  3. 避免构造函数调用

    • 当希望避免调用构造函数来创建对象时。
    • 例如:某些构造函数参数复杂或不可控。

3. 使用原型模式进行重构

重构思路
  1. 定义原型接口

    • Enemy 类实现 Cloneable 接口,支持克隆。
  2. 实现克隆方法

    • Enemy 类中实现 clone() 方法,复制现有对象的属性。
  3. 使用原型对象

    • 从配置文件中加载一个原型对象,后续通过克隆原型对象来创建新对象。
重构后的代码
敌人类:Enemy(实现 Cloneable 接口)
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Enemy implements Cloneable {

    /**
     * 名称
     */
    private String name;

    /**
     * 血量
     */
    private int health;

    /**
     * 攻击力
     */
    private int attackPower;

    /**
     * 防御力
     */
    private int defensePower;

    @Override
    public Enemy clone() {
        try {
            return (Enemy) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("对象拷贝异常: ", e);
        }
    }

    @Override
    public String toString() {
        return "{" +
                "name='" + name + ''' +
                ", health=" + health +
                ", attackPower=" + attackPower +
                ", defensePower=" + defensePower +
                '}';
    }
}
数据来源加载类
/**
 * 从配置信息中加载敌人信息
 */
public class EnemyConfig {

    public static Enemy loadEnemyFromConfig() {
        // 模拟数据由配置文件 或 数据库加载
        return new Enemy("暗黑巨魔"100105);
    }

}
客户端代码
public class EnemyTest {

    @Test
    public void getEnemy() {
        // 第一次获取敌人对象 加载数据
        Enemy enemyA = EnemyConfig.loadEnemyFromConfig();
        Enemy enemyB = enemyA.clone();

        System.out.println("敌军一号: " + enemyA);
        System.out.println("敌军二号: " + enemyB);
    }

}

重构后的优势

  1. 减少初始化成本

    • 只需要从配置文件中加载一次原型对象,后续通过克隆创建新对象,避免了重复的 I/O 操作和初始化逻辑。
  2. 提升性能

    • 克隆操作比从配置文件加载数据更快,显著提升了性能。
  3. 代码更简洁

    • 初始化逻辑集中在原型对象中,客户端代码更简洁、更易维护。

4. 开源项目中的应用

长话短说

1、核心思想

  • 克隆代替创建:通过复制现有对象来创建新对象,而不是通过调用构造函数。
  • 减少创建成本:避免重复执行复杂的初始化逻辑。

2、何时可以使用原型模式?

  • 对象的创建成本高(如需要从配置文件或数据库加载数据)。
  • 需要频繁创建相似对象。
  • 希望避免重复的初始化逻辑。

3、使用思路

  1. 定义原型接口

    • 定义克隆方法的接口。
    • 例如:Cloneable 接口。
  2. 实现具体原型

    • 实现原型接口,提供克隆方法的具体实现。
    • 例如:实现 Cloneable 接口的类。
  3. 客户端使用原型

    • 使用原型对象创建新对象。
    • 例如:调用 clone() 方法复制对象。

设计模式是好东西,用对了项目好维护。但别硬套,不然设计过度,浪费成本。