Java創建型設計模式 —— 哇,原型設計模式原來是這麼玩的

一、引言

欲言又止,

二、克隆羊

假設現在我們有一個對象,需要拷貝新的對象出來,以下代碼是最簡單粗暴的方式了。 但是如果這個對象有很多屬性呢? 那豈不是太麻煩了,針對這種情況就可以使用我們的原型模式來實現。

原型模式是指:用原型實例指定創建對象的種類,並且通過拷貝原型,從而創建新的對象。

原型模式是一種創建型的設計模式,允許一個對象在創建另外一個可定製的對象,無需知道創建的細節。其實也就是說白了把拷貝的具體實現封裝在類裏面,外部只需要使用對應的方法則可以實現拷貝的效果。

  public static void main(String[] args) {
        
        // 創建一個對象
        Sheep sheep = new Sheep("Tom", 1);

        // 根據上面那個對象,創建新的對象
        Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge());
        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge());

        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep3);

    }

三、使用原型模式實現克隆羊

針對上面剛剛那個案例,使用原型模式進行改進。

步驟一:在Sheep這個類需要實現Cloneable的接口,其實這個Cloneable是一個標記接口,然後在類中需要重寫Object的clone方法,然後通過類調用這個clone方法,從而達到克隆的效果。

如果不實現這個接口,則會拋出CloneNotSupportedException克隆不被支持的異常。

步驟二:重寫Object的clone方法,使用時直接調用即可。

/**
 * @Auther: IT賤男
 * @Date: 2019/8/5 15:21
 * @Description: 克隆羊 - 原型模式
 */
@Data
public class Sheep implements Cloneable {

    private String name;
    private int age;


    public Sheep(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Sheep clone() {
        try {
            // 這裏默認調用父類的實現方法即可,默認是淺拷貝
            return (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
 public static void main(String[] args) {

        Sheep sheep = new Sheep("Tom", 1);
        // 這裏直接調用clone方法即可
        Sheep sheep1 = sheep.clone();
        Sheep sheep2 = sheep.clone();

        System.out.println(sheep == sheep1);
        System.out.println(sheep2 == sheep1);
    }

 

四、理解深拷貝和淺拷貝

淺拷貝

對於基本數據類型的成員變量,淺拷貝會進行值傳遞,也就是將屬性值賦值給新對象。

對於成員是引用類型,比如數組、對象等,那麼淺拷貝會進行引用傳遞。也就是將成員等引用值複製一個給新對象,實際上兩個對象都是指向同一個實例,在這種情況下修改了一個對象中得值,另外一個對象也會隨之而改變。

深拷貝

深拷貝不僅僅複製了所有的基本數據類型,爲所有的引用數據類型的成員變量申請存儲空間。 也就是說引用成員不再拷貝引用地址,而是整個對象進行拷貝。

 

五、深拷貝兩種實現方式

現在我們在Sheep類中新增一個對象叫Friend,並且添加對應的構造方法。

/**
 * @Auther: IT賤男
 * @Date: 2019/8/5 15:21
 * @Description: 克隆羊 - 原型模式
 */
@Data
public class Sheep implements Cloneable {

    private String name;
    private int age;
    private Friend friend;


    public Sheep(String name, int age, Friend friend) {
        this.name = name;
        this.age = age;
        this.friend = friend;
    }

    @Override
    protected Sheep clone() {
        try {
            return (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
 public static void main(String[] args) {

        Sheep sheep = new Sheep("Tom", 1,new Friend("Miqi"));
        // 這裏採用的是默認淺拷貝,所以拷貝出來的friend對象是同一個
        Sheep sheep1 = sheep.clone();
        System.out.println(sheep.getFriend() == sheep1.getFriend());

    }

實現方式一:首先我們需要將Friend這個類,實現默認的clone方法,然後重寫Sheep中的clone方法。

這種方式雖然可以實現,如果引用對象很多,是不推薦使用的

/**
 * @Auther: IT賤男
 * @Date: 2019/8/20 11:25
 * @Description: 
 */
public class Friend implements Cloneable {

    public Friend(String name) {
        this.name = name;
    }

    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
 /**
     * 深拷貝實現方式一
     * @return
     */
    @Override
    protected Sheep clone() {
        try {
            Sheep sheep = (Sheep) super.clone();
            // 這裏需要調用friend的clone方法,然後複製給新克隆出來的對象
            sheep.setFriend((Friend) friend.clone());
            return sheep;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

實現方式二:方式一雖然說可以實現,但是大家也看出來了,引用對象需要重新賦值,那如果引用對象很多,那也很麻煩,所以來看看第二種方式,這種方式比較省事。

首先需要將引用對象實現序列化接口,然後採用序列化的方式來實現深拷貝。

/**
 * @Auther: IT賤男
 * @Date: 2019/8/20 11:25
 * @Description:
 */
public class Friend implements Serializable{

    private static final long serialVersionUID = -3019656355622657141L;

    public Friend(String name) {
        this.name = name;
    }

    private String name;

}
/**
 * @Auther: IT賤男
 * @Date: 2019/8/5 15:21
 * @Description: 克隆羊 - 原型模式
 */
@Data
public class Sheep implements Cloneable, Serializable {

    private static final long serialVersionUID = -270095030076915889L;
    private String name;
    private int age;
    private Friend friend;

    public Sheep(String name, int age, Friend friend) {
        this.name = name;
        this.age = age;
        this.friend = friend;
    }

    /**
     * 使用序列化的方式實現深拷貝
     * @return
     */
    @Override
    protected Sheep clone() {

        Sheep sheep = null;
        try {

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            sheep = (Sheep) ois.readObject();

            bos.close();
            oos.close();
            bis.close();
            ois.close();

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

        return sheep;
    }
}

六、原型模式優缺點

優點:如果需要複製一個新的對象時,可以利用原型模式簡化對象的創建過程,同時也可以提高效率。

缺點:需要爲每一個類配備一個克隆方法,如果對已有的代碼類進行改造,需要修改源碼,違背了OCP原則。

 

 

 

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