java设计模式之原型模式
Posted J_Newbie
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java设计模式之原型模式相关的知识,希望对你有一定的参考价值。
定义:
原型模式指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象,属于创建型设计模式
原型模式的核心在于复制原型对象。以系统中已存在的一个对象为原型,直接基于内存二进制流进行复制,不需要在经历耗时的对象初始化过程(不调用构造函数),性能提升许多。当对象的构建过程比较耗时,可以把当前系统中已存在的对象作为原型,对其进行复制(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大缩短。
应用场景:
-
创建对象成本较大(例如:初始化时间长,占用CPU太多,或者占用网络资源太多等),需要优化资源
-
创建一个对象需要繁琐的数据准备或者访问权限等,需要提高性能或者提高安全性
-
系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值
在Spring中,原型模式应用非常广泛,例如scope=prototype,JSON.parseObject(),都是原型模式的具体应用
UML类图:
由上图可以看到,原型模式主要包含3个角色
-
客户(Client):客户类提出创建对象请求
-
抽象原型(IPrototype):规定复制接口
-
具体原型(ConcretePrototype):被复制的对象
注:不是通过new关键字而是通过对象复制来实现创建对象的模式被称为原型模式
通用写法
//客户端
package com.design.pattern.Prototype;
public class Client
public static void main(String[] args)
//创建原型对象
ConcretePrototypeA prototypeA = new ConcretePrototypeA("originalA");
System.out.println(prototypeA);
//复制原型对象
ConcretePrototypeA cloneTypeA = prototypeA.clone();
System.out.println(cloneTypeA);
cloneTypeA.desc="clone";
System.out.println(cloneTypeA);
//抽象原型
package com.design.pattern.Prototype;
public interface IPrototype<T>
T clone();
//具体原型
package com.design.pattern.Prototype;
public class ConcretePrototypeA implements IPrototype<ConcretePrototypeA>
public String desc;
public ConcretePrototypeA(String desc)
this.desc = desc;
@Override
public ConcretePrototypeA clone()
return new ConcretePrototypeA(this.desc);
@Override
public String toString()
return "ConcretePrototypeA" +
"desc='" + desc + '\\'' +
'';
//具体原型
package com.design.pattern.Prototype;
public class ConcretePrototypeB implements IPrototype<ConcretePrototypeB>
private String desc;
public ConcretePrototypeB(String desc)
this.desc = desc;
@Override
public ConcretePrototypeB clone()
return new ConcretePrototypeB(this.desc);
@Override
public String toString()
return "ConcretePrototypeB" +
"desc='" + desc + '\\'' +
'';
使用原型模式解决实际问题
分析jdk浅克隆API带来的问题:
在Java提供的api中,不需要手动创建抽象原型接口,因为java已经内置了Cloneable抽象原型接口,自定义的类型只需要实现该接口并重写Object.clone()方法即可完成本类的复制。
通过jdk源码可以知道,其实Cloneable是一个空接口。Java之所以提供Cloneable接口,只是为了在运行时通知java虚拟机可以安全的在该类上使用clone()方法。而如果该类没有实现Cloneable接口,则调用clone()方法会抛出CloneNotSupportedException异常
一般情况下,如果使用clone()方法,则需满足以下条件
-
对任何对象o,都有o.clone() != o,换言之,克隆对象与原型对象不是同一个对象
-
对任何对象o,都有o.clone(),getClass() = o.getClass(),换言之,克隆对象与原型对象的类型一样
-
如果对象o的equals()方法定义恰当,则o.clone().equals(o)应当成立
我们在设计自定义类的clone()方法时,应当遵守这3个条件,一般来说,这3个条件的前2个是必须的第3个是可选的。
下面使用java提供的api应用来实现原型模式,代码如下
package com.design.pattern.Prototype;
public class ClientJDK
public static void main(String[] args)
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype("original");
System.out.println(prototype);
//复制原型对象
ConcretePrototype clone = prototype.clone();
System.out.println(clone);
System.out.println(prototype == clone);
System.out.println(prototype.getClass() == clone.getClass());
clone.desc = "clone";
System.out.println(clone);
package com.design.pattern.Prototype;
public class ConcretePrototype implements Cloneable
public String desc;
public ConcretePrototype(String desc)
this.desc = desc;
@Override
public ConcretePrototype clone()
ConcretePrototype cloneType = null;
try
cloneType = (ConcretePrototype) super.clone();
catch (Exception e)
e.printStackTrace();
return cloneType;
@Override
public String toString()
return "ConcretePrototype" +
"desc='" + desc + '\\'' +
'';
Super.clone()方法直接从对内存中以二进制流的方式进行复制,重新分配一个内存块,因此效率很高,由于super.clone()方法基于内存复制,因此不会调用对象的构造函数,也就是不需要经历初始化的过程
在日常开发中,使用super.clone()方法并不能满足所有需求。如果类中存在引用对象属性,则原型对象与克隆对象的该属性会指向同一对象的引用
package com.design.pattern.Prototype;
import java.util.ArrayList;
import java.util.List;
public class ClientJDK
public static void main(String[] args)
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("张三");
List<String> hobbies = new ArrayList<>();
hobbies.add("书法");
hobbies.add("美术");
prototype.setHobbies(hobbies);
System.out.println(prototype);
ConcretePrototype clone = prototype.clone();
clone.getHobbies().add("java");
System.out.println("原型对象: " + prototype);
System.out.println("克隆对象:" + clone);
package com.design.pattern.Prototype;
import lombok.Data;
import java.util.List;
@Data
public class ConcretePrototype implements Cloneable
public int age;
public String name;
public List<String> hobbies;
@Override
public ConcretePrototype clone()
ConcretePrototype cloneType = null;
try
cloneType = (ConcretePrototype) super.clone();
catch (Exception e)
e.printStackTrace();
return cloneType;
@Override
public String toString()
return "ConcretePrototype" +
"age=" + age +
", name='" + name + '\\'' +
", hobbies=" + hobbies +
'';
运行结果:
ConcretePrototypeage=18, name='张三', hobbies=[书法, 美术]
原型对象: ConcretePrototypeage=18, name='张三', hobbies=[书法, 美术, java]
克隆对象:ConcretePrototypeage=18, name='张三', hobbies=[书法, 美术, java]
我们给克隆对象新增了一个属性hobbies(爱好)之后,发现原型对象也发生了变化,这显然不符合预期。因为我们希望克隆对象和原型对象是两个独立的对象。从测试结果来看,应该是hobbies共用一个内存地址,意味着复制的不是值,而是引用的地址。这样的话,如果我们修改任意一个对象中的属性值,原型对象与克隆对象的值都会改变。这就是我们常说的浅克隆,只是完整的复制了值的类型数据,没有赋值引用对象。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。
java自带的clone()方法进行的就是浅克隆。而如果我们想进行深克隆,可以直接在super.clone()后,手动给克隆对象的相关属性分配另一块内存,不过如果当原型对象维护很多引用属性的时候,手动分配比较麻烦。因此,java中,如果想完成原型对象的深克隆,则通常使用序列化的方式
使用序列化实现深克隆:
对上面的代码,我们增加一个deepClone()方法
package com.design.pattern.Prototype;
import lombok.Data;
import java.io.*;
import java.util.List;
@Data
public class ConcretePrototype implements Cloneable, Serializable
public int age;
public String name;
public List<String> hobbies;
@Override
public ConcretePrototype clone()
ConcretePrototype cloneType = null;
try
cloneType = (ConcretePrototype) super.clone();
catch (Exception e)
e.printStackTrace();
return cloneType;
public ConcretePrototype deepClone()
try
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ConcretePrototype) ois.readObject();
catch (Exception e)
e.printStackTrace();
return null;
@Override
public String toString()
return "ConcretePrototype" +
"age=" + age +
", name='" + name + '\\'' +
", hobbies=" + hobbies +
'';
package com.design.pattern.Prototype;
import java.util.ArrayList;
import java.util.List;
public class ClientJDK
public static void main(String[] args)
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("张三");
List<String> hobbies = new ArrayList<>();
hobbies.add("书法");
hobbies.add("美术");
prototype.setHobbies(hobbies);
System.out.println(prototype);
ConcretePrototype clone = prototype.deepClone();
clone.getHobbies().add("java");
System.out.println("原型对象: " + prototype);
System.out.println("克隆对象:" + clone);
System.out.println(prototype == clone);
System.out.println("原型对象的爱好:" + prototype.getHobbies());
System.out.println("克隆对象的爱好:" + clone.getHobbies());
System.out.println(prototype.getHobbies() == clone.getHobbies());
运行结果:
ConcretePrototypeage=18, name='张三', hobbies=[书法, 美术]
原型对象: ConcretePrototypeage=18, name='张三', hobbies=[书法, 美术]
克隆对象:ConcretePrototypeage=18, name='张三', hobbies=[书法, 美术, java]
false
原型对象的爱好:[书法, 美术]
克隆对象的爱好:[书法, 美术, java]
false
还原克隆破坏单例的事故现场
假设如果复制的目标对象恰好是单例对象,我们试一下,
package com.design.pattern.Prototype;
public class SingletonPrototype implements Cloneable
private static SingletonPrototype instance = new SingletonPrototype();
private SingletonPrototype();
public static SingletonPrototype getInstance()
return instance;
@Override
public SingletonPrototype clone()
try
return (SingletonPrototype) super.clone();
catch (Exception e)
e.printStackTrace();
return null;
package com.design.pattern.Prototype;
public class ClientTest
public static void main(String[] args)
SingletonPrototype instance = SingletonPrototype.getInstance();
SingletonPrototype clone = instance.clone();
System.out.println(instance == clone);
运行结果:
false
从运行结果来看,确实创建了两个不同的对象,实际上防止复制破坏单例对象的解决思路非常简单,禁止复制便可。要么我们单例类不实现Cloneable接口,要么我们重写clone()方法,在clone()方法中返回单例对象即可,具体代码如下
@Override
public SingletonPrototype clone()
return instnce;
优点:
-
java自带的原型模式基于内存二进制流的复制,在性能上比直接new一个对象更加优良
-
可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其保存起来,简化了创建对象的过程,以便在需要的时候使用,可辅助实现撤销操作
缺点:
-
需要为每一个类都配置一个clone方法
-
clone方法位于类的内部,当对已有类进行修改的时候,需要修改代码,违背了开闭原则
-
当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦,因此,深克隆,浅克隆需要应用得当
以上是关于java设计模式之原型模式的主要内容,如果未能解决你的问题,请参考以下文章