简单理解原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在
使用场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗
- 通过new产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝
原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
spring中bean的创建实际就两种模式:单例模式和原型模式。(当然,原型模式需要和工厂模式搭配起来)
结构
Client: 客户端角色
Prototype: 抽象原型角色,抽象类或者接口,用来声明clone方法
ConcretePrototype: 具体的原型类,是客户端角色使用的对象,即被复制的对象
原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:
- 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
- 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。
需要注意的是,Prototype通常是不用自己定义的,因为拷贝这个操作十分常用,Java中就提供了Cloneable接口来支持拷贝操作,它就是原型模式中的Prototype。当然,原型模式也未必非得去实现Cloneable接口,也有其他的实现方式。
实现代码
拿孙悟空来举例
浅克隆
Work
public class Work {
private String address;
private String doWhat;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDoWhat() {
return doWhat;
}
public void setDoWhat(String doWhat) {
this.doWhat = doWhat;
}
}
WuKong(ConcretePrototype)
public class WuKong implements Cloneable{
private String name;
private Work work = new Work();
public WuKong() {
System.out.println("执行了构造函数");
}
public void setName(String name) {
this.name = name;
}
public void setWork(String address, String doWhat) {
this.work.setAddress(address);
this.work.setDoWhat(doWhat);
}
@Override
public WuKong clone(){
WuKong wuKong = null;
try {
wuKong = (WuKong) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return wuKong;
}
public void say(){
System.out.println("我是" + name + ",我的工作是" + work.getAddress() + work.getDoWhat());
}
}
TestClient(Client)
public class TestClient {
public static void main(String[] args) {
test1();
System.out.println("======================================");
test2();
}
public static void test1() {
WuKong wukong = new WuKong();
wukong.setName("真·悟空");
wukong.setWork("在原地", "看护唐僧");
wukong.say();
WuKong clone_wukong1 = wukong.clone();
clone_wukong1.setName("克隆悟空1号");
clone_wukong1.setWork("去河边", "打水");
clone_wukong1.say();
WuKong clone_wukong2 = wukong.clone();
clone_wukong2.setName("克隆悟空2号");
clone_wukong2.setWork("去村里", "化斋");
clone_wukong2.say();
}
public static void test2() {
WuKong wukong = new WuKong();
wukong.setName("真·悟空");
wukong.setWork("在原地", "看护唐僧");
WuKong clone_wukong1 = wukong.clone();
clone_wukong1.setName("克隆悟空1号");
clone_wukong1.setWork("去河边", "打水");
WuKong clone_wukong2 = wukong.clone();
clone_wukong2.setName("克隆悟空2号");
clone_wukong2.setWork("去村里", "化斋");
wukong.say();
clone_wukong1.say();
clone_wukong2.say();
}
}
输出
执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋
======================================
执行了构造函数
我是真·悟空,我的工作是去村里化斋
我是克隆悟空1号,我的工作是去村里化斋
我是克隆悟空2号,我的工作是去村里化斋
为什么test2
的克隆悟空做的工作都和真·悟空的工作一样呢?这是因为Object
类提供的clone
方法不会拷贝内部数组和引用对象,导致它们仍旧指向原来对象的内部元素地址,这种拷贝叫做浅拷贝。
在WuKong
类中,Work
字段是引用类型,WuKong
被拷贝后,Work
字段仍旧指向原来的WuKong
对象的Work
字段地址,这样每次设置Work
的值,都会覆盖上一次设置的值
这就是浅克隆存在的问题
- 被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象
深克隆
让我们改造一下上面的代码
Work
Work类实现Cloneable接口,重写clone()方法
public class Work implements Cloneable{
private String address;
private String doWhat;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDoWhat() {
return doWhat;
}
public void setDoWhat(String doWhat) {
this.doWhat = doWhat;
}
@Override
public Work clone(){
Work work = null;
try {
work = (Work) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return work;
}
}
WuKong
public class WuKong implements Cloneable{
private String name;
private Work work = new Work();
public WuKong() {
System.out.println("执行了构造函数");
}
public void setName(String name) {
this.name = name;
}
public void setWork(String address, String doWhat) {
this.work.setAddress(address);
this.work.setDoWhat(doWhat);
}
@Override
public WuKong clone(){
WuKong wuKong = null;
try {
wuKong = (WuKong) super.clone();
wuKong.work = this.work.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return wuKong;
}
public void say(){
System.out.println("我是" + name + ",我的工作是" + work.getAddress() + work.getDoWhat());
}
}
将Work属性也进行克隆
输出
执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋
======================================
执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋
利用序列化和反序列化实现深克隆
Work
public class Work implements Serializable {
private static final long serialVersionUID = 7344227889442923109L;
private String address;
private String doWhat;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDoWhat() {
return doWhat;
}
public void setDoWhat(String doWhat) {
this.doWhat = doWhat;
}
}
WuKong
public class WuKong implements Serializable {
private static final long serialVersionUID = 7345427889442923974L;
private String name;
private Work work = new Work();
public WuKong() {
System.out.println("执行了构造函数");
}
public void setName(String name) {
this.name = name;
}
public void setWork(String address, String doWhat) {
this.work.setAddress(address);
this.work.setDoWhat(doWhat);
}
public void say(){
System.out.println("我是" + name + ",我的工作是" + work.getAddress() + work.getDoWhat());
}
}
CloneUtils
public class CloneUtils {
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
TestClient
public class TestClient {
public static void main(String[] args) {
WuKong wukong = new WuKong();
wukong.setName("真·悟空");
wukong.setWork("在原地", "看护唐僧");
WuKong clone_wukong1 = CloneUtils.clone(wukong);
clone_wukong1.setName("克隆悟空1号");
clone_wukong1.setWork("去河边", "打水");
WuKong clone_wukong2 = CloneUtils.clone(wukong);
clone_wukong2.setName("克隆悟空2号");
clone_wukong2.setWork("去村里", "化斋");
wukong.say();
clone_wukong1.say();
clone_wukong2.say();
}
}
输出
执行了构造函数
我是真·悟空,我的工作是在原地看护唐僧
我是克隆悟空1号,我的工作是去河边打水
我是克隆悟空2号,我的工作是去村里化斋
效率测试
代码
public class EfficiencyTest {
public static void main(String[] args) {
testNew(300);
testClone(300);
}
public static void testClone(int num){
Long startTime = System.currentTimeMillis();
ObjDemo demo = new ObjDemo();
for (int i=0;i<num;i++){
ObjDemo demo_clone = demo.clone();
}
Long endTime = System.currentTimeMillis();
System.out.println("clone方式耗时:" + (endTime-startTime));
}
public static void testNew(int num){
Long startTime = System.currentTimeMillis();
for (int i=0;i<num;i++){
ObjDemo demo = new ObjDemo();
}
Long endTime = System.currentTimeMillis();
System.out.println("new方式耗时:" + (endTime-startTime));
}
}
class ObjDemo implements Cloneable{
public ObjDemo() {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public ObjDemo clone() {
ObjDemo objDemo = null;
try {
objDemo = (ObjDemo) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return objDemo;
}
}
输出
new方式耗时:3313
clone方式耗时:11
原型模式优缺点
优点:
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率
- 可以动态增加或减少产品类
- 原型模式提供了简化的创建结构
- 可以使用深克隆的方式保存对象的状态
缺点:
- 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”
- 在实现深克隆时需要编写较为复杂的代码。