原型模式(Prototype Pattern)

一、定义

原型模式(Prototype Pattern):创新型模式之一,用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

二、UML类图

在这里插入图片描述

三、角色职责

  • 原型类(Prototype):声明一个克隆自己的接口。
  • 具体原型类(Concrete Prototype):实现一个克隆自己的操作。
  • 客户端(Client):客户端让一个原型对象克隆自己,从而创建一个新的对象。

四、代码实现

前言:举个栗子,我们现在创建了一个新的QQ号,我们想把我们老QQ号上的好友克隆到新的QQ号上。那么,常规代码如下:
先创建一个QQ好友类

@AllArgsConstructor
@Data
public class QQFriend {
    /**
     * 昵称
     */
    private String nickname;

    /**
     * QQ号
     */
    private String QQNo;
}

然后我们进行克隆

public class PrototypeTest {
    public static void main(String[] args) {
        QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690");
        QQFriend clone = new QQFriend(qqFriend.getNickname(), qqFriend.getQQNo());
        // 运行结果:false
        System.out.println(qqFriend == clone);
    }
}

这种写法,在我们每次创建新对象的时候都要获取原始对象的属性,效率很低,并且不能动态获取对象的运行时状态,如果原始对象的属性改变了,也需要改动代码。对此,我们可以使用原型模式

原型模式又可分为浅拷贝和深拷贝,区别在于对引用数据类型的成员变量的拷贝。

  • 浅拷贝:浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。

    将原先的QQ好友类实现Cloneable接口并重写clone()方法,再添加一个QQ好友的属性

    @AllArgsConstructor
    @Data
    public class QQFriend implements Cloneable {
        /**
         * 昵称
         */
        private String nickname;
    
        /**
         * QQ号
         */
        private String QQNo;
    
        /**
         * QQ好友
         */
        private QQFriend qqFriend;
    
        public QQFriend(String nickname, String QQNo) {
            this.nickname = nickname;
            this.QQNo = QQNo;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    

    查看运行结果

    public class PrototypeTest {
        public static void main(String[] args) throws Exception {
            QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690",new QQFriend("初见","1415478419"));
            QQFriend clone = (QQFriend)qqFriend.clone();
            // 运行结果:true
            System.out.println(qqFriend.getQqFriend() == clone.getQqFriend());
        }
    }
    
    

    通过运行结果可以发现,浅拷贝通过clone()方法拷贝了一个新对象,但是这个新对象的好友属性地址还是指向原始对象的好友属性地址,如果原始对象的属性改变了,那新对象的属性也会发生改变。

  • 深拷贝:深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对象。

    我们只需要将QQ好友类的克隆属性进行修改,将QQ好友类的QQ好友属性在克隆时再次进行克隆。

    @AllArgsConstructor
    @Data
    public class QQFriend implements Cloneable {
        /**
         * 昵称
         */
        private String nickname;
    
        /**
         * QQ号
         */
        private String QQNo;
    
        /**
         * QQ好友
         */
        private QQFriend qqFriend;
    
        public QQFriend(String nickname, String QQNo) {
            this.nickname = nickname;
            this.QQNo = QQNo;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            QQFriend qqFriend = (QQFriend)super.clone();
            if(this.getQqFriend() != null)
                qqFriend.setQqFriend((QQFriend)this.getQqFriend().clone());
            return qqFriend;
        }
    }
    

    查看运行结果

    public class PrototypeTest {
        public static void main(String[] args) throws Exception {
            QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690",new QQFriend("初见","1415478419"));
            QQFriend clone = (QQFriend)qqFriend.clone();
            // 运行结果:false
            System.out.println(qqFriend.getQqFriend() == clone.getQqFriend());
        }
    }
    

    浅拷贝通过clone()方法拷贝了一个新对象时,其对象的好友属性也会拷贝成一个全新属性。这时,我们就完成了深拷贝。

  • 深拷贝(序列化)
    我们还可以通过序列化的方式完成深拷贝。我们在QQFriend类中实现序列化接口。

    @AllArgsConstructor
    @Data
    public class QQFriend implements Serializable {
        /**
         * 昵称
         */
        private String nickname;
    
        /**
         * QQ号
         */
        private String QQNo;
    
        /**
         * QQ好友
         */
        private QQFriend qqFriend;
    
        public QQFriend(String nickname, String QQNo) {
            this.nickname = nickname;
            this.QQNo = QQNo;
        }
    
        public Object cloneBySerializable() throws Exception {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            QQFriend qqFriend = (QQFriend) ois.readObject();
            bos.close();
            oos.close();
            bis.close();
            ois.close();
            return qqFriend;
        }
    }
    

    运行结果

    public class PrototypeTest {
        public static void main(String[] args) throws Exception {
            QQFriend qqFriend = new QQFriend("奶油芝士蘑菇汤", "1706046690",new QQFriend("初见","1415478419"));
            QQFriend clone = (QQFriend)qqFriend.cloneBySerializable();
            // 运行结果:false
            System.out.println(qqFriend.getQqFriend() == clone.getQqFriend());
        }
    }
    

五、源码分析

在Spring中,配置bean时,scope属性的prototype值就是使用的原型模式。
我们直接进入AbstractBeanFactory类的doGetBean()方法。
在这里插入图片描述
在这里插入图片描述
进入到doGetBean()方法可以发现,spring对参数进行了判断,对应调用createBean创建了原型模式的对象

六、优缺点分析

优点

  • 使用原型模式复用的方式创建实例对象,比使用构造函数重新创建对象性能要高。
  • 原型模式可以简化创建的过程,可以直接修改现有的对象实例的值 , 达到复用的目的。

缺点

  • Java 中提供了 cloneable 标识该对象可以被拷贝,但是必须覆盖 Object 的 clone 方法才能被拷贝 。

七、适用场景

当类中定义的构造函数复杂或在内存中循环创建了很多该实例对象,就可以使用原型模式复用不用的对象,用于创建新对象。

八、总结

其实原型模式说白了就是克隆,用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值