设计模式学习——原型模式

原型模式的定义

原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

调用这不需要知道任何创建细节,不调用构造函数。

属于创建型模式。

 

原型模式的使用场景

在开发中常能见到如下代码,虽然命名规范,注释也全面,但是这种代码并不算优雅,只是重复取值赋值。

能否通过某种方式将其简化呢?原型模式就可以实现。

public UserDTO converEntityToDTO(UserEntity entity){
    UserDTO dto = new UserDTO();
    //用户名
    dto.setUserName(entity.getUserName());
    //用户手机号
    dto.setUserPhone(entity.getUserPhone());
    //用户地址
    dto.setUserAddress(entity.getUserAddress());
    //用户性别
    dto.setGender(entity.getGender);
    //用户头像
    dto.setPicture(entity.getPicture);
    //用户xxx...
    //....more lines
 
    return dto;
}


原型模式适用的场景:

1.类初始化消耗的资源较多

2.new一个对象的过程较为繁琐(准备数据、访问权限等)

3.构造函数较为复杂

4.循环中创建大量对象

在Spring中,原型模式使用广泛,如scope="prototype",还有经常使用到的JSON.parseObject()也是原型模式的应用。

 

原型模式——浅克隆

/**
 * 原型的抽象
 */
public interface IPrototype {
    IPrototype clone();
}
/**
 * @Description: 原型的具体实现类
 */
public class PrototypeObj 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 IPrototype clone() {
        PrototypeObj prototypeObj = new PrototypeObj();
        prototypeObj.setAge(this.age);
        prototypeObj.setName(this.name);
        return prototypeObj;
    }

    @Override
    public String toString() {
        return "PrototypeObj{" + "age=" + age +
                ", name='" + name + '}';
    }
}
/**
 * @Description:测试类
 */
public class PrototypeTest {

    public static void main(String[] args) {
        PrototypeObj obj = new PrototypeObj();
        obj.setAge(18);
        obj.setName("prototype");
        System.out.println("obj = " + obj);

        PrototypeObj cloneObj = (PrototypeObj) obj.clone();
        System.out.println("cloneObj = " + cloneObj);
    }
}

原型模式就是这样,只是上面的属性赋值过程是手动完成的,该如何优化呢?

其实JDK中已经提供了用于clone的API,只需要实现Cloneable接口即可:

/**
 * @Description: 优化后的原型的具体实现类
 */
public class PrototypeObj implements Cloneable {
    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
    protected Object clone()  {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

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

重新运行,可以发现仍可以得到一样的结果。这样繁多的属性复制就可以通过JDK的api解决了。

再进行一个测试,给原型对象加上一个family属性:

/**
 * @Description: 原型的具体实现类
 */
public class PrototypeObj implements Cloneable {
    private int age;
    private String name;
    private List<String> family;//家庭成员

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

    public List<String> getFamily() {
        return family;
    }

    public void setFamily(List<String> family) {
        this.family = family;
    }

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

    @Override
    public String toString() {
        return "PrototypeObj{" +
                "age=" + age + ", name='" + name + ", family="
                + family + '}';
    }
}
/**
 * @Description: 测试类
 */
public class PrototypeTest {
    public static void main(String[] args) {
        PrototypeObj obj = new PrototypeObj();
        obj.setAge(18);
        obj.setName("prototype");
        List<String> family = new ArrayList<>();
        family.add("父亲");
        family.add("母亲");
        obj.setFamily(family);

        PrototypeObj cloneObj = (PrototypeObj) obj.clone();
        cloneObj.getFamily().add("表妹");
        System.out.println("obj = " + obj);
        System.out.println("cloneObj = " + cloneObj);
    }
}

可以发现,给克隆后的对象的家庭成员添加一个表妹,原型对象的家庭成员也发生了改变。

克隆出来的对象和原对象应该是两个独立了的对象才对,这种情况显然不符合预期。

System.out.println(obj.getFamily()==cloneObj.getFamily());

比较两个对象中该属性地址值,发现是相同的。这表示复制的并不是值,而是引用地址。

那么当修改两个对象任意其一的family属性时,两者都会发生改变。

这就是所谓的浅克隆”,只是复制了对象中属性的地址值,不是真正的重新赋予一个值相同的属性。

一处对象属性变其他地方也改变,互相干扰。这显然不是我们想要的结果。

 

原型模式——深度克隆

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

/**
 * @Description: 原型的具体实现类
 */
@Data
public class PrototypeObj implements Cloneable,Serializable {

    private int age;
    private String name;
    private List<String> family;//家庭成员

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

    public PrototypeObj 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);
            PrototypeObj cloneObj = (PrototypeObj) ois.readObject();
            return cloneObj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

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

 

/**
 * @Auther: jesses
 * @Date: 2021/4/13
 * @Description: 深度克隆的测试
 */
public class DeepCloneTest {
    public static void main(String[] args) {
        PrototypeObj obj = new PrototypeObj();
        obj.setAge(24);
        obj.setName("jesses");
        List<String> family = new ArrayList<>();
        family.add("父亲");
        family.add("母亲");
        obj.setFamily(family);

        PrototypeObj cloneObj = obj.deepClone();
        cloneObj.getFamily().add("表哥");

        System.out.println("obj = " + obj);
        System.out.println("cloneObj = " + cloneObj);
        System.out.println(obj.getFamily()==cloneObj.getFamily()); //比较对象中引用类型的地址
    }
}

运行得到了预期的结果,这样就只是对值进行拷贝了。

 

原型模式和克隆的关系

为什么原型模式一直在说克隆?

原型模式其实就是如何快速构建对象的方法总结,克隆是原型模式实现的最常见的方式,一般通过实现JDK提供的Cloneable接口,实现快速复制。

 

克隆破坏单例模式

如果克隆的目标是单例对象,那么深克隆就会破坏单例。

防止克隆破坏单例就要禁止深克隆。要么单例类不实现Cloneable接口,要么重写clone()方法,在clone方法中返回单例对象。

比如:

/**
 * @Description: 原型的具体实现类
 */
@Getter
@Setter
public class PrototypeObj implements Cloneable,Serializable {

    private int age;
    private String name;
    private List<String> family;//家庭成员


    //构造方法私有化
    private PrototypeObj() {
    }

    public static PrototypeObj INSTANCE=new PrototypeObj();

     //调用clone时返回静态初始化的单例对象
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return INSTANCE;
    }


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

原型模式和单例模式的冲突
对比单例模式和克隆模式,两者是存在一定冲突的。

如果对象是单例的,要不破坏单例的就只能浅克隆,克隆来克隆去都是同一个对象。深度克隆实现克隆出新对象才是我们需要的,浅克隆不符合克隆模式需要达成的效果。

而如果使用深度克隆,又会导致单例被破坏。

 

原型模式在JDK中的应用:

ArrayList是实现Cloneable接口,重写clone方法,将list中的元素遍历复制了一遍。

但是这种方式并不适用于业务中的代码使用,如果对象中定义了多种集合类型,每种情况都要单独处理,这种硬编码不如使用序列化来操作。

 

原型模式的优点

1.基于二进制流的拷贝,性能比new创建对象提升许多。

2.简化了创建对象时内部繁琐的过程

 

原型模式的缺点

1.需要为每一个类配置一个克隆方法

2.克隆方法位于类的内部,对类进行改造的时候,需要修改各个类的代码,违反开闭原则

3.实现深克隆代码较复杂,当多个类嵌套时,每一层对象都要实现深克隆,实现比较麻烦。需根据需求场景适用浅克隆和深克隆。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值