原型模式
原型模式,用起来其实就是做clone操作,clone一个对象,越过构造器,在特定使用场景下增加效率。
UML
使用场景:
- 类初始化需要消耗很多资源,比较耗时。
- new方式非常繁琐,还涉及到权限之类的。用clone就减少操作。
- 一个对象需要提供给其他对象使用,而且各个使用都可能改到值,可以考虑用原型模式,做保护性拷贝。
先说说原型模式主要的浅拷贝和深拷贝:
浅拷贝:
可被clone的对象:
- 实现 Cloneable接口
- 重写clone方法
- 先clone,再给参数赋值
public class Document implements Cloneable { private String mText; private ArrayListlist = new ArrayList<>(); public String getmText() { return mText; } public void setmText(String mText) { this.mText = mText; } @Override protected Document clone() throws CloneNotSupportedException { Document document = (Document) super.clone(); document.mText = this.mText; document.list = this.list; return document; }}复制代码
深拷贝:
对于上面ArrayList,它自身实现类cloneable接口的,所以它本身也是可以clone的,对于对象,赋值后,是指向同一个地址的,对于浅拷贝来说,如果改了list对象,那么原有的对象list也会被修改到。所以对于list对象,我们也需要拷贝一下,修改如下。
public class Document implements Cloneable { private String mText; private ArrayListlist = new ArrayList<>(); public String getmText() { return mText; } public void setmText(String mText) { this.mText = mText; } @Override protected Document clone() throws CloneNotSupportedException { Document document = (Document) super.clone(); document.mText = this.mText; document.list = (ArrayList ) this.list.clone(); return document; }}复制代码
检验
首先:对于浅拷贝
- 我们用下面一段代码来做检验:
Document document = new Document(); document.setmText("11111100000"); document.getList().add("00"); System.out.println("内容原始:" + document.toString()); Document document1 = document.clone(); document1.setmText("111111"); document1.getList().add("111"); System.out.println("内容修改:" + document1.toString()); System.out.println("内容原始:" + document.toString());复制代码
- 输出:
内容原始:Document{mText='11111100000', list=[00]}内容修改:Document{mText='111111', list=[00, 111]}内容原始:Document{mText='11111100000', list=[00, 111]}复制代码
这里很明显的能看到,我们改了String类型,也改了list对象。但是输出后,原始的String类型的text没变,但是list却被改变了。 这是为什么呢?
原因其实是: String类型是不可变类型,所以我们不推荐在for循环中使用+号来拼接一样,因为每+一次就会创建一块内存来装。同理,这里的虽然拷贝了string类型,但是第二个对象修改的时候,string的text是指向了新的地址而不是把原地址数据给改了,所以原对象的text并没被改。但list就不一样了,改的是原对象地址的数据 所以我们才会需要深拷贝。
然后我们再检验一次深拷贝的:
输出内容:
内容原始:Document{mText='11111100000', list=[00]}内容修改:Document{mText='111111', list=[00, 111]}内容原始:Document{mText='11111100000', list=[00]}复制代码
我们使用深拷贝的时候,将对象list也clone了,就没有指向的不再是原有地址了,所以改动也就影响不到原来数据咯。
另外:实现Cloneable接口只是一种方式。要实现clone也不一定非要实现这个接口(使用Object来重写clone方法的话一定要实现,不然会抛出异常)。
如果使用new的方式并不耗资源等,clone的时候我们传入自己,用new的效率或许会更高:
public Document(Document document) { this.mText = document.getmText(); this.list = document.getList(); }@Override public Document clone() throws CloneNotSupportedException { Document document = new Document(this); return document; }复制代码
总结:原型模式在copy资源非常方便,当然拷贝不一定比new快,所以需要评估测试再考虑是否不用new