設計模式之原型模式

咱們在建立對象時,一般是經過new關鍵字來建立的。可是,思考一下,若是當前類的構造函數很複雜,每次new對象時都會消耗很是多的資源,這樣確定是不行的,耗時又費力。ide

那有沒有什麼辦法解決這種問題呢?固然有,原型模式就能夠解決這個痛點。函數

原型模式很是好理解,就是類的實例對象能夠克隆自身,產生新的實例對象,這樣就無需用new來建立。想一下,齊天大聖孫悟空是否是拔一根汗毛,就複製出了不少個如出一轍的孫悟空,道理是同樣的。(新的對象和原對象,內容相同,可是內存地址不一樣,由於是不一樣的對象。)測試

那在Java中,咱們怎麼實現原型模式呢?很是簡單,只須要原型對象實現Cloneable接口就能夠了,看代碼:(學生對象的複製,學生對象中包含他所學的學科類的對象)this

//學科類
public class Subject {
    private String name;
    private String content;

    public String getName() {
        return name;
    }

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

    public String getContent() {
        return content;
    }

    public Subject setContent(String content) {
        this.content = content;
        return this;
    }

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
//學生類
public class Student implements Cloneable {
    private int age;
    private String name;
    private Subject subject;

    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 Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //實現Cloneable接口,調用父類Object的clone方法來實現對象的拷貝
        return super.clone();
    }
}
public class ProTest {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student(18,"張三",new Subject("語文","這是語文書"));
        //經過調用s1對象的clone方法,便可建立一個新的對象s2
        Student s2 = (Student)s1.clone();

        System.out.println(s1);
        System.out.println(s2);

        s2.setAge(20);
        s2.setName("李四");
        s2.getSubject().setName("數學").setContent("這是數學書");

        System.out.println("=======");
        System.out.println(s1);
        System.out.println(s2);

    }
}

打印結果以下:.net

Student{age=18, name='張三', subject=Subject{name='語文', content='這是語文書'}}
Student{age=18, name='張三', subject=Subject{name='語文', content='這是語文書'}}
========
Student{age=18, name='張三', subject=Subject{name='數學', content='這是數學書'}}
Student{age=20, name='李四', subject=Subject{name='數學', content='這是數學書'}}

能夠發現,s2對象修改的年齡和姓名對原對象s1沒有任何影響,可是subject對象修改以後,原對象s1中的subject對象內容也被更改了,這是怎麼回事呢?其實,這是由於咱們使用的是淺拷貝。(Object對象的clone方法自己就是淺拷貝)code

淺拷貝:對值類型的成員變量進行值的複製,對引用類型的成員變量只進行引用的複製,而不復制引用所指向的對象。(嗯?這句話怎麼聽起來這麼熟悉,看下這個:爲何你們都說Java中只有值傳遞)此時,新對象裏邊的引用類型變量至關於原對象的引用類型變量的副本,他們指向的是同一個對象。對象

所以,修改了s2對象的subject對象的內容,原對象s1的subject對象內容也會跟着改變。那若是,我不想讓原對象的引用類型變量內容發生改變,應該怎麼作呢?blog

這就要用到深拷貝了,即把引用類型變量的內容也拷貝一份,這樣他們就互不影響了。通常有兩種方式來實現深拷貝:一種是讓須要拷貝的引用類型也實現Cloneable接口,而後重寫clone方法;另外一種是利用序列化。接口

clone方式:內存

仍是以上邊的例子來講。首先須要修改Subject類讓它實現Cloneable接口,重寫clone方法。而後修改Student類的clone方法,把全部引用類型的變量手動拷貝一下。

public class Subject implements Cloneable{
    private String name;
    private String content;

    public String getName() {
        return name;
    }

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

    public String getContent() {
        return content;
    }

    public Subject setContent(String content) {
        this.content = content;
        return this;
    }

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Student implements Cloneable {
    private int age;
    private String name;
    private Subject subject;

    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 Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student cloneStu = (Student)super.clone();
        //手動拷貝subject對象,而後賦值給student克隆的新對象
        cloneStu.setSubject((Subject) this.subject.clone());
        return cloneStu;
    }
}

再次運行測試類,會發現修改新對象的引用類型變量已經沒法影響原對象的引用類型變量了。

Student{age=18, name='張三', subject=Subject{name='語文', content='這是語文書'}}
Student{age=18, name='張三', subject=Subject{name='語文', content='這是語文書'}}
========
Student{age=18, name='張三', subject=Subject{name='語文', content='這是語文書'}}
Student{age=20, name='李四', subject=Subject{name='數學', content='這是數學書'}}

序列化方式

能夠發現,用clone的方式實現起來是很是簡單的。可是,考慮若是對象中的引用類型變量不少的時候,這種方式就不太方便 了,由於你要把全部的引用類型都手動clone一遍。另外,若是引用類型又嵌套了多層引用類型,那將是一場災難。這時,就能夠考慮用序列化方式。

系列化方式是經過把對象序列化成二進制流,放到內存中,而後再反序列化成爲新的Java對象,這樣就能夠保證新對象和原對象的互相獨立了。

這種方式,須要全部類都實現Serializable接口。Student類只須要添加一個系列化和反序列化的方法就能夠了。

public class Subject implements Serializable {
    private String name;
    private String content;

    public String getName() {
        return name;
    }

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

    public String getContent() {
        return content;
    }

    public Subject setContent(String content) {
        this.content = content;
        return this;
    }

    public Subject(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public Subject() {
    }

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
public class Student implements Serializable {
    private int age;
    private String name;
    private Subject subject;

    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 Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public Student(int age, String name, Subject subject) {
        this.age = age;
        this.name = name;
        this.subject = subject;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", subject=" + subject +
                '}';
    }


    //深克隆
    public Object deepClone() throws IOException, ClassNotFoundException{
        //把對象寫入到流中
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //從流中讀取
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }
}
public class ProTest {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student(18,"張三",new Subject("語文","這是語文書"));
        Student s2 = (Student)s1.deepClone();

        System.out.println(s1);
        System.out.println(s2);

        s2.setAge(20);
        s2.setName("李四");
        s2.getSubject().setName("數學").setContent("這是數學書");

        System.out.println("========");
        System.out.println(s1);
        System.out.println(s2);

    }
}

能夠看到,在測試類中經過調用deepClone自定義的深克隆方法便可。這種方式適合引用類型或者子嵌套特別多的狀況,每一個類只須要實現Serializable接口便可,Student的deepClone方法也不須要再改動。

須要注意,這種方式的深拷貝,類中引用類型變量不能用 transient 修飾。(不懂的,自行搜索transient關鍵字,簡單說就是用transient修飾的變量默認不會被序列化)

相關文章
相關標籤/搜索