设计模式: Prototype (原型)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
Prototype模式允许一个对象再创建另外一个可定制的对象,无需指定如何创建的细节。
原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实现创建。
简单来说,就是当你做一个有固定模板的东西的时候,只是其中部分内容有变化,你是想从头开始,新建然后写写写,还是Ctrl C+Ctrl V,当然除却勤快的人愿意从头开始,我个人是比较倾向于copy/pasty的。
Java中提供了clone()方法来实现对象的克隆,所以Prototype模式一下子变得很简单。
使用场景
资源优化场景。
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
性能和安全要求的场景。
通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
一个对象多个修改者的场景。
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
我们先看一下之前工厂模式的例子,来重新用原型模式做一下,创建一个抽象类Animal 和扩展了Animal 类的实体类。下一步是定义类AnimalCache,该类把Animal 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。
PrototypPatternDemo,我们的演示类使用 AnimalCache类来获取 Animal 对象。
创建一个实现了Clonable 接口的抽象类。
Animal.java
public abstract class Animal implements Cloneable {
private String name;
protected String type;
abstract void sing();
public String getType() {
return type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
创建扩展了上面抽象类的实体类。
Dog.java
public class Dog extends Animal{
public Dog() {
type = "Dog!";
}
@Override
void sing() {
System.out.println("Inside Dog::sing() method.");
System.out.println("what's dog sing: 汪汪汪");
}
}
Pig.java
public class Pig extends Animal {
public Pig() {
type = "Pig!";
}
@Override
void sing() {
System.out.println("Inside Pig::sing() method.");
System.out.println("what's pig sing: 哼哼哼");
}
}
Fox.java
public class Fox extends Animal {
public Fox() {
type = "Fox!";
}
@Override
void sing() {
System.out.println("Inside Fox::sing() method.");
System.out.println("what's fox sing: 叮叮叮");
}
}
创建一个类,从数据库获取实体类,并把它们存储在一个 Hashtable 中。
AnimalCache.java
import java.util.Hashtable;
public class AnimalCache {
private static Hashtable<String, Animal> AnimalMap = new Hashtable<String, Animal>();
public static Animal getAnimal(String AnimalName) {
Animal cachedAnimal = AnimalMap.get(AnimalName);
return (Animal) cachedAnimal.clone();
}
// 对每种动物都运行数据库查询,并创建该动物
// AnimalMap.put(AnimalKey, Animal);
// 例如,我们要添加三种动物
public static void loadCache() {
Dog Dog = new Dog();
Dog.setName("1");
AnimalMap.put(Dog.getName(), Dog);
Pig Pig = new Pig();
Pig.setName("2");
AnimalMap.put(Pig.getName(), Pig);
Fox Fox = new Fox();
Fox.setName("3");
AnimalMap.put(Fox.getName(), Fox);
}
}
PrototypePatternDemo使用 AnimalCache 类来获取存储在 Hashtable 中的形状的克隆。
PrototypePatternDemo.java
public class PrototypePatternDemo {
public static void main(String[] args) {
AnimalCache.loadCache();
Animal clonedAnimal = (Animal) AnimalCache.getAnimal("1");
System.out.println("Animal : " + clonedAnimal.getType());
clonedAnimal.sing();
Animal clonedAnimal2 = (Animal) AnimalCache.getAnimal("2");
System.out.println("Animal : " + clonedAnimal2.getType());
clonedAnimal2.sing();
Animal clonedAnimal3 = (Animal) AnimalCache.getAnimal("3");
System.out.println("Animal : " + clonedAnimal3.getType());
clonedAnimal3.sing();
}
}
验证输出:
优点
简化对象创建过程,通过拷贝的方式构建效率更高
可运行时指定动态创建的对象
Animal : Dog!
Inside Dog::sing() method.
what's dog sing: 汪汪汪
Animal : Pig!
Inside Pig::sing() method.
what's pig sing: 哼哼哼
Animal : Fox!
Inside Fox::sing() method.
what's fox sing: 叮叮叮
缺点
需要实现 Cloneable接口, clone位于内部,不易扩展,容易违背开闭原则(程序扩展,不应该修改原有代码)
默认的 clone 只是浅克隆,深度克隆需要额外编码(比如:统一实现 Cloneable接口,或者序列化方式,还有 org.apache.commons:commons-lang3.SerializationUtils.java)
注意事项
通过内存拷贝的方式构建出来的,会忽略构造函数限制
需要注意深拷贝和 浅拷贝,默认Cloneable 是浅拷贝,只拷贝当前对象而不会拷贝引用对象,除非自己实现深拷贝
与单例模式冲突, clone是直接通过内存拷贝的方式,绕过构造方法
常用克隆不可变对象,如果你克隆的对象10个字段改9个还不如实例化算了
clone只是一个语法,非强制方法命名
很少单独出现,常与工厂模式相伴
本文参考了Patterns in Java 和 http://www.runoob.com