设计模式之原型模式

prototype design pattern

原型模式的概念、原型模式的结构、原型模式的优缺点、原型模式的使用场景、浅拷贝与深拷贝、原型模式的实现示例、原型模式的源码分析


1、原型模式的概念

  原型模式,即将一个对象作为原型对象,通过对其克隆而复制出多个与原型对象类似的新实例。

  使用原型模式创建对象要比使用 new 等关键字创建对象性能好的多,因为原型模式的本质是使用 Object 的 clone() 方法克隆对象,而这个方法是本地方法,它直接操作内存中的二进制流,特别是大对象时,性能差异特别明显。使用原型模式就好比复制粘贴,而不是亲手码。需要注意的是,使用原型模式时需要考虑克隆的深、浅对结果的影响。

2、原型模式的结构

  • 原型类实现 Cloneable 接口。
  • 原型类实现 clone() 方法。

prototype-class

3、原型模式的优缺点

3.1、优点
  • 比使用 new 等关键字创建对象在性能上好很多,因为其是本地方法,直接操作内存中的二进制流。
  • 对于相似度高的大对象的创建,效率要高很多。
  • 原型对象在继承时,子类重写父类方法时可先调用父类方法,在扩展,开发效率高。
3.2、缺点
  • 当多个目标对象共享同一个原型对象的引用数据时(如 array、object 等),可能会相互影响。主要原因是浅拷贝与深拷贝的实现不同。

4、原型模式的使用场景

  • 基类的结构和数据都要被复用时。
  • 对目标对象的修改不影响既有的原型对象时(深拷贝时完全不影响)。
  • 当一个对象创建时需要繁琐的数据准备和访问权限时。
  • 当一个对象需要共享给多个对象,且每个对象都有可能修改该对象时。
  • 原型模式很少单独出现,一般和工厂方法模式和抽象工厂模式结合使用,先通过原型模式创建一个对象,然后由工厂方法或抽象工厂提供给调用者使用。

5、深拷贝与浅拷贝

  • 浅拷贝:

    浅拷贝只是在栈上多了一份当前对象的引用,当前对象在堆中只有一份,且地址未发生变化。

  • 深拷贝:

    深拷贝是将当前对象复制了一份,且在堆中分配了内存,从物理层面来说,拷贝结果与原始对象已经是两个东西了,只是长得一样而已。

6、原型模式的实现示例

6.1、浅拷贝实现原型模式

  定义原型类:

public class Prototype implements Cloneable {

    private String username;

    private Integer gender;

    private Prototype friend;

    public Prototype(String username, Integer gender) {
        super();
        this.username = username;
        this.gender = gender;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Prototype getFriend() {
        return friend;
    }

    public void setFriend(Prototype friend) {
        this.friend = friend;
    }

    @Override
    public Prototype clone() {
        try {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

  测试类:

public class PrototypeTest {

    public static void main(String[] args) {
        Prototype prototype = new Prototype("zed", 24);
        prototype.setFriend(new Prototype("fizz", 21));

        Prototype prototype1 = prototype.clone();
        Prototype prototype2 = prototype.clone();

      	System.out.println("prototype = " + prototype + " prototype.friend = "+ prototype.getFriend().hashCode());
        System.out.println("prototype = " + prototype + " prototype.friend = "+ prototype.getFriend().hashCode());
        System.out.println("prototype1 = " + prototype1 + " prototype1.friend = "+ prototype1.getFriend().hashCode());
        System.out.println("prototype2 = " + prototype2 + " prototype2.friend = " + prototype2.getFriend().hashCode());
    }
}

  测试结果:

  可以看到,浅拷贝只是复制了当前对象的指针,也就是说只是在栈上多了一份原对象的引用。

prototype = org.xgllhz.designpattern.createtype.prototype.shallow.Prototype@5034c75a prototype.friend = 748658608
prototype1 = org.xgllhz.designpattern.createtype.prototype.shallow.Prototype@396a51ab prototype1.friend = 748658608
prototype2 = org.xgllhz.designpattern.createtype.prototype.shallow.Prototype@51081592 prototype2.friend = 748658608
6.2、深拷贝实现原型模式

  引用对象:

public class Children implements Cloneable, Serializable {

    private static final long serialVersionUID = 2263495181008794106L;

    private String username;

    private Integer gender;

    public Children(String username, Integer gender) {
        this.username = username;
        this.gender = gender;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    @Override
    public Children clone() {
        try {
          	// 该类属性全是非引用数据类型 故直接使用默认 clone 实现
            return (Children) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

  原型类:

public class Prototype implements Cloneable, Serializable {

    private static final long serialVersionUID = -2375259498572341431L;

    private String username;

    private Children children;

    public Prototype(String username, Children children) {
        super();
        this.username = username;
        this.children = children;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Children getChildren() {
        return children;
    }

    public void setChildren(Children children) {
        this.children = children;
    }

    /**
     * 深拷贝
     * 深拷贝推荐用序列化实现
     * @return
     */
    @Override
    public Prototype clone() {
        ByteArrayOutputStream byteArrayOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            // 序列化
            byteArrayOutputStream = new ByteArrayOutputStream();
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);

            // 反序列化
            byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            objectInputStream = new ObjectInputStream(byteArrayInputStream);
            Object o = objectInputStream.readObject();

            return (Prototype) o;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (objectOutputStream != null) {
                    objectOutputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
                if (objectInputStream != null) {
                    objectInputStream.close();
                }
                if (byteArrayInputStream != null) {
                    byteArrayInputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  测试类:

public class PrototypeTest {

    public static void main(String[] args) {
        Children children = new Children("Kaisa", 21);
        Prototype prototype = new Prototype("Kassadin", children);

        Prototype prototype1 = prototype.clone();
        Prototype prototype2 = prototype.clone();

        System.out.println("prototype = " + prototype + " prototype.friend = "+ prototype.getChildren().hashCode());
        System.out.println("prototype1 = " + prototype1 + " prototype1.friend = "+ prototype1.getChildren().hashCode());
        System.out.println("prototype2 = " + prototype2 + " prototype2.friend = " + prototype2.getChildren().hashCode());
    }
}

  测试结果:

  可以看到,其引用对象的地址发生了变化,说明深拷贝是将原对象复制了一份,且在堆中重新分配了内存。

prototype = org.xgllhz.designpattern.createtype.prototype.deep.Prototype@50c87b21 prototype.friend = 403716510
prototype1 = org.xgllhz.designpattern.createtype.prototype.deep.Prototype@5649fd9b prototype1.friend = 701141022
prototype2 = org.xgllhz.designpattern.createtype.prototype.deep.Prototype@2d928643 prototype2.friend = 112061925

7、原型模式的源码分析

  原型模式的本质是拷贝原来的对象从而创建新的对象。但当原对象存在引用数据类型时,若使用浅拷贝实现,则对新对象的修改会导致原对象也被修改;若使用深拷贝实现,则不会出现这种情况。

  JDK 中存在很多深拷贝的源码,以 java.util.ArrayList 为例:

public Object clone() {
  	try {
    		ArrayList<?> v = (ArrayList<?>) super.clone();
      	// 在这里对数组进行了拷贝 这个拷贝实际上是在堆中生成了一个新对象
    		v.elementData = Arrays.copyOf(elementData, size);
    		v.modCount = 0;
    		return v;
  	} catch (CloneNotSupportedException e) {
    		// this shouldn't happen, since we are Cloneable
    		throw new InternalError(e);
  	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值