用原型模式解決重複setter方法

什麼是原型模式

原型模式(Prototype Pattern)是指原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象,調用者不需要知道任何創建細節,不調用構造函數,原型模式屬於創建型模式。

原型模式示例1

1.標準的原型模式需要先創建一個原型接口
package com.zwx.design.pattern.prototype;

public interface IPrototype {
    IPrototype clone();
}
2.創建一個對象實現原型接口
package com.zwx.design.pattern.prototype.impl;

import com.zwx.design.pattern.prototype.IPrototype;
import java.util.List;

public class PrototypeImplA implements IPrototype{
    private String name;

    private int age;

    private List<String> phoneList;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    public List<String> getPhoneList() {
        return phoneList;
    }

    public void setPhoneList(List<String> phoneList) {
        this.phoneList = phoneList;
    }

    @Override
    public IPrototype clone() {
        PrototypeImplA prototypeImplA = new PrototypeImplA();
        prototypeImplA.setAge(this.age);
        prototypeImplA.setName(this.name);
        prototypeImplA.setPhoneList(this.phoneList);
        return prototypeImplA;
    }
}

PrototypeImplA實現了接口IPrototype,並且實現了clone方法,返回了一個新的對象

3.編寫測試類
package com.zwx.design.pattern.prototype;

import com.zwx.design.pattern.prototype.impl.PrototypeImplA;
import java.util.ArrayList;
import java.util.List;

public class ProtoTypeTest {
    public static void main(String[] args) throws Exception {
        PrototypeImplA prototypeImplA = new PrototypeImplA();
        prototypeImplA.setAge(18);
        prototypeImplA.setName("張三");
        List<String> phoneList = new ArrayList<>();
        phoneList.add("88888888");
        phoneList.add("77777777");
        prototypeImplA.setPhoneList(phoneList);

        PrototypeImplA cloneProtoType = (PrototypeImplA) prototypeImplA.clone();
        System.out.println(prototypeImplA.getPhoneList() == cloneProtoType.getPhoneList());//true
    }
}

最後一個會輸出true,這是因爲這種克隆方式是淺克隆,對象中如果有引用對象那麼被克隆後的對象依然會指向原對象,如果需要複製兩個獨立的對象,則需要使用深克隆,後面示例2中我們會對比一下兩種克隆方式

原型模式示例2

1.創建一個原型對象,實現Cloneable接口
package com.zwx.design.pattern.prototype.impl;

import java.io.*;
import java.util.List;

public class PrototypeB implements Cloneable {
    private String name;

    private int age;

    private List<String> phoneList;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<String> getPhoneList() {
        return phoneList;
    }

    public void setPhoneList(List<String> phoneList) {
        this.phoneList = phoneList;
    }
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Object類默認有clone()方法,protected級別,且是淺克隆,如果我們需要使用默認的clone()方法,則必須實現一個Cloneable接口(Cloneable是一個標記接口,類似的還有Serializable等接口),否則會拋出異常CloneNotSupportedException

2.編寫測試類
package com.zwx.design.pattern.prototype;

import com.zwx.design.pattern.prototype.impl.PrototypeB;
import java.util.ArrayList;
import java.util.List;

public class ProtoTypeTest2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        PrototypeB prototypeImplB = new PrototypeB();
        prototypeImplB.setAge(18);
        prototypeImplB.setName("張三");
        List<String> phoneList = new ArrayList<>();
        phoneList.add("88888888");
        phoneList.add("77777777");
        prototypeImplB.setPhoneList(phoneList);

        PrototypeB cloneProtoTypeB = (PrototypeB)prototypeImplB.clone();
        System.out.println(prototypeImplB.getPhoneList() == cloneProtoTypeB.getPhoneList());//true
    }
}

這時候輸出的結果還是true,因爲上面我們用的是Object自帶的clone()方法,默認是淺克隆,那麼如何實現深克隆呢?接下來我們對PrototypeB類進行改寫。

3.改寫PrototypeB 類,實現深克隆
package com.zwx.design.pattern.prototype.impl;

import java.io.*;
import java.util.List;

public class PrototypeB implements Cloneable, Serializable {
    private String name;

    private int age;

    private List<String> phoneList;

    public String getName() {
        return name;
    }

    public void setName(String name) throws CloneNotSupportedException{
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<String> getPhoneList() {
        return phoneList;
    }

    public void setPhoneList(List<String> phoneList) {
        this.phoneList = phoneList;
    }
    public Object clone() throws CloneNotSupportedException {
//        return super.clone();
        return this.deepClone();
    }

    public PrototypeB 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);

            PrototypeB clone = (PrototypeB)ois.readObject();
            return clone;
        }catch (Exception e){
            return null;
        }
    }
}

多實現了一個Serializable接口,然後clone()方法中返回了自定義的深克隆方法deepClone(),這時候再運行測試類,返回的就是false!

4.深克隆帶來的問題

深克隆會破壞單例對象,所以如果是單例對象我們可以通過兩個辦法防止破壞:
1、不實現Cloneable接口,使得該類不支持克隆
2、重寫clone()方法,並且返回單例對象

原型模式適用場景

1、類初始化消耗資源較多時
2、初始化一個對象時需要非常繁瑣的過程時
3、構造函數比較複雜時
4、循環體中生產大量對象時,可讀性下降時

原型模式優點

1、當創建一個對象比較複雜時,使用原型對象通常效率會更高

原型模式缺點

1、需要爲每個對象配備克隆或者可拷貝的方法
2、對克隆複雜對象或者對克隆出來的對象進行復雜改造時,易帶來風險,深克隆、淺克隆要運用得當

總結

原型模式就是如何快速構建對象的方法總結, 通過簡單工廠將getter、setter封裝到某個方法中,實現快速複製。
原型模式到底應該採用淺克隆還是深克隆?這個應根據實際業務場景具體分析,但是即使淺克隆能滿足需求,也應該實現Cloneable接口,將clone()方法定義爲public,並調用super.clone()返回克隆對象

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