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

一、应用场景 

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

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

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.循环中创建大量对象

 

 

二、简单克隆

一个标准的原型模式代码,应该这样设计。

原型接口:

public interface Prototype {
    Prototype clone();
}

继承原型接口的需要被克隆的对象: 

/**
 * @Auther: jesses
 * @Date: 2020/7/3
 * @Description:
 */
public class ConcretePrototype implements Prototype {
    private int age;
    private String name;
    private List hobbies;

    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 getHobbies() {
        return hobbies;
    }

    public void setHobbies(List hobbies) {
        this.hobbies = hobbies;
    }

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

 操作克隆的client对象:

/**
 * @Auther: jesses
 * @Date: 2020/7/3
 * @Description:
 */
public class Client {

    public Client(){}

    public Prototype startClone(Prototype prototype){
        return prototype.clone();
    }
}

 测试类:

/**
 * @Auther: jesses
 * @Date: 2020/7/3
 * @Description:
 */
public class PrototypeTest {

    public static void main(String[] args) {
        // 创建一个具体的需要克隆的对象,填充属性,方便测试
        ConcretePrototype concretePrototypeA = new ConcretePrototype();
        concretePrototypeA.setAge(18);
        concretePrototypeA.setName("prototype");
        List hobbies = new ArrayList<String>();
        concretePrototypeA.setHobbies(hobbies);

        Client client = new Client();
        ConcretePrototype concretePrototypeB = (ConcretePrototype) client.startClone(concretePrototypeA);

        System.out.println("concretePrototypeA = " + concretePrototypeA);
        System.out.println("concretePrototypeB = " + concretePrototypeB);
        System.out.println(concretePrototypeA.getHobbies());
        System.out.println(concretePrototypeB.getHobbies());
        System.out.println(concretePrototypeA.getHobbies()==concretePrototypeB.getHobbies());
    }
}

可以看出hobbies 的引用地址是相同的,意味着复制的不是hobbies的值,而是引用的地址。

因此当我们修改concretePrototypeA中的hobbies或concretePrototypeB中的hobbies,两个对象中的hobbies都会改变。

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

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

 

三、深度克隆

举个例子,齐天大圣。首先它是一只猴子,拔一根 毫毛就可以吹出千万个泼猴,手里还拿着金箍棒,金箍棒可以变大变小。
这就是我们耳熟能详的原型模式的经典体现。
 
先创建原型类-猴子:
/**
 * @Auther: jesses
 * @Date: 2020/7/3
 * @Description: 原型猴子 Monkey 类
 */
public class Monkey {
    public int height;
    public int weight;
    public Date birthday;
}

然后是猴子的引用对象类-金箍棒: 

/**
 * @Auther: jesses
 * @Date: 2020/7/3
 * @Description: 引用对象金箍棒 Jingubang 类
 */
public class Jingubang implements Serializable {

    public float h = 100;

    public float d = 10;

    public void big() {
        this.d = d * 2;
        this.h = h * 2;
    }

    public void small() {
        this.d = d / 2;
        this.h = h / 2;
    }
}

创建具体的对象-齐天大圣类: 

/**
 * @Auther: jesses
 * @Date: 2020/7/3
 * @Description: 具体的对象齐天大圣 QiTianDaSheng 类
 */
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {

    public Jingubang jingubang;

    //初始化
    public QiTianDaSheng(){
        this.birthday=new Date();
        this.jingubang=new Jingubang();
    }

    /**
     * 深度克隆
     */
    public Object 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);
            QiTianDaSheng copy = (QiTianDaSheng) ois.readObject();
            copy.birthday=new Date();
            return copy;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 浅克隆
     */
    public QiTianDaSheng shallowClone(QiTianDaSheng target){
        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        qiTianDaSheng.height=target.height;
        qiTianDaSheng.weight=target.weight;
        qiTianDaSheng.jingubang=target.jingubang;
        qiTianDaSheng.birthday=new Date();
        return qiTianDaSheng;
    }
}

测试类: 

public class DeepCloneTest {
    public static void main(String[] args) {
        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        QiTianDaSheng deep = (QiTianDaSheng) qiTianDaSheng.deepClone();

        System.out.println("深克隆"+(qiTianDaSheng.jingubang == deep.jingubang));

        QiTianDaSheng shallow = qiTianDaSheng.shallowClone(qiTianDaSheng);
        System.out.println("浅克隆"+(qiTianDaSheng.jingubang == shallow.jingubang));
    }
}

运行结果: 

显然,浅克隆前后两对象中的金箍棒属性是同一个对象,而深度克隆前后中的金箍棒属性不是用一个对象。
 
 

四、小结

原型模式和克隆的关系

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

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

 

克隆破坏单例模式

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

想解决这个问题,就要禁止深度克隆。

要么单例类不再实现Cloneable接口,要么就重写clone()方法,在clone方法中返回单例对象。

@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
 
原型模式和单例模式的冲突

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

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

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


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章