设计模式-原型模式

设计模式-原型模式

和单例模式一样,原型模式是一种创建型设计模式,就是从一个样板对象中复制出一个内部属性一致的对象。

举个例子:

假如有一天,小灰被外星人抓走了,外星人要拿小灰做实验,想了解小灰在吃得好、睡得好、玩得开心的场景下,与现实中小灰的生存状态有什么区别。

于是,外星人克隆了几个一模一样的小灰:复制体A,复制体B,复制体C

就这样,小灰的原型被留在现实中,而三个复制体分别提供了吃得好、睡得好、玩得开心三种不同环境,小灰的原型则不受三个复制体的影响。

过了一段时间,我们来观察一下本体与分身的生存状态。

这就是实现了保护性拷贝的原型模式,这种设计模式的UML图如下:

prototype:n. 原型;样本;vt. 制作原型

concrete:adj. 混凝土的;实在的,具体的;有形的;n. 具体物;凝结物

图片

原型模式在Java中的一个最典型的应用在Object类中

在Java语言中,Object类实现了Cloneable接口,一个对象可以通过调用**Clone()**方法生成对象,这就是原型模式的典型应用。

但需要注意的是,clone()方法并不是Cloneable接口里的,而是Object类里的,Cloneable是一个标识接口,标识这个类的对象是可被拷贝的,如果没有实现Cloneable接口,却调用了clone()方法,就会报错。

// protected native Object clone() throws
 CloneNotSupportedException;protected Object clone() throws CloneNotSupportedException {
    if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException(
			"Class " + getClass().getName() +
			" doesn't implement Cloneable");
    }
    return internalClone();
}

// Native helper method for cloning.

private native Object internalClone();

深拷贝和浅拷贝

Java中的数据类型,分为基本类型和引用类型。在一个方法里的变量如果是基本类型的话,变量就直接存储在这个方法的栈帧里,例如int、long等;而引用类型则在栈帧里存储这个变量的指针,指向堆中该实体的地址,例如String、Array等。深拷贝和浅拷贝是只针对引用数据类型的。

比如一个方法有一个基本类型参数和一个引用类型参数,在方法体里对参数重新赋值,会影响传入的引用类型参数,而不会影响基本类型参数,因为基本类型参数是值传递,而引用类型参数是引用传递。

先定义一个用户类:

// 这是一个非常简单的用户类

  public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name=name;
        this.age=age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override

    public String toString() {
        return "User{name='" + name + ", age=" + age +'}';
    }

}
  private int x=10;

  public void updateValue(int value) {
    value = 3 * value;
  }

  private User user= new User("大黄",20);

  public void updateUser(User student) {
    student.setName("小灰");
    student.setAge(18);
  }

  public void test() {
    System.out.println("调用前x的值:"+x);
    updateValue(x);
    System.out.println("调用后x的值:"+x);
    System.out.println("调用前user的值:"+user.toString());
    updateUser(user);
    System.out.println("调用后user的值:"+user.toString());
  }

log打印结果:

调用前x的值:10
调用后x的值:10
调用前user的值:User{name='大黄, age=20}
调用后user的值:User{name='小灰, age=18}

传递基本类型的方法(updateValue())流程图:

图片

传递引用类型的方法(updateUser())流程图:

图片

这其中也包含着例外,比如String类型和大小不超过127的Long类型,虽然也是引用类型,却像基本类型一样不受影响。这是因为它们会先比较常量池维护的值,这涉及VM的内容,今天不做过多讨论。

浅拷贝是在按位(bit)拷贝对象,这个对象有着原始对象属性值的一份精确拷贝。我们结合应用场景分析一下,还是刚才的User类,我们增加一个存放地址的内部类Address,我们需要用户信息可以被其他module查询,但是不允许它们被其他module修改,新增代码如下:

// 这是一个稍微复杂的、支持拷贝的用户类

  public class User implements Cloneable {
// ……省略上文代码……
      private Address address;

    @NonNull
    @NotNull
    @Override

    public User clone() {
        try{
            return (User)super.clone();

        }catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public class Address{
        // 地市
        public String city;
        // 区县
        public String county;
        // 乡镇街道
        public String street;
    } 
}

浅拷贝会带来数据安全方面的隐患,这个时候就需要深拷贝了

对于有多层对象的,每个对象都需要实现Cloneable并重写clone()方法,才可以实现对象的串行层层拷贝

// 这是一个更复杂的、支持深拷贝的用户类

  public class User implements Cloneable {

      // ……省略上文代码……
    @NonNull
    @NotNull
    @Override
    public User clone() {
        try{
            User newUser = (User)super.clone();
            newUser.setName(this.name);
            newUser.setAddress(this.address.clone());
            return newUser;
        }catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public class Address implements Cloneable{
        // ……省略上文代码……
        @NonNull
        @NotNull
        @Override
        public Address clone() {
            try{
                Address newAddress = (Address)super.clone();
                newAddress.city = this.city;
                newAddress.county = this.county;
                newAddress.street = this.street;
                return newAddress;
            }catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}

需要注意的是,上面代码的深拷贝其实并不彻底,因为彻底的深拷贝几乎是不可能实现的,那样不但可能存在引用关系非常复杂的情况,也可能存在引用链的某一级上引用了一个没有实现Cloneable接口的第三方对象的情况。

绝大多数设计模式都是牺牲性能提升开发效率的,原型模式则是为数不多的牺牲开发效率提升性能的设计模式。

对象除了被new出来和clone()出来,还有哪些方式产生?

反射和反序列化

如果需要在循环体内产生大量对象的时候,适合用原型模式实现。原型模式是在内存中拷贝二进制流,比new一个对象性能好很多。

new和clone的对比:

private User user= new User("大黄",20);

  public void testNew(){

    User user1 = new User("小灰",18);

}

  public void testClone(){

    User user2 = user.clone();

}

通过ASM工具查看bytecode,可以看出二者对栈资源的消耗:

// access flags 0x1
  public  testNew()V
   ……省略……
    MAXSTACK  = 4
     MAXLOCALS = 2

 

  // access  flags 0x1
  public  testClone()V
   ……省略……
    MAXSTACK  = 1
     MAXLOCALS = 2

需要注意一点:拷贝不会执行构造函数,所以有时候会存在潜在问题。

这个问题不是不可避免的,参考Android第五大组件Intent的clone()的实现,他就没有使用拷贝

总结原型模式的核心用途:

1.解决构建复杂对象的资源消耗问题,提升创建对象的效率。

2.保护性拷贝,防止外部对只读对象进行需修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值