1、前言
上几篇文章大致讲了工厂模式的几种类型,今天聊聊原型模式!今天看了LINUX的发展历史,喜欢他的态度不是为了啥!“just for fun!”觉得好玩,没有什么能比这更重要了!我希望自己打工只是满足自己的生存,同时可以不断向前只是为了做出自己觉得有意思好玩的东西!“just for fun!”。谈到正题,原型模式也是一种创建型的设计模式。通过复制自己进行创建。分为深度克隆,和浅克隆
2、原型模式得概念
原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象!
用什么例子来解释原型模式了?
多重影分身非常合适这个概念,鸣人本体是原型对象!通过“多重影分身术”也就是原型模式(自己本身使用忍术进行创建),进行分身!创建新对象,(分身)。
需要注意的是,创建新“分身”的人就是鸣人!这个意思就是说原型对象自己不仅是个对象还是个工厂!并且通过克隆方式创建的对象是全新的对象,它们都是有自己的新的地址,通常对克隆模式所产生的新对象(影分身)进行修改(攻击)是不会对原型对象(鸣人)造成任何影响的!,每一个克隆对象都是相对独立的,通过不同的方式对克隆对象进行修改后,可以的到一系列相似但不完全相同的对象。(参考多重影分身之色诱术)。
3、原型的UML图
原型模式分三个角色,抽象原型类,具体原型类,客户类。
抽象原型类(prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口,抽象类甚至是一个具体的实现类。
具体原型类(concretePrototype):它实现了抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象。
客户类(Client):在客户类中,使用原型对象只需要通过工厂方式创建或者直接NEW(实例化一个)原型对象,然后通过原型对象的克隆方法就能获得多个相同的对象。由于客户端是针对抽象原型对象编程的所以还可以可以很方便的换成不同类型的原型对象!
4、浅克隆于深克隆
在原型模式中有两个概念我们需要了解一下,就是浅克隆和深克隆的概念。按照我的理解,浅克隆只是复制了基础属性,列如八大基本类型,然而引用类型实际上没有复制,只是将对应的引用给复制了!复制地址。
4.1、浅克隆
在浅克隆中,如果原型对象的成员变量是值类型(八大基本类型,byte,short,int,long,char,double,float,boolean).那么就直接复制,如果是复杂的类型,(枚举,String,对象)就只复制对应的内存地址。
在换个说法,就是复杂类型的成员变量(String,枚举,啥的)用的是一个。修改了克隆对象的原型对象也会变。他们是共用的。而值类型不是共用的!
4.2、深克隆
深克隆,我就不用多说了吧,就是什么都是单独的!全部复制,然后各自独立。你修改克隆对象对于原型对象没有丝毫影响,完全的影分身!
5、原型模式的实现
讲了这么多概念,是时候动动手来实现了!
创建一个相似或相同的类!
附件类:
package prototypePattern; public class Attachment { private String name; //附件名 public String getName() { return name; } public void setName(String name) { this.name = name; } public void download() { System.out.println("下载附件"+name); } }
周报类:这里面很多属性其实可以忽略,但是再真正的操作时他们是确实存在的!
关键点在于,实现cloneable接口以及用object的clone方法。
package prototypePattern; public class WeeklyLog implements Cloneable{ private Attachment attachment; private String date; private String name; private String content; public Attachment getAttachment() { return attachment; } public void setAttachment(Attachment attachment) { this.attachment = attachment; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } //实现clone()方法实现浅克隆 public WeeklyLog clone() { //需要实现cloneable的接口,直接继承object就好,它里面自带一个clone方法! Object obj = null; try { obj = super.clone(); return (WeeklyLog)obj; } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block System.out.println("不支持克隆方法!"); return null; } } }
package prototypePattern; public class Client { //测试类,客户端 public static void main(String[] args) { WeeklyLog log_1,log_2; log_1 = new WeeklyLog(); //创建原型对象 Attachment attachment = new Attachment(); //创建附件对象 log_1.setAttachment(attachment); //将附件添加到周报种去 log_2=log_1.clone(); //克隆周报 System.out.println("周报是否相同"+(log_1==log_2)); System.out.println("附件是否相同"+(log_1.getAttachment()==log_2.getAttachment())); } }
结果:
以上是JAVA浅克隆的实现
可以看出,周报类型不是相同的类型,但是附件还是同一个类。
深克隆
在JAVA怎么做到深度克隆了?通过序列化(Serialization)等方式来进行深度克隆。这个时候要聊一聊什么是序列化了。简单的讲就是序列化就将对象写到流的一个过程,写到流里面去(就是字节流)就等于复制了对象,但是原来的对象并没有动,只是复制将类型通过流的方式进行读取,然后写到另个内存地址中去!
JAVA序列化介绍!
- 步骤为,首先将引用成员变量序列化。
- 将成员变量通过流的方式CLONE
- 成功.
首先将附件类修改。将其序列化
package prototypePattern; import java.io.Serializable; public class Attachment_2 implements Serializable { private String name; //附件名 public String getName() { return name; } public void setName(String name) { this.name = name; } public void download() { System.out.println("下载附件"+name); } }
周报类
package prototypePattern; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; /** * * <p>Title: WeeklyLog</p> * <p>Description:周报类充当具体的原型类 </p> * @author HAND_WEILI * @date 2018年9月2日 */ public class WeeklyLog_2 implements Serializable{ private Attachment_2 attachment; private String date; private String name; private String content; public Attachment_2 getAttachment() { return attachment; } public void setAttachment(Attachment_2 attachment) { this.attachment = attachment; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } //通过序列化进行深克隆 public WeeklyLog_2 deepclone() throws Exception { //将对象写入流中, ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); oos.writeObject(this); //将对象取出来 ByteArrayInputStream bi = new ByteArrayInputStream(bao.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bi); return (WeeklyLog_2)ois.readObject(); } }
客户类
package prototypePattern; public class Client_2 { //测试类,客户端 public static void main(String[] args) { WeeklyLog_2 log_1,log_2=null; log_1 = new WeeklyLog_2(); //创建原型对象 Attachment_2 attachment = new Attachment_2(); //创建附件对象 log_1.setAttachment(attachment); //将附件添加到周报种去 try { log_2=log_1.deepclone(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //克隆周报 System.out.println("周报对象是否相同"+(log_1==log_2)); System.out.println("附件对象是否相同"+(log_1.getAttachment()==log_2.getAttachment())); } }
6、原型模式的优缺点
原型模式作为一种快速创建大量相同或相似的对象方式,在软件开发种的应用较为广泛,很多软件提供的CTRL+C和CTRL+V操作的就是原型模式的典型应用!
优点
当创建的对象实例较为复杂的时候,使用原型模式可以简化对象的创建过程!
扩展性好,由于写原型模式的时候使用了抽象原型类,在客户端进行编程的时候可以将具体的原型类通过配置进行读取。
可以使用深度克隆来保存对象的状态,使用原型模式进行复制。当你需要恢复到某一时刻就直接跳到。比如我们的idea种就有历史版本,或则SVN中也有这样的操作。非常好用!
缺点
需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的里面,当对已有的类经行改造时需要修改源代码,违背了开闭原则。
在实现深克隆的时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用的时候,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现相对麻烦。
7、原型模式适用场景
在以下情况可以考虑使用。
1创建对象成本比较大,比如初始化要很长时间的,占用太多CPU的,新对象可以通过复制已有的对象获得的,如果是相似的对象,则可以对其成员变量稍作修改。
2系统要保存对象状态的,而对象的状态改变很小。
3需要避免使用分层次的工厂类来创建分层次的对象,并且类的对象就只用一个或很少的组合状态!
8、总结
创建型的设计模式,除开建造者模式基本学习完毕。不过是基础的学习。还没有正式的运用!在写代码的时候需要取考虑使用这种设计模式与否。学而不用存粹浪费时间。其次,创建型的设计模式是基础,需要好好理解这些模式才能够理解其他的结构型以及行为型的设计模式。
对于设计模式这一遍是泛读,以后需要通过实战来一一熟悉使用。路漫漫其修远兮吾将上下而求索!