案例背景
假设我们有一个游戏系统,游戏中需要频繁创建 敌人对象。每个敌人对象包含以下属性:
- 名称(
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("暗黑巨魔", 100, 10, 5);
}
}
测试代码
public class EnemyTest {
@Test
public void getEnemy() {
// 第一次获取敌人对象 加载数据
Enemy enemyA = EnemyConfig.loadEnemyFromConfig();
// 第二次获取敌人对象 加载数据
Enemy enemyB = EnemyConfig.loadEnemyFromConfig();
System.out.println("敌军一号: " + enemyA);
System.out.println("敌军二号: " + enemyB);
}
}
存在的问题
-
重复初始化:
- 每次创建敌人对象时,都需要从配置文件中加载数据并初始化,导致重复的 I/O 操作和初始化逻辑。
-
性能问题:
- 如果配置文件加载过程耗时较长(如读取数据库或远程接口),频繁创建敌人对象会导致性能下降。
-
代码冗余:
- 初始化逻辑分散在多个地方,难以维护和扩展。
原型模式重构
1. 原型模式的主要内容
定义
原型模式是一种 创建型设计模式,它通过复制现有对象来创建新对象,而不是通过调用构造函数。原型模式的核心思想是 克隆。
核心角色
-
原型接口(Prototype) :
- 定义克隆方法的接口。
-
具体原型(Concrete Prototype) :
- 实现原型接口,提供克隆方法的具体实现。
-
客户端(Client) :
- 使用原型对象创建新对象。
2. 使用场景
原型模式适用于以下场景:
-
对象创建成本高:
- 当对象的创建过程非常复杂或耗时,且需要频繁创建相似对象时。
- 例如:数据库连接、线程池。
-
对象状态相似:
- 当需要创建的对象与现有对象状态相似时。
- 例如:游戏中的敌人、文档模板。
-
避免构造函数调用:
- 当希望避免调用构造函数来创建对象时。
- 例如:某些构造函数参数复杂或不可控。
3. 使用原型模式进行重构
重构思路
-
定义原型接口:
- 让
Enemy类实现Cloneable接口,支持克隆。
- 让
-
实现克隆方法:
- 在
Enemy类中实现clone()方法,复制现有对象的属性。
- 在
-
使用原型对象:
- 从配置文件中加载一个原型对象,后续通过克隆原型对象来创建新对象。
重构后的代码
敌人类: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("暗黑巨魔", 100, 10, 5);
}
}
客户端代码
public class EnemyTest {
@Test
public void getEnemy() {
// 第一次获取敌人对象 加载数据
Enemy enemyA = EnemyConfig.loadEnemyFromConfig();
Enemy enemyB = enemyA.clone();
System.out.println("敌军一号: " + enemyA);
System.out.println("敌军二号: " + enemyB);
}
}
重构后的优势
-
减少初始化成本:
- 只需要从配置文件中加载一次原型对象,后续通过克隆创建新对象,避免了重复的 I/O 操作和初始化逻辑。
-
提升性能:
- 克隆操作比从配置文件加载数据更快,显著提升了性能。
-
代码更简洁:
- 初始化逻辑集中在原型对象中,客户端代码更简洁、更易维护。
4. 开源项目中的应用
长话短说
1、核心思想
- 克隆代替创建:通过复制现有对象来创建新对象,而不是通过调用构造函数。
- 减少创建成本:避免重复执行复杂的初始化逻辑。
2、何时可以使用原型模式?
- 对象的创建成本高(如需要从配置文件或数据库加载数据)。
- 需要频繁创建相似对象。
- 希望避免重复的初始化逻辑。
3、使用思路
-
定义原型接口:
- 定义克隆方法的接口。
- 例如:
Cloneable接口。
-
实现具体原型:
- 实现原型接口,提供克隆方法的具体实现。
- 例如:实现
Cloneable接口的类。
-
客户端使用原型:
- 使用原型对象创建新对象。
- 例如:调用
clone()方法复制对象。
设计模式是好东西,用对了项目好维护。但别硬套,不然设计过度,浪费成本。