原型模式即prototype pattern, 你可能在听说过它以前就已经体验过原型模式了. 一些编程语言中也在内部实现了原型模式.
原型模式也是构建型模式的一种, 如果说工厂模式侧重于create的话, 那么原型模式可能更适合用clone来描述. 虽然从实际行为表现上, 原型模式也可以轻松地大批量生成各不相同却又结构类似的实例.
在java中实际上提供了clone()方法用以覆写.
public class Foo implements Cloneable{
private String field_1;
private Double field_2;
// Constructor can be done here or leave it be default
@Override
protected Object clone(){
Foo foo = null;
try{
// DO SOMETHING...
foo = (Foo)super.clone();
}catch(Exception e){
// DO SOMETHING...
}
return foo;
}
}
所以你突然发现, 从前需要使用new来生成对象的时候, 使用clone()方法即可完成.(当然需要相关的类型转换)
实际上不同语言虽然实现clone()方法的途径有所差异, 但最终目的都是希望能够将原型写成一个更高程度上的抽象.这里的抽象不是指非要写抽象类, 而是说原型模式能够做的事情非常多. 你可以在这个译文中了解到更多原型模式的是是非非.
这里则更多地写一些体会和分析,以及一些java实践方面的东西(事实上这其实就是这个知识库的编排本意).
Prototype In Spring
还记得Spring framework吗, 我最开始使用Spring的时候总是觉得虽然xml的配置十分结构化且具体, 然而总是会有一段时间不停地在xml和java代码中周旋. 或许你会说使用注解是更方便的, 但从我的感受来看, xml的方式原始但直观.
在xml里的bean配置里, 有名为scope的属性,你可以通过它来控制IOC容器是以怎样的方式表现beans的, 默认是singleton, 也就是单例, 还可以是prototype, request, session, global session等.
这里的prototype和原型设计模式有什么关系吗? 至少从表现上, 他与单例正好相反, 每次获取到的都是新的对象, 正如你使用clone方法所做的那样. 另外就是和Spring框架相关的, 你会发现容器—也就是通常所说的某个ApplicationContext实现类在调用其close()方法时, 即使指定了销毁方法也不会在容器销毁时销毁prototype的bean(但是JVM还是可以回收). 因此clone这样的行为需要保留一个”原型”是显而易见的.
实际上在BeanFactory的getBean的实现上可以发现原型模式的使用, 尽管它的名字带了”工厂”
浅拷贝还是深拷贝
在java中, 使用clone()方法的话, 对于对象中的字段, 如果是基本类型则复制, 如果是引用类型则只改变指向, 也就是浅拷贝.
但是如果我们真的需要深拷贝, 也就是说: 真正意义上的克隆呢?
那么实现方式可以是:
- 在重写clone方法时做手脚(DO SOMETHING…)
-
clone深拷贝
public class FooDeep implements Cloneable, Serializable{
private String field_1;
private Bar bar; // An object
private Double field_2; // Yes, Double is also an object, but it's bult-in
// Constructor can be done here or leave it be default
public FooDeep(){
super();
}
@Override
protected Object clone() throws CloneNotSupportException{
Object foo = null;
foo = super.clone();
// Handle with our bar obj
FooDeep foo_deep = (foo_deep)foo;
foo_deep.bar = (Bar)bar.clone();
return foo_deep;
}
}
显然这是在clone方法内部再次将bar克隆, 只要它可以克隆, 那么依赖者FooDeep也可以克隆.
Serialization
序列化其实就是将对象拍扁成像字符串或者字节那样的sequential结构.
在Serialize的方法中, 深拷贝可以这样做(同样以上面的FooDeep为例):
public Object deepClone(){
// You defintely want to encapsulate those streams
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try{
// Serialize
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); // 'this' means current classs's instance
// Deserialize
bis = new ByteArrayInputStream(bos.toByteArray());
ois = ObjectInputStream(bis);
FooDeep foo_deep = (FooDeep)ois.readObject();
return foo_deep;
}catch(Exception e){
return null; // or a vanilla foo_deep
}finally{
// close, but also can be done with try()
bis.close();ois.close();bos.close();ois.close();
}
}