原型模式
1. 介绍
原型模式(Prototype Pattern)属于对象创建模式,通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。
当直接创建对象的代价比较大时,可以使用原型模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。主要解决的是在运行期建立和删除原型。
2. 实现方式
原型模式一般的实现方式有两种:简单形式和登记形式。
(1)简单形式:简单形式的原型模式涉及三个角色,客户(Client)角色、抽象原型(Prototype)角色、具体原型(Concrete Prototype)角色
(2)登记形式:相对于简单形式的原型模式,登记形式的原型模式多了一个原型管理器(PrototypeManager)角色,该角色的作用是:创建具体原型类的对象,并记录每一个被创建的对象。
抽象原型角色:
public interface Prototype{
public Prototype clone();
public String getName();
public void setName(String name);
}
具体原型角色
public class ConcretePrototype1 implements Prototype {
private String name;
public Prototype clone(){
ConcretePrototype1 prototype = new ConcretePrototype1();
prototype.setName(this.name);
return prototype;
}
public String toString(){
return "Now in Prototype1 , name = " + this.name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
原型管理角色
public class PrototypeManager {
/**
* 用来记录原型的编号和原型实例的对应关系
*/
private static Map<String,Prototype> map = new HashMap<String,Prototype>();
/**
* 私有化构造方法,避免外部创建实例
*/
private PrototypeManager(){}
/**
* 向原型管理器里面添加或是修改某个原型注册
* @param prototypeId 原型编号
* @param prototype 原型实例
*/
public synchronized static void setPrototype(String prototypeId , Prototype prototype){
map.put(prototypeId, prototype);
}
/**
* 从原型管理器里面删除某个原型注册
* @param prototypeId 原型编号
*/
public synchronized static void removePrototype(String prototypeId){
map.remove(prototypeId);
}
/**
* 获取某个原型编号对应的原型实例
* @param prototypeId 原型编号
* @return 原型编号对应的原型实例
* @throws Exception 如果原型编号对应的实例不存在,则抛出异常
*/
public synchronized static Prototype getPrototype(String prototypeId) throws Exception{
Prototype prototype = map.get(prototypeId);
if(prototype == null){
throw new Exception("您希望获取的原型还没有注册或已被销毁");
}
return prototype;
}
}
客户端角色
public class Client {
public static void main(String[]args){
try{
Prototype p1 = new ConcretePrototype1();
PrototypeManager.setPrototype(“p1”, p1);
//获取原型来创建对象
Prototype p3 = PrototypeManager.getPrototype(“p1”).clone();
p3.setName(“张三”);
System.out.println(“第一个实例:” + p3);
//有人动态的切换了实现
Prototype p2 = new ConcretePrototype2();
PrototypeManager.setPrototype(“p1”, p2);
//重新获取原型来创建对象
Prototype p4 = PrototypeManager.getPrototype(“p1”).clone();
p4.setName(“李四”);
System.out.println(“第二个实例:” + p4);
//有人注销了这个原型
PrototypeManager.removePrototype(“p1”);
//再次获取原型来创建对象
Prototype p5 = PrototypeManager.getPrototype(“p1”).clone();
p5.setName(“王五”);
System.out.println(“第三个实例:” + p5);
}catch(Exception e){
e.printStackTrace();
}
}
}
3. 优缺点
(1)优点:
使用原型模式创建对象比直接new一个对象更有效
隐藏制造新实例的复杂性
重复地创建相似对象时可以考虑使用原型模式
(2)缺点:
每一个类必须配备一个克隆方法
深层复制比较复杂
4. 应用场景
(1)资源优化场景,复制对象的结构与数据
(2)创建对象成本较大的情况下。类初始化需要消耗很大资源,包括数据和硬件资源等。或者也可以说产生一个对象需要非常繁琐的数据准备或访问权限
(3)希望对目标对象的修改不影响既有的原型对象
Jdk中Object clone方法
5. 注意事项
(1)浅拷贝是实现Cloneable,重写clone方法,深拷贝是通过实现Serializale读取二进制流实现。
(2)Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。
(3)使用原型模式复制对象不会调用类的构造方法。所以,单例模式与原型模式是冲突的,在使用时要特别注意。