03.【设计模式】原型模式

1.定义

  • 原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  • 调用者不需要知道任何创建细节,不调用构造函数。
  • 属于创建型模式

原型模式的核心在于拷贝原型对象 。以系统中已存在的一个对象为原型,直接基于内存二进制流进
行拷贝,无需再经历耗时的对象初始化过程(不调用构造函数) 性能提升许多。当对象的构建过程比较耗时时,可以利用当前系统中已存在的对象作为原型,对其进行克隆(一般是基于二进制流的复制)躲避初始化过程,使得新对象的创建时间大大减少。

注:对不通过new关键字,而是通过对象拷贝来实现创建对象的模式就称作原型模式

2.原型模式适用场景

  1. 类初始化消耗资源较多
  2. new产生的一个对象重要非堂整琐的过程 (数据准备、 访问权限等
  3. 构造函数比较复杂。
  4. 循环体中生产大量对象时

在Spring中,原型模式应用得非常广泛。 例如 scope="prototype"在我们经常用的JSON.parseObject()也是一种原型模式。

3.原型的通用写法

  • IPrototype接口
public interface IPrototype<T> {
    T clone();
}
  • 创建具体需要克隆的对象ConcretePrototype继承原型接口
public class ConcretePrototype implements IPrototype{

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public ConcretePrototype clone() {
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setAge(age);
        concretePrototype.setName(name);
        return concretePrototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 测试代码
public class Client {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(12);
        prototype.setName("小郑");

        // 拷贝原型对象
        ConcretePrototype clone = prototype.clone();
        System.out.println(prototype);
        System.out.println(clone);
    }
}
  • 运行结果
    在这里插入图片描述
  • 类图
    在这里插入图片描述

我们可以看到,原型模式 主要包含三个角色

  • 客户(Client):客户类提出创建对象的请求
  • 抽象原型(Prototype):规定拷贝接口。
  • 具体原型 (Concrete Prototype): 被拷贝的对象

4.分析原型浅克隆所带来的的问题

看完原型的通用写法,有小伙伴就问了,原型模式就这么简单吗?对,就是这么简单。在这个简单的场景之下,看上去操作好像变复杂了。但如果有几百个属性需要复制,那我们就可以一劳永逸。但是,上面的复制过程是我们自己完成的,在实际编码中,我们一般不会浪费这样的体力劳动,JDK 已经帮我们实现了一个现成的API,我们只需要实现Cloneable接口即可。-

  • 修改ConcretePrototype类实现Cloneable接口:
@Data
public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

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

重新运行,也会得到同样的结果。有了JDK的支持再多的属性复制我们也能轻而易举地搞定了。下面我们再来做一个测试,给ConcretePrototype增加一个个人爱好的属性hobbies

  • ConcretePrototype增加属性
@Data
public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}
  • 修改客户端测试代码
public class Client {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(12);
        prototype.setName("小郑");
        List<String> hobbies = new ArrayList<>();
        hobbies.add("书法");
        hobbies.add("弹琴");
        prototype.setHobbies(hobbies);

        // 拷贝原型对象
        ConcretePrototype clone = prototype.clone();
        clone.getHobbies().add("美术");
        System.out.println(prototype);
        System.out.println(clone);
    }
}
  • 结果
    在这里插入图片描述
    我们给,复制后的克隆对象新增一项爱好,发现原型对象也发生了变化,这显然不符合我们的预期,因为我们希望克隆出来的对象应该和原型对象是两个独立的对象,不应该再有联系了。从测试结果分析来看,应该是hobbies共用了一个内存地址,意味着复制的不是值,而是引用的地址。这样的话,如果我们修改任意一个对象中的属性值, prototype 和colneType的hobbies值都会改变。这就是我们常说的 浅克隆只是完整复制了值类型数据,没有赋值引用对象 。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。那如何解决这个问题呢?下面我们来看深度克隆继续改造

5.使用序列化实现深克隆

在上面的基础上我们继续改造,增加一个deepClone()方法:

  • ConcretePrototype类
@Data
public class ConcretePrototype implements Cloneable, Serializable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    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 +
                '}';
    }
}
  • 测试
public class Client {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(12);
        prototype.setName("小郑");
        List<String> hobbies = new ArrayList<>();
        hobbies.add("书法");
        hobbies.add("弹琴");
        prototype.setHobbies(hobbies);

        // 拷贝原型对象
        ConcretePrototype clone = prototype.deepClone();
        clone.getHobbies().add("美术");
        System.out.println(prototype);
        System.out.println(clone);
    }
}
  • 结果
    在这里插入图片描述

6.克隆破坏单例模式

如果我们克隆的目标的对象是单例对象那意味着,深克隆就会破坏单例。实际上防止克隆破坏单例解决思路非常简单,禁止深克隆便可。要么你我们的单例类不实现Cloneable 接口:要么我们重写clone)方法,在cone方法中返回单例对象即可,具体代码如下

    @Override
    public ConcretePrototype clone() {
        return INSTANCE;
    }

7.总结

原型模式的优点

  • 性能优良,Java 自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作

原型模式的缺点

  • 需要为每一个类配置一个克隆方法。
  • 克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则
  • 在实现深克隆时需要编写较为复杂的代码,而目当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深拷贝、浅拷贝需要运用得当
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值