1. 程式人生 > >設計模式-原型模式(Prototype)

設計模式-原型模式(Prototype)

概述

  • 定義 : 指原型例項指定建立物件的種類, 並且通過拷貝這些原型建立新的物件
  • 不需要知道任何建立細節, 不呼叫建構函式
  • 型別 : 建立型

適用場景

  • 類初始化消耗較多資源
  • new產生一個物件需要非常繁瑣的過程(資料準備, 訪問許可權等)
  • 建構函式比較複雜
  • 迴圈體中生產大量物件時

優點

  • 原型模式在效能上比直接new一個物件效能高
  • 簡化建立過程

缺點

  • 必須配備克隆方法
  • 對克隆複雜物件或克隆出的物件進行復雜改造時, 容易引入風險
  • 深克隆, 淺克隆要運用得當

模式角色

  • Prototype : 宣告一個克隆自身的介面, 需要注意的是, 在Java中Object類中存在了clone方法, 所以這個角色就相當於Object類的角色了, 使用時不需要再宣告介面了, 直接覆蓋Object的clone方法即可
  • ConcretePrototype : 實現一個克隆自身的操作
  • Client : 即客戶端, 讓一個原型克隆自身從而建立一個新的物件

程式碼實現

  • java中要實現clone, 需要實現Cloneable介面, 這是一個標記介面
  • 重寫Object類的clone方法
/**
 * 原型模式
 *
 * @author 七夜雪
 * @create 2018-11-23 10:51
 */
public class Person implements Cloneable { private String name; private Integer age; private String address; private String company; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString
() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + ", company='" + company + '\'' + ", hashCode='" + hashCode() + '\'' + '}'; } // ..............Setter, Getter方法省略................. }

測試程式碼:

    public static void main(String[] args) throws Exception {
        // 原型
        Person prototype = new Person();
        prototype.setName("蕭憶情");
        prototype.setAddress("洛陽");
        prototype.setCompany("聽雪樓");
        prototype.setAge(18);
        Person clone = (Person)prototype.clone();
        System.out.println("原型:" + prototype);
        System.out.println("拷貝:" + clone);
    }

輸出結果:

原型:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', hashCode='356573597'}
拷貝:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', hashCode='1735600054'}
  • 對於都是簡單屬性的類, 這種操作並沒有什麼問題, 如果屬性中存在複雜物件, 就可能會存在問題, 現在我們在Person類中增加一個Date型別的birthday屬性, 然後修改一下測試方法試一下:
    public static void main(String[] args) throws Exception {
        // 原型
        Person prototype = new Person();
        prototype.setName("蕭憶情");
        prototype.setAddress("洛陽");
        prototype.setCompany("聽雪樓");
        prototype.setAge(18);
        prototype.setBirthday(new Date());
        Person clone = (Person)prototype.clone();
        System.out.println("原型:" + prototype);
        System.out.println("拷貝:" + clone);
        // 這裡如果直接呼叫clone.setBirthday()方法是並沒有問題的, 因為prototype和clone是兩個不同的物件了,直接呼叫set方法的話, 只是會把clone物件的Birthday屬性指向一個新的Date物件
        clone.getBirthday().setTime(211111212121L);
        System.out.println("--------------更新了clone物件的birthday屬性---------------");
        System.out.println("原型:" + prototype);
        System.out.println("拷貝:" + clone);
    }

輸出結果:

原型:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Fri Nov 23 11:15:11 CST 2018', hashCode='1836019240'}
拷貝:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Fri Nov 23 11:15:11 CST 2018', hashCode='325040804'}
--------------更新了clone物件的birthday屬性---------------
原型:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Thu Sep 09 18:00:12 CST 1976', hashCode='1836019240'}
拷貝:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Thu Sep 09 18:00:12 CST 1976', hashCode='325040804'}

從上面的輸出結果可以看出, 這種情況下prototype和clone物件的birthday屬性還是同一個物件, 這種就是淺克隆, 又叫淺拷貝, 這種情況就需要改寫clone方法, 對birthday屬性再進行一次clone操作, 修改後的clone方法如下:

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person clone = (Person) super.clone();
        clone.birthday = (Date) this.birthday.clone();
        return clone;
    }

修改完之後, 再執行上面的測試用例, 就會發現結果已經正確了:

原型:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Fri Nov 23 11:20:51 CST 2018', hashCode='1836019240'}
拷貝:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Fri Nov 23 11:20:51 CST 2018', hashCode='325040804'}
--------------更新了clone物件的birthday屬性---------------
原型:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Fri Nov 23 11:20:51 CST 2018', hashCode='1836019240'}
拷貝:Person{name='蕭憶情', age=18, address='洛陽', company='聽雪樓', birthday='Thu Sep 09 18:00:12 CST 1976', hashCode='325040804'}