为了账号安全,请及时绑定邮箱和手机立即绑定

设计模式(一)-创建型之原型模式

定义

  • 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新对象
  • 不需要知道任何创建细节,不调用构造函数

适用场景

  • 创建对象麻烦或困难时。1.对象种类繁多,无法整合到一个类时 2.要创建一个类,初始化时需要使用较多资源。
  • 想解耦框架和生成实例时,生成的框架不依赖于具体的类。

优点

  • 创建过程简单
  • 原型模式性能比直接new一个对象性能高

缺点

  • 必须配备克隆方法
  • 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入代码BUG
  • 必须清楚了解深拷贝与浅拷贝

原型模式中的角色

角色说明

  • Prototype(原型)

    • Product角色负责定义用于复制现有实例来生成新实例的方法。在实例程序中,由Cloneable接口来扮演此角色。
  • ConcreteProrotype(具体原型)

    • ConcretePrototype角色负责实现复制现有实例并生成新实例方法。在实例程序中,由User类扮演此角色。
  • Client(使用者)

    • Client角色负责使用复制实例的方法生成新的实例。在实例程序中,由单元测试类扮演此角色。

类图

代码实现

原型模式代码

写法一

1.用java实现原型模式比较简单,只需要将目标类实现Cloneable接口即可

@Getter
@Setter
@AllArgsConstructor
public class User implements Cloneable {
    private String name;
    private Date birthday;

    public User() {
        System.out.println("User constructor被调用");
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        System.out.println("User clone被调用");
        // 浅克隆写法
        return super.clone();
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}'+super.toString();
    }
}

写法二

  1. 抽象父类实现Cloneable接口
public abstract class AbstractUser implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("抽象父类clone方法被调用");
        return super.clone();
    }
}
  1. 目标类继承抽象父类接口
@Getter
@Setter
public class User extends AbstractUser {
    private String name;
    private Date birthday;

    public User() {
        System.out.println("User constructor被调用");
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}'+super.toString();
    }
}

单元测试(写法一)

测试代码

    @Test
    public void testClone() throws CloneNotSupportedException {
        Date birthday = new Date(0L);
        // 1. 创建用户对象
        User user1 = new User();
        user1.setName("小林");
        user1.setBirthday(birthday);
        
        // 2. 克隆用户对象
        User user2 = (User) user1.clone();
        // 3. user1,user2对比
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user1 == user2);

        System.out.println("<==============我是分割线============>");

        // 4. 赋值新的birthday并打印
        user1.getBirthday().setTime(666666666666L);
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user1 == user2);
    }

执行结果

根据user1和user2的hash值不同可以看出是不同的类,但user1的birthday修改后,user2的birthday也同样被修改,说明user1和user2指向了同一个birthday,这里就引发深拷贝与浅拷贝的问题。

深拷贝 VS 浅拷贝

使用clone(),之后,由于birthday是一个引用对象,由于是浅拷贝,user中的引用成员变量依然指向的是同一个。

什么是深拷贝,我们cloneuser对象的同时,也将其内部引用的对象进行拷贝,使得每个引用对象无关联,都是单独的对象。

深拷贝代码改造

重新改造user中clone()方法的实现

@Getter
@Setter
public class User implements Cloneable {
    private String name;
    private Date birthday;

    public User() {
        System.out.println("User constructor被调用");
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        System.out.println("User clone被调用");
        // 浅拷贝写法
        // return super.clone();

        // 深拷贝写法
        User user = (User) super.clone();
        user.birthday=(Date) user.getBirthday().clone();
        return user;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}'+super.toString();
    }
}

执行结果

可以看到改造完成之后user1的birthday与user2的birthday指向不是同一个birthday了。

总结

使用原型模式时要十分注意深拷贝、浅拷贝的问题,即使了解了深拷贝和浅拷贝,在写代码的过程中一个疏忽就可能产生BUG,如果对深拷贝和浅拷贝不了解同学,需要慎用原型模式。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消