1.概要
2.内容
当前:原型模式(使用价值方面说明)
需求:坦克大战
创建两种坦克
坦克类型 | 射程 | 速度 |
b70 | 70米 | 时/70公里 |
b50 | 50米 | 时/70公里 |
类图
需求设计
我们假设坦克有两种,
一种数速度型坦克
一种是构造型坦克
速度型坦克的改变,无非就是改变数据,70,50,60
而发射行坦克无非改变的就是设计的距离 50 100 105
该实例通过,通过改变参数创建不同类型的坦克。
而不是通过类,如果分类的对象过多了,可以考虑通过该方式来消减类。
这里面其实用了桥接的思想。
这里改变的规格就相当于桥接对象。
类本身承担一个维度的变化,而这个规格的改变又承担了一个规格的变化。
坦克类型决定了坦克的基本功能,而规格的变化决定了他的性能。
模式价值:就是把类型迁移到参数里。
本来是需要分类的,但是用一个成员变量承载了这个变化,通过给一类对象设计不同的参数产生实际类别的对象。
优点:避免类型数量的增加。
适用情况:一个类对外接口稳定,但是在实际应用中,需要衍生很多多的子类。
子类的数量多到我们不愿意去接受,比如20个50个等等。
这时候我们怎么办,我们要降低类的数量。
什么降低呢,当然从问题入手。
看什么因素导致了这么多的类,这个因素一定是一个或几个成员变量。
那么我们把这些成员拿出来,给这些成员提供接口。
用设置这些成员的方法来来转换类的衍生数量。
这就是原型模式的基本创建过程。
这里有一个问题需要解释一下,什么叫稳定的接口呢。
举个例子吧
A{
int mA;
B mB;
public:
fun(int a)
fun2(int b)
private fun3();
}
mA的名称变化了,接口是否稳定。当然稳定。
mB的fun3变化了,接口是否稳定。当然稳定,因为外部接口没有变化。
fun的名称变化了,接口不稳定了。
fun2 的参数编程两个了,接口不稳定了。
这样,应该明白什么叫接口稳定了吧。
说白了,就是对外提供的函数没有变化。
接口没变,但是实际上对象变化了,变化有谁承载呢,由成员变量的变化承载;
当然也有可能是由函数内的不同处理逻辑承载,但是这个模式主要体现的是由变量承载的变化。
总结一下:用自己拷贝对象,用参数的变化承载对象的差异。
代码
interface ITank{
void setmName(String mName);
ITank clones();
void exe();
void setmSpecification(int mSpecification);
}
abstract class Tank implements ITank{
int mSpecification;
String mName;
String mType;
protected void setmType(String mType) {
this.mType = mType;
}
public void setmName(String mName) {
this.mName = mName;
}
public void setmSpecification(int mSpecification) {
this.mSpecification = mSpecification;
}
}
class ShotTank extends Tank{
public ShotTank() {
mType = "射击";
}
public ITank clones() {
Tank t = new ShotTank();
t.setmType(mType);
return t;
}
public void exe() {
System.out.println("型坦克型号:"+mType+mName);
System.out.println("射击距离:"+mSpecification+"米");
}
}
class RunTank extends Tank{
public RunTank() {
mType = "奔跑";
}
public ITank clones() {
Tank t = new ShotTank();
t.setmType(mType);
return t;
}
public void exe() {
System.out.println("坦克型号:"+mType+mName);
System.out.println("速度:"+mSpecification+"公里");
}
}
class ShotFactory{
public ITank createS70(ITank shotPrototype) {
ITank shot = shotPrototype.clones();
shot.setmName("S70");
shot.setmSpecification(70);
return shot;
}
public ITank createS50(ITank shotPrototype) {
ITank shot = shotPrototype.clones();
shot.setmName("S51");
shot.setmSpecification(50);
return shot;
}
public ITank createS43(ITank shotPrototype) {
ITank shot = shotPrototype.clones();
shot.setmName("S43");
shot.setmSpecification(40);
return shot;
}
}
class RunFactory{
public ITank createR80(ITank runPrototype) {
ITank run = runPrototype.clones();
run.setmName("R80");
run.setmSpecification(70);
return run;
}
public ITank createR999(ITank runPrototype) {
ITank run = runPrototype.clones();
run.setmName("R999");
run.setmSpecification(999);
return run;
}
public ITank createR47(ITank runPrototype) {
ITank run = runPrototype.clones();
run.setmName("R47");
run.setmSpecification(440);
return run;
}
}
public class Client {
public static void main(String[] args) {
System.out.println("Prototype 演示\n");
ITank shotPrototype = new ShotTank();
ITank runPrototype = new RunTank();
ShotFactory sf = new ShotFactory();
sf.createS43(shotPrototype).exe();
sf.createS70(shotPrototype).exe();
sf.createS50(shotPrototype).exe();
System.out.println("");
RunFactory rf = new RunFactory();
rf.createR80(runPrototype).exe();
rf.createR999(runPrototype).exe();
rf.createR47(runPrototype).exe();
}
}
运行结果
3.关联知识
1.原型模式
原型模式(Prototype Pattern)是创建型设计模式之一,通过复制现有对象来生成新对象,而非通过新建类实例。其核心思想是避免重复初始化对象,尤其适用于对象创建成本较高或需要动态配置的场景。
核心思想
- 克隆代替新建:通过实现克隆方法(如
clone()
)直接复制对象,跳过复杂的构造函数。 - 深拷贝 vs 浅拷贝:
- 浅拷贝:仅复制对象本身,引用类型字段共享(修改会影响原对象)。
- 深拷贝:完全复制对象及其所有引用字段,新旧对象完全独立。
适用场景
- 对象创建复杂(如需要初始化大量数据或资源)。
- 需要动态配置对象类型(如通过类名动态加载类)。
- 避免重复初始化开销(如频繁创建相似对象)。
结构组成
- 原型接口(Prototype):声明克隆方法(如
clone()
)。 - 具体原型类(ConcretePrototype):实现克隆方法,处理深拷贝逻辑。
- 客户端(Client):通过调用
clone()
创建新对象。
代码示例(Java)
// 1. 定义原型接口
interface Prototype {
Prototype clone();
}
// 2. 实现具体原型类(深拷贝示例)
class ConcretePrototype implements Prototype, Serializable {
private String name;
private List<String> hobbies;
public ConcretePrototype(String name, List<String> hobbies) {
this.name = name;
this.hobbies = new ArrayList<>(hobbies); // 深拷贝引用字段
}
@Override
public Prototype clone() {
try {
// 通过序列化实现深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Prototype) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("Clone failed", e);
}
}
// Getters and Setters...
}
// 3. 客户端使用
public class Client {
public static void main(String[] args) {
List<String> hobbies = new ArrayList<>();
hobbies.add("Reading");
ConcretePrototype original = new ConcretePrototype("Alice", hobbies);
ConcretePrototype cloned = (ConcretePrototype) original.clone();
cloned.setName("Bob");
cloned.getHobbies().add("Gaming");
System.out.println(original.getName()); // Alice
System.out.println(original.getHobbies()); // [Reading](未受影响)
}
}
优缺点
优点 | 缺点 |
---|---|
避免重复初始化,性能高 | 深拷贝实现复杂(需处理循环引用等) |
动态配置对象类型(如反射) | 某些语言(如Java)需显式实现克隆方法 |
简化对象创建逻辑 | 浅拷贝可能导致意外数据共享 |
注意事项
- 深拷贝实现:Java中默认
Object.clone()
是浅拷贝,需手动处理深拷贝(如序列化、递归复制)。 - 循环引用:深拷贝时需检测循环引用,避免无限递归。
- 性能权衡:深拷贝可能带来额外开销,需根据场景选择。
典型应用
- 配置对象:复制默认配置后修改部分参数。
- 复杂对象树:快速复制整个对象结构(如文档编辑器中的节点)。
- 插件系统:动态加载类并复制实例。
通过合理使用原型模式,可以显著提升对象创建效率,尤其在需要频繁复制对象的场景中。
2.设计模式的原型模式深入分析
设计模式之原型模式:深入分析
原型模式(Prototype Pattern)的核心是通过复制现有对象来创建新对象,而非通过构造函数或工厂方法。以下从实现细节、应用场景、优化策略、挑战与解决方案等角度展开深入分析。
一、实现细节与关键机制
- 浅拷贝 vs 深拷贝
- 浅拷贝:仅复制对象的基本字段,引用类型字段共享(新旧对象指向同一内存地址)。
- 深拷贝:完全复制对象及其所有引用字段,确保新旧对象完全独立。
- 实现方式:
- Java:默认
Object.clone()
是浅拷贝,需重写并实现深拷贝逻辑。 - 序列化:通过
ObjectOutputStream
和ObjectInputStream
实现深拷贝(需处理Serializable
接口)。 - 手动复制:递归复制所有引用字段(需处理循环引用问题)。
- Java:默认
- 循环引用问题
- 场景:对象A引用对象B,对象B又引用对象A。
- 解决方案:
- 哈希表标记:在序列化或手动复制时,用哈希表记录已复制的对象,避免重复复制。
- 忽略循环引用:根据业务场景决定是否允许循环引用,或抛出异常。
二、应用场景扩展
- 高性能对象创建
- 场景:对象初始化成本高(如数据库连接池、复杂计算对象)。
- 优势:通过克隆跳过初始化过程,直接复制已准备好的状态。
- 动态配置与插件系统
- 场景:根据运行时参数动态创建对象(如通过类名字符串加载类)。
- 实现:结合反射机制,从原型对象派生新实例并动态配置。
- 对象状态备份与恢复
- 场景:需要保存对象状态快照(如游戏存档、撤销操作)。
- 实现:通过深拷贝创建状态副本,供后续恢复使用。
三、优化策略与设计扩展
- 原型管理器(Prototype Manager)
- 作用:集中管理原型对象,提供注册、检索和克隆功能。
- 优势:
- 缓存原型:避免重复创建原型对象。
- 动态扩展:运行时注册新原型类型。
- 示例代码:
class PrototypeManager { private Map<String, Prototype> prototypes = new HashMap<>(); public void registerPrototype(String name, Prototype prototype) { prototypes.put(name, prototype); } public Prototype getPrototype(String name) { return prototypes.get(name).clone(); } }
- 结合享元模式(Flyweight Pattern)
- 场景:需要共享部分对象状态以减少内存占用。
- 实现:将公共状态存储在原型对象中,克隆时仅复制可变状态。
- 线程安全设计
- 问题:多线程环境下克隆可能导致数据不一致。
- 解决方案:
- 同步克隆方法:使用
synchronized
关键字。 - 不可变对象:确保原型对象在克隆过程中状态不变。
- 同步克隆方法:使用
四、挑战与解决方案
- 深拷贝的复杂性
- 挑战:对象结构复杂时,手动实现深拷贝容易出错。
- 解决方案:
- 代码生成工具:如 Lombok 的
@DeepCopy
注解(需配合库支持)。 - 反射+递归:自动遍历字段并复制(需处理私有字段和循环引用)。
- 代码生成工具:如 Lombok 的
- 维护成本
- 挑战:对象结构变化时需同步修改克隆方法。
- 解决方案:
- 统一克隆接口:强制子类实现克隆逻辑。
- 自动化测试:验证克隆后的对象与原对象独立性。
- 性能权衡
- 挑战:深拷贝可能带来额外性能开销。
- 解决方案:
- 懒拷贝(Lazy Copy):仅在需要时复制引用字段。
- 选择性拷贝:根据业务场景仅复制关键字段。
五、实际案例与框架应用
- 游戏开发
- 场景:快速生成敌人、道具等对象。
- 实现:定义基础原型(如
EnemyPrototype
),通过克隆调整属性(如位置、血量)。
- 文档编辑器
- 场景:复制节点及其子节点树。
- 实现:深拷贝节点对象,保持父子关系引用。
- Spring框架中的原型作用域(Prototype Scope)
- 机制:每次获取Bean时创建新实例(类似原型模式思想)。
- 区别:Spring通过代理和依赖注入管理对象生命周期,而非手动克隆。
六、与其他创建型模式对比
模式 | 核心思想 | 适用场景 |
---|---|---|
工厂模式 | 通过子类决定创建对象 | 对象类型明确,需解耦创建逻辑 |
抽象工厂 | 创建一系列相关产品 | 多产品族,需保证产品兼容性 |
建造者模式 | 分步构建复杂对象 | 对象构造过程复杂,需灵活配置 |
原型模式 | 通过复制现有对象创建新对象 | 对象创建成本高,需动态配置 |
七、最佳实践总结
- 明确拷贝需求:优先使用浅拷贝,仅在必要时实现深拷贝。
- 封装克隆逻辑:将克隆方法定义在接口中,强制子类实现。
- 结合管理器:通过原型管理器集中控制原型对象的注册和克隆。
- 权衡性能:在深拷贝开销较大时,考虑懒拷贝或选择性拷贝。
- 防御性编程:处理循环引用、空指针等边界情况。
通过深入理解原型模式的实现细节、应用场景和优化策略,可以更灵活地将其应用于实际开发中,提升代码的可维护性和性能。