設計模式學習——原型模式

一、應用場景 

在開發中常能見到如下代碼,雖然命名規範,註釋也全面,但是這種代碼並不算優雅,只是重複取值賦值。

能否通過某種方式將其簡化呢?原型模式就可以實現。

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;
}
 
原型模式和單例模式的衝突

對比單例模式和克隆模式,兩者是存在一定衝突的。

如果對象是單例的,要不破壞單例的就只能淺克隆,克隆來克隆去都是同一個對象。深度克隆實現克隆出新對象纔是我們需要的,淺克隆不符合克隆模式需要達成的效果。

而如果使用深度克隆,又會導致單例被破壞。


 

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