1. 定义
原型模式(Prototype Pattern)是一种创建型设计模式,旨在通过复制现有对象来创建新对象,而不是通过实例化类的方式。这个模式可以提高对象创建的效率,尤其是在创建对象的过程非常复杂或代价高昂时。
2. 结构
原型模式包含以下角色:
- Prototype(原型接口):用于声明克隆自身的方法。通常这个接口会定义一个名为clone的抽象方法。
- ConcretePrototype(具体原型类):实现原型接口,并实现克隆自身的方法。这类对象可以被克隆。
- Client(客户端):使用原型接口来克隆新的对象。
UML类图:
3. 示例代码
// 原型接口
interface Prototype extends Cloneable {
Prototype clone();
}
// 具体原型类A
class ConcretePrototypeA implements Prototype {
private String name;
public ConcretePrototypeA(String name) {
this.name = name;
}
@Override
public Prototype clone() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "ConcretePrototypeA{name='" + name + "'} hashcode= "+ hashCode();
}
}
// 具体原型类B
class ConcretePrototypeB implements Prototype {
private int value;
public ConcretePrototypeB(int value) {
this.value = value;
}
@Override
public Prototype clone() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "ConcretePrototypeB{value=" + value + "} hashcode= "+ hashCode();
}
}
测试:
@Test
public void test() {
ConcretePrototypeA prototypeA = new ConcretePrototypeA("Prototype A");
ConcretePrototypeB prototypeB = new ConcretePrototypeB(42);
Prototype clonedPrototypeA = prototypeA.clone();
Prototype clonedPrototypeB = prototypeB.clone();
System.out.println("原型A: "+prototypeA);
System.out.println("克隆A: "+clonedPrototypeA);
System.out.println("原型B: "+prototypeB);
System.out.println("克隆B: "+clonedPrototypeB);
}
打印:
原型A: ConcretePrototypeA{name='Prototype A'} hashcode= 1823541245
克隆A: ConcretePrototypeA{name='Prototype A'} hashcode= 1020154737
原型B: ConcretePrototypeB{value=42} hashcode= 398457879
克隆B: ConcretePrototypeB{value=42} hashcode= 1850954068
4. 应用场景
- 对象的创建开销很大:通过克隆现有对象而不是重新创建,可以节省时间和资源。
- 系统需要大量类似对象:通过克隆原型对象,可以快速生成多个相似但
独立
的对象。 - 对象的状态需要动态改变:通过克隆原型对象,可以创建具有特定状态的新对象。
情景回顾:
原型模式在实际应用中可能并不常见,或许你见到过但没留意,因为它的应用场景太少了,让我想起了在写一个多任务下载模块时,每下载一个文件其都有对应的mode,记录一些下载信息状态信息等内容,再进行更新进度的时候需要把这个对象发送出去,通知外界更新UI,有一个问题就是下载中操作的对象和发送出去的对象是同一个,就会出现异常现象,我发送出去的下载进度是90%,UI还没来得及更新,这边又将进度修改为95%了,emm… 可能我描述的问题,你觉的不严重,但这种不可预测的现象是我们不希望发生的。
其中解决方式一 就是克隆一个对象,让UI显示的和下载操作的对象互不干扰,这时如果你new
一个新对象的话相比克隆就劣势就凸显出来了。
5. 优缺点
优点:
- 提高对象创建效率:避免了复杂对象的重复创建,通过克隆现有对象来生成新对象。
- 动态创建对象:可以在运行时动态创建对象,而无需了解具体的类。
- 减少子类的数量:通过克隆原型对象,可以减少创建子类的数量,增强系统的灵活性。
缺点:
- 深拷贝和浅拷贝问题:在涉及复杂对象时,深拷贝和浅拷贝的问题需要特别注意。如果对象包含对其他对象的引用,浅拷贝可能不够用,需要实现深拷贝。
- 克隆方法的实现复杂:对于一些复杂的对象,克隆方法的实现可能比较复杂,需要处理对象间的依赖关系。
6. 深拷贝与浅拷贝
在原型模式中,克隆可以分为浅拷贝和深拷贝:
- 浅拷贝:复制对象时,只复制对象本身,而不复制对象所引用的其他对象。也就是说,复制后的对象与原对象共享对其他对象的引用。
- 深拷贝:复制对象时,不仅复制对象本身,还复制对象所引用的其他对象。这样,复制后的对象与原对象是完全独立的,不共享任何引用。
7. 深拷贝与浅拷贝示例
// 原型接口
interface Prototype extends Cloneable {
Prototype clone();
}
// 具体原型类
class ConcretePrototype implements Prototype {
private String name;
private List<String> list;
public ConcretePrototype(String name) {
this.name = name;
this.list = new ArrayList<>();
}
public void addToList(String item) {
list.add(item);
}
public List<String> getList() {
return list;
}
@Override
public Prototype clone() {
try {
ConcretePrototype copy = (ConcretePrototype) super.clone();
// 深拷贝 -测试浅拷贝时注释下面代码!!!
copy.list = new ArrayList<>(this.list);
return copy;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "ConcretePrototype{name='" + name + "', list=" + list + "} list hashcode=" + list.hashCode();
}
}
测试:
@Test
public void test() {
ConcretePrototype prototype = new ConcretePrototype("Prototype");
prototype.addToList("Item1");
prototype.addToList("Item2");
// 浅拷贝示例
ConcretePrototype shallowClone = (ConcretePrototype) prototype.clone();
// 添加新项到原型对象的列表
prototype.addToList("Item3");
System.out.println("原型: " + prototype);
System.out.println("浅拷贝: " + shallowClone);
// 深拷贝示例
ConcretePrototype deepClone = (ConcretePrototype) prototype.clone();
// 添加新项到深拷贝对象的列表
deepClone.addToList("Item4");
System.out.println("原型: " + prototype);
System.out.println("深拷贝: " + deepClone);
}
测试浅拷贝打印:
原型: ConcretePrototype{name='Prototype', list=[Item1, Item2, Item3]} list hashcode=1757018142
浅拷贝: ConcretePrototype{name='Prototype', list=[Item1, Item2, Item3]} list hashcode=1757018142
可以看出两个对象所引用的list
对象为同一个
测试深拷贝打印:
原型: ConcretePrototype{name='Prototype', list=[Item1, Item2, Item3]} list hashcode=1757018142
深拷贝: ConcretePrototype{name='Prototype', list=[Item1, Item2, Item3, Item4]} list hashcode=-1296039165
两个对象引用的list
完全不一样了,操作互不影响
8. 设计模式的比较
原型模式与其他创建型设计模式(如工厂模式、抽象工厂模式、单例模式等)有其独特之处:
- 与工厂模式的区别:工厂模式通过提供一个方法来创建对象,而原型模式通过复制现有对象来创建新的对象。工厂模式适合对象创建过程简单但需要解耦对象创建过程的场景,而原型模式适合对象创建过程复杂且需要高效创建对象的场景。
- 与单例模式的区别:单例模式确保一个类只有一个实例,而原型模式则允许通过克隆来创建多个实例。它们解决的问题不同,前者关注的是实例的唯一性,后者关注的是高效创建对象。
9. 结论
原型模式是一种高效的对象创建模式,它通过克隆现有对象来创建新对象,避免了通过构造函数创建对象的高昂代价,尽管原型模式在许多方面具有优势,但在实现过程中需要注意对象间的引用关系,确保深拷贝和浅拷贝的正确实现,以避免不必要的资源浪费和潜在的错误。