设计模式之原型模式详解

设计模式之原型模式详解

一、什么原型模式

原型模式 (Prototype Pattern) 是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。原型模式的核心在于拷贝原型对象。简单来说,对于不通过new 关键字,而是通过对象拷贝来实现创建对象的模式就可以称为原型模式。

二、原型模式的角色

我们先来看下原型模式的通用类图:
在这里插入图片描述

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

二、原型模式示例

先创建一个原型IPrototype接口:

public interface IPrototype<T> {
    T clone();
}

创建具体需要克隆的对象 ConcreteProtoType

@Data
public class ConcretePrototype implements IPrototype<ConcretePrototype> {

    private int age;
    private String name;

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

测试:

public class PrototypeTest {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(16);
        prototype.setName("YanZi");
        System.err.println(prototype);

        //拷贝原型对象
        ConcretePrototype cloneObj = prototype.clone();
        System.err.println(cloneObj);
    }
}

控制台输出:
在这里插入图片描述
这样一个最简单的原型模式就完成了,但是上面的复制过程是我们自己完成的,如果属性非常多,那么写起来会很繁琐。在JDK中已经提供了一个现成的API,只需要实现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;
        }
    }
}

以上代码用上面同样的测试类,输出结果是一样的。紧接着我们再给ConcretePrototype加上一个hobbies属性来说明深、浅拷贝的问题:

@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;
        }
    }
}

我们编写一个测试类来测试一下:

public class ShallowCopyTest {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototypeObj = new ConcretePrototype();
        prototypeObj.setAge(16);
        prototypeObj.setName("YanZi");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("Java");
        hobbies.add("Python");
        prototypeObj.setHobbies(hobbies);
        //拷贝原型对象
        ConcretePrototype cloneObj = prototypeObj.clone();
        cloneObj.getHobbies().add("Go");

        System.err.println("原型对象:" + prototypeObj);
        System.err.println("克隆后的对象:" + cloneObj);
        System.err.println(prototypeObj == cloneObj);

        System.err.println("原型对象的爱好:" + prototypeObj.getHobbies());
        System.err.println("克隆对象的爱好:" + cloneObj.getHobbies());
        System.err.println(prototypeObj.getHobbies() == cloneObj.getHobbies());
    }
}

控制台输出:
![在这里插入图片描述](https://img-blog.csdnimg.cn/799ac33ec9774386b57056dd7e9d2dae.png
从输出结果可以看出,在我们原型对象和克隆后的对象中hobbies属性值在克隆前后是一样的,打印出hobbies属性在内存中的地址值也是一样的,这说明了克隆过程中只是完整的复制了值类型的数据,没有赋值引用对象,也就是所有的引用对象仍然指向原来的对象。只要我们改变克隆前后任一对象中的hobbies属性,两个对象中的hobbies值都会改变!接下来我们使用深度克隆来解决这个问题:

@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;
        }
    }
}

我们再用同样的测试类来测试一下,只是改变了克隆方式:

public class DeepCloneTest {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototypeObj = new ConcretePrototype();
        prototypeObj.setAge(16);
        prototypeObj.setName("YanZi");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("Java");
        hobbies.add("Python");
        prototypeObj.setHobbies(hobbies);
        //拷贝原型对象
        ConcretePrototype cloneObj = prototypeObj.deepClone();
        cloneObj.getHobbies().add("Go");

        System.err.println("原型对象:" + prototypeObj);
        System.err.println("克隆后的对象:" + cloneObj);
        System.err.println(prototypeObj == cloneObj);
        
        System.err.println("原型对象的爱好:" + prototypeObj.getHobbies());
        System.err.println("克隆对象的爱好:" + cloneObj.getHobbies());
        System.err.println(prototypeObj.getHobbies() == cloneObj.getHobbies());
    }
}

从控制台输出可以看出已经达到了我们的预期:
在这里插入图片描述

  • 优点

    • 性能较好,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多
    • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程
  • 缺点

    • 需要为每一个类配置一个克隆方法
    • 克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则

三、注意

当我们克隆的目标对象是单例对象的时候,我们使用深克隆可能会破坏掉单例,深克隆要视情况而使用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值