原型模式概述
使用原型实例指定创建对象的种类,并且通过这些原型创建新的对象,同样是一种创建型模式。
分析结构图
(1)Prototyoe(抽象原型类)声明克隆方法的接口,是所有具体原型类的公共父类,可以是接口,也可以是抽象类,甚至是可以是具体实现类。
(2) ConcretePrototyoe(具体原型类)实现在抽象原型类的克隆方法,在克隆方法中返回自己的一个克隆对象。
(3)Client(客户类)让一个原型克隆自身而创建一个对象,客户类中只需要直接实例化或者通过工厂方法创建一个原型对象,再通过调用该原型对象的克隆方法可得到多个相同的对象。客户类面向抽象原型类编程,可以选择具体原型类,系统将更有可扩展性。Java中通常有两种方法实现
1通过实现方法
通过实现克隆方法,在具体原型中实例化一个与自身对象相同的对象并返回,并将新的参数传入所创建的新对象中,保证成员变量相同。代码如下
package com.learn.designmode.mode.factory.clone;
import lombok.Data;
@Data
public class ConCretePrototype extends Prototype {
private String attr;
public Prototype clone(){
Prototype prototype = new ConCretePrototype();
((ConCretePrototype) prototype).setAttr(this.attr);
return prototype;
}
}
package com.learn.designmode.mode.factory.clone;
public class Prototype {
public Prototype clone(){
return null;
}
}
public static void main(String[] args) {
Prototype prototype = new ConCretePrototype();
((ConCretePrototype) prototype).setAttr("aaaa");
ConCretePrototype prototype1 = (ConCretePrototype) prototype.clone();
System.out.println(prototype1);
System.out.println(prototype1);
}
不能,因为复制出来的对象与克隆对象拥有同一个地址。并不满足原型模式设计的初衷。
2Java 语言提供的clone方法
Java 所有类都继承Object,Object提供了一个clone 方法,可以将一个Java对象克隆一份。但是需要注意的是,实现克隆的类必须实现Cloneable接口,表示这个类支持被复制,如果一个类没有实现这个接口,但是调用了clone方法,Java 编译器将抛出一个CloneNotSupportedException异常。
package com.learn.designmode.mode.clone;
import lombok.Data;
@Data
public class ConJavaCreatePrototype implements Cloneable{
private String attr;
public ConJavaCreatePrototype clone() throws CloneNotSupportedException {
return (ConJavaCreatePrototype) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
ConJavaCreatePrototype prototype = new ConJavaCreatePrototype();
prototype.setAttr("a");
ConJavaCreatePrototype prototype1 = prototype.clone();
System.out.println(prototype1);
}
}
完美的解决方案
package com.learn.designmode.mode.clone;
import lombok.Data;
@Data
public class WeekLog implements Cloneable {
private String content;
private String name;
private Integer count;
public WeekLog clone(){
try {
WeekLog copy = (WeekLog) super.clone();
return copy;
}catch (Exception ex){
System.out.println("不支持复制");
return null;
}
}
public static void main(String[] args) {
WeekLog weekLog = new WeekLog();
weekLog.setContent("我工作了....");
weekLog.setName("zlk");
weekLog.setCount(1);
System.out.println(weekLog);
WeekLog weekLog1 = weekLog.clone();
weekLog1.setCount(2);
System.out.println(weekLog1);
String name = "a";
String name1 = "a";
// 对象所指向的内存地址不相同
System.out.println(weekLog == weekLog1);
// 字符串相同,会指向内存的同一块地址
System.out.println(weekLog.getName() == weekLog1.getName());
}
}
带附件的周报
浅克隆
在浅克隆中,如果原型对象的成员变量是基本数据类型,将复制一份给克隆对象,如果对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象成员变量指向相同的内存地址。简单来说,当对象被复制时,只复制它本身和其中包含的基本数据类型的成员变量,而引用类型的成员对象并没有没复制。
Java语言中,通过Object类的clone方法可以实现浅克隆,为了更好的理解浅克隆和深克隆,我们先用浅克隆实现工作周报和附件类的复制。
public static void main(String[] args) {
Attachment attachment = new Attachment();
attachment.setName("我是周报");
WeekLog weekLog = new WeekLog();
weekLog.setContent("我工作了....");
weekLog.setName("zlk");
weekLog.setCount(1);
weekLog.setAttachment(attachment);
System.out.println(weekLog);
WeekLog weekLog1 = weekLog.clone();
weekLog1.setCount(2);
System.out.println(weekLog1);
// 演示浅克隆和深克隆
// 对象所指向的内存地址不相同
System.out.println(weekLog == weekLog1);
// 字符串相同,会指向内存的同一块地址
System.out.println(weekLog.getAttachment() == weekLog1.getAttachment());
}
此处使用的是浅克隆,所以工作周报复制成功,但是附件对象判断== 输出true说明,两个对象共用附件对象的地址是一样的。
深克隆
深克隆主要为了解决浅克隆中引用类型复制的时候地址还是一样的问题,也就是所有被复制的成员变量都会真正的贝一份到复制的对象中,他们的地址不同。
java中实现深克隆,可以通过序列化的方式实现,序列化就是将对象写到流的过程,写到流的对象是原有对象的一个复制品,而原对象仍然存与内存中,通过序列化实现的复制不止可以复制对象的本身,而且可以复制其引用的成员变量,因此通过序列化将对象写到流的中,再从流里读出来可以实现深克隆。
但是需要注意的是序列化复制需要实现Serializable接口,否则无法实现序列化的操作。其结构图如下
package com.learn.designmode.mode.clone;
import lombok.Data;
import java.io.Serializable;
@Data
public class Attachment implements Serializable {
private String name;
private void download(){
System.out.println("下载附件,文件名为" + this.name);
}
}
package com.learn.designmode.mode.clone;
import lombok.Data;
import java.io.*;
@Data
public class DeepClone implements Serializable {
private Attachment attachment;
private String name;
public DeepClone deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (DeepClone)objectInputStream.readObject();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepClone deepClone = new DeepClone();
deepClone.setName("zlx");
deepClone.setAttachment(new Attachment(){
{
setName("aaaaaa");
}
});
DeepClone deepClone1 = deepClone.deepClone();
System.out.println(deepClone == deepClone1);
System.out.println(deepClone1.getAttachment() == deepClone.getAttachment());
}
}
输出结果可以看出,由于使用了深克隆,在附件对象判断==的时候输出false,深克隆技术实现了原型对象和克隆对象的完全独立,对克隆对象的修改都不会给其他对象产生影响。
原型管理器的引入和实现
原型管理器是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的克隆,可以到复制集合中对应的原型对象来获取。原型管理器针对抽象原型类编程。
package com.learn.designmode.mode.clone.offical;
public interface OfficialDocument extends Cloneable {
public OfficialDocument clone();
public void show();
}
class FAR implements OfficialDocument{
@Override
public OfficialDocument clone() {
OfficialDocument officialDocument;
try {
officialDocument = (OfficialDocument) super.clone();
return officialDocument;
}catch (Exception ex){
System.out.println("不支持复制");
return null;
}
}
@Override
public void show() {
System.out.println("《可行性分析报告》");
}
}
class SAR implements OfficialDocument{
@Override
public OfficialDocument clone() {
OfficialDocument officialDocument;
try {
officialDocument = (OfficialDocument) super.clone();
return officialDocument;
}catch (Exception ex){
System.out.println("不支持复制");
return null;
}
}
@Override
public void show() {
System.out.println("《软件规格需求说明书》");
}
}
package com.learn.designmode.mode.clone.offical;
import java.util.Hashtable;
/** 原型管理器
* 使用单例模式
* @author zlx
*/
public class PrototypeManage {
private Hashtable<String,Object> hashtable = new Hashtable<String,Object>(10);
private static class HolderClass{
private static PrototypeManage prototypeManage = new PrototypeManage();
}
private PrototypeManage(){
hashtable.put("FAR",new FAR());
hashtable.put("SAR",new SAR());
}
public void putNewOfficial(String key,OfficialDocument document){
hashtable.put(key,document);
}
// 通过浅克隆获取新的公文对象
public OfficialDocument getOfficialDocument(String key){
return ((OfficialDocument) hashtable.get(key)).clone();
}
public static PrototypeManage getInstance(){
return HolderClass.prototypeManage;
}
public static void main(String[] args) {
PrototypeManage prototypeManage = PrototypeManage.getInstance();
OfficialDocument document1, document2, document3, document4;
document1 = prototypeManage.getOfficialDocument("FAR");
document1.show();
document2 = prototypeManage.getOfficialDocument("FAR");
document2.show();
System.out.println(document1 == document2);
document3 = prototypeManage.getOfficialDocument("SAR");
document4 = prototypeManage.getOfficialDocument("SAR");
document3.show();
document4.show();
System.out.println(document3 == document4);
}
}
我们只需要增加一个PPR的类实现OfficialDocument的接口
原型模式的总结
优点
(1)当创建新的对象实例比较复杂时,使用原型模式可以简化创建过程,通过复制一个已有的实例可以提高新实例的创建效率。
(2)扩展性好,由于原型模式提供了抽象原型类,客户端针对原型类编程,而只需要将具体类型写在配置文件中,增加或者减少原型类都对系统没有任何影响。
(3)原型模式提供了简化的创建结构,工厂模式常常需要有一个与产品等级类相同的工厂等级结构,而原型模式就不需要这样,原型模式的产品复制是通过实现父类原型类的克隆方法,无须有专门的工厂类创建产品。
(4)可以使用深克隆的方式来保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,例如恢复到某一历史状态,可复制实现撤销的操作。
缺点
(1)需要为每一个类配置克隆方法,而该克隆方法位于一个类的内部, 当对已有的类进行改造时,需要修改源代码,违背了开闭原则。
(2)在实现深克隆需要编写较为复杂的代码,而且当对象之间存在多重嵌套的引用,为了实现深克隆,每一层对应的类型都必须支持深克隆。
使用场景
(1)创建新对象成本比较大,新的对象可以用原有的对象进行复制,如果是相似对象,则可以对其成员变量稍作修改。
(2)如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用的内存较少时,可以使用原型模式配合适备忘录模式来实现。
(3)需要避免使用分层次的工厂类创建分层次的对象,并且类的实例对象只有一个或者很少的几个组合状态,通过复制原型对象得到新实例可能比构造方法创建一个新实例更加方便。