又是一个相对简单的模式,原型模式属于对象创建型模式。他主要作用就是通过一个原型对象克隆出多个一模一样的对象。
在我个人看来,设计模式作用方面可以分为两类:一类是用来优化代码结构的,比如系统解耦,让系统可扩展等等。另一类的模式对系统结构并无帮助,他不是结构型的,而是功能型的,就像单例,原型这些。
模式背景
在某些情况下,我们需要完整的复制某一个对象。比如游戏中的小兵,我的世界里面的土块,他们都是以一个模板为原型的相同的对象。如何方便高效的完成对象的复制,就是原型模式要解决的。
UML
原理
让原型类自己提供一个clone方法,使用者直接调用原型类对象的该clone方法,就可以克隆出一个与该原型对象一样的克隆对象。
注意克隆后的对象是全新的对象,是新创建的内存空间,不是引用这种。他和原来的原型对象相互独立,修改各自的属性不会相互影响。
基本要素
- 一个代表原型模式的接口,申明clone方法。
- 一个实现了接口的原型类。
- 提供clone方法。
实现
根据不同的语言的特性,会有一些不同的实现方式。这里只说Java的实现和一种通用的实现。
通用实现
通用的实现就是自己创建一个新对象,然后将原型对象的属性重新赋值给这个新对象。这种方式是所有面向对象语言都通用的。
定义一个Prototype接口,表明原型模式:
public interface Prototype {
Prototype clone();
}
原型类通用实现
public class BasePrototypeDemo implements Prototype {
private String attr;
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
//内部克隆一个新的对象
@Override
public Prototype clone() {
BasePrototypeDemo instance = new BasePrototypeDemo();
instance.setAttr(this.attr);
return instance;
}
}
优点
- 适用于所有的语言,都可以实现。
缺点
- 代码较为复杂。通过new再赋值的方式,如果属性很多势必要大量代码,效率也不会太高。
使用Java的 Cloneable
Java的Object里面有个clone方法,可以实现对象的赋值(想当于Java语言自身特性),但是使用clone的类需要实现Cloneable接口。否则会抛CloneNotSupportedException异常。
Java的实现方式如下:
public class JavaPrototypeDemo implements Cloneable {
@Override
protected JavaPrototypeDemo clone() {
try {
return (JavaPrototypeDemo) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
优点
- Java实现起来很方便
缺点
- 只支持Java
- 只是浅复制(对象内组合的对象还是原来的对象,复制只是复制了引用,只会复制基本数据类型,引用数据类型只复制地址)
使用场景
原型模式主要可以简化我们创建对象的过程。它适合于来创建对象成本较大的情况。当我们需要一个比较复杂的对象的一个副本做操作的时候,我们可以试着考虑一下原型模式。
深复制&浅复制
原型模式携带着一个问题:就是上面提到的浅复制问题。
- 浅复制:原型对象的属性也是一个对象,复制的时候如果复制的是该属性对象引用,那么这就是浅复制。
- 深复制:原型对象的属性也是一个对象,复制的时候将该属性对象也复制一份到克隆对象上,那么这就是深复制。
也就是说,深复制才是真正的完全克隆。浅复制只是复制了最外面的一层壳。
实现深复制
深复制需要将内部的引用数据类型的属性也要复制一份全新的对象。我们可以递归式的向下找出每个引用对象然后复制一份再赋值,但是那样太麻烦了,效率也太差了。我们一般可以使用序列化的方式来实现:将对象二进制流写到内存中,然后再从流中反序列化这个对象。
// 原型类
@Data
class SerialObj implements Serializable {
private String name;
private Attachment attachment;
public SerialObj deepClone() throws IOException, ClassNotFoundException {
// 将对象写入流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 从流中取出对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (SerialObj) ois.readObject();
}
}
// 内置组合对象
class Attachment implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
优点
- 可以深复制
缺点
- 代码量大些,但必要用时候只能用这个。
原型管理器
将多个原型对象存储在一个集合中给客户端使用。相当于是一个工厂,这个工厂里面已经有了一堆的模具,谁要对象和这个工厂一提,工厂不会吧自己的模具给你,而是以这个模具克隆一份给你。原型管理器就相当于原型类的工厂。
简而言之,就是创建一个原型管理器,里面存放着一堆原型对象,外界通过原型管理器去克隆原型对象,而不是直接操作原型对象。
UML
基本要素
- 原型对象的统一接口
- 原型对象
- 原型对象管理器类,这个工厂一般肯定是单例的,因为不是单例也没啥意义。
实现
//1: 一个该管理器管理原型类的公共接口
interface IObj extends Cloneable {
IObj clone();
void say();
}
//2: 原型类定义,需要自己实现clone
class Obj1 implements IObj {
@Override
public IObj clone() {
try {
return (IObj) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public void say() {
System.out.println("i am 1");
}
}
class Obj2 implements IObj {
@Override
public IObj clone() {
try {
return (IObj) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
@Override
public void say() {
System.out.println("i am 2");
}
}
//3: 原型对象管理器
class PrototypeManager {
/*使用饿汉式单例。*/
private HashMap<String, IObj> hm = new HashMap<>();
private static final PrototypeManager manager = new PrototypeManager();
private PrototypeManager() {
hm.put("1", new Obj1());
hm.put("2", new Obj2());
}
public IObj getObj(String key) {
return (hm.get(key)).clone();
}
public static PrototypeManager getManager() {
return manager;
}
}
附
如有代码和文章问题,还请指正!感谢!