设计模式-原型模式

原型模式

定义

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

原型模式的克隆分为浅克隆和深克隆。

浅克隆

创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

深克隆

创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

优点
  1. Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
  2. 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点
  1. 需要为每一个类都配置一个 clone 方法
  2. clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
  3. 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
结构

原型模式包含以下主要角色。

  1. 抽象原型类:规定了具体原型对象必须实现的接口。
  2. 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  3. 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
要求
浅克隆
  1. 实现接口Cloneable
  2. 重写方法clone()
  3. 设置方法clone()为public。
深克隆

所有的对象都需要实现java.io.Serializable接口,并且可以序列化。

应用场景

原型模式通常适用于以下场景。

  1. 对象之间相同或相似,即只是个别的几个属性不同的时候。
  2. 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
  3. 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
  4. 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。

示例

现在有一只熊猫(有自己的名字和巢穴),需要克隆出另外的一只熊猫。两只熊猫的属性完全一样,但是却不是同一个对象。

浅克隆

浅克隆的时候,只会对需要克隆的对象进行克隆,但是不会对被设置为属性的对象进行克隆。

巢穴
package com.designpattern.prototype.shallowclone.objects;

public class Lair {
    private String name;

    public Lair(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "Lair{" + "name='" + name + '\'' + '}';
    }
}
熊猫
package com.designpattern.prototype.shallowclone.objects;

public class Panda implements Cloneable{
    private String name;
    private Lair lair;

    public Panda(String name, Lair lair) {
        this.name = name;
        this.lair = lair;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Lair getLair() {
        return lair;
    }

    public void setLair(Lair lair) {
        this.lair = lair;
    }
    
    @Override
    public String toString() {
        return "Panda{" + "name='" + name + '\'' + ", lair=" + lair + '}';
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
克隆
package com.designpattern.prototype.shallowclone;

import com.designpattern.prototype.shallowclone.objects.Lair;
import com.designpattern.prototype.shallowclone.objects.Panda;

public class Application {
    public static void main(String[] args) throws CloneNotSupportedException {
        Panda panda1 = new Panda("A", new Lair("A"));
        Panda panda2 = panda1.clone();
        System.out.println("panda1 = " + panda1);
        System.out.println("panda2 = " + panda2);
        System.out.println("panda1 == panda2 is " + (panda1 == panda2));
        System.out.println("panda1.getLair() == panda2.getLair() is " + (panda1.getLair() == panda2.getLair()));
    }
}

输出

panda1 = Panda{name='A', lair=Lair{name='A'}}
panda2 = Panda{name='A', lair=Lair{name='A'}}
panda1 == panda2 is false
panda1.getLair() == panda2.getLair() is true
深克隆

深克隆的时候,需要每一个属性都满足克隆要求(实现Serializable接口)。

巢穴·改
package com.designpattern.prototype.deepclone.objects;

import java.io.Serializable;

public class Lair implements Serializable {
    private String name;

    public Lair(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Lair{" + "name='" + name + '\'' + '}';
    }
    
}
熊猫·改
package com.designpattern.prototype.deepclone.objects;

import java.io.*;

public class Panda implements Serializable {
    private String name;
    private Lair lair;

    public Panda(String name, Lair lair) {
        this.name = name;
        this.lair = lair;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Lair getLair() {
        return lair;
    }

    public void setLair(Lair lair) {
        this.lair = lair;
    }

    @Override
    public String toString() {
        return "Panda{" + "name='" + name + '\'' + ", lair=" + lair + '}';
    }

    //需要关闭流
    public Panda deepClone() throws IOException, ClassNotFoundException {
        //将对象写到流里
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //从流里读回来
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Panda) ois.readObject();
    }
}
克隆
package com.designpattern.prototype.deepclone;

import com.designpattern.prototype.deepclone.objects.Lair;
import com.designpattern.prototype.deepclone.objects.Panda;

import java.io.IOException;

public class Application {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Panda panda1 = new Panda("A", new Lair("A"));
        Panda panda2 = panda1.deepClone();
        System.out.println("panda1 = " + panda1);
        System.out.println("panda2 = " + panda2);
        System.out.println("panda1 == panda2 is " + (panda1 == panda2));
        System.out.println("panda1.getLair() == panda2.getLair() is " + (panda1.getLair() == panda2.getLair()));
    }
}

输出

panda1 = Panda{name='A', lair=Lair{name='A'}}
panda2 = Panda{name='A', lair=Lair{name='A'}}
panda1 == panda2 is false
panda1.getLair() == panda2.getLair() is false
总结

使用浅克隆的时候,克隆对象和原对象使用相同的引用对象,但是只需要对被克隆对象类实现Cloneable接口即可。深克隆的时候,需要对所有要求被克隆的属性实现Serializable接口,层级不容易控制。

如果一个属性对象不想被复制,可以使用transient关键字来确保该对象(该对象无须实现Serializable接口或者Cloneable接口)不会被复制,新对象的该值为null。

静态变量不管是否被transient修饰,均不能被序列化,反序列化后类中static型变量的值为当前JVM中对应static变量的值,这个值是JVM中的不是反序列化得出的。

此外,如果一个属性对象中有部分信息(不管该信息是否被transient修饰)需要被序列化,可以通过实现Externalizable接口,并重写它的方法来制定需要实例化的信息(或者添加指定的writeObject和readObject方法)。

package com.designpattern.prototype.deepclone.objects;

import java.io.*;

public class Panda implements Serializable {
    private static final long serialVersionUID = -1L;
    private String name;
    private Lair lair;
    private transient Toy toy;
    private transient Bed bed;

    public Panda(String name, Lair lair, Toy toy, Bed bed) {
        this.name = name;
        this.lair = lair;
        this.toy = toy;
        this.bed = bed;
    }

    @Override
    public String toString() {
        return "Panda{" + "name='" + name + '\'' + ", lair=" + lair + ", toy=" + toy + ", bed=" + bed + '}';
    }

    public Panda deepClone() throws IOException, ClassNotFoundException {
        //将对象写到流里
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //从流里读回来
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Panda) ois.readObject();
    }

    //写入内存
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(this.name);
        out.writeObject(this.lair);
        out.writeObject(this.bed);
    }

    //从内存读取
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        lair = (Lair) in.readObject();
        bed = (Bed) in.readObject();
    }
}

此外,还可以与其他模式混合使用,尤其是备忘录模式,作为备份或者快照使用。也可以使用一个额外的类来管理所有的需要复制的对象(提供两个基础方法:保存对象复制对象并返回),每次只需要从管理者获得复制对象,而不需要自己去识别系统中是否已经存在了该对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值