看完這篇原型設計模式,還不會,請你吃瓜

realyrare發表於2023-02-20

概述

使用原型例項指定建立物件的種類,並且透過複製這些原型建立新的物件。

在軟體系統開發中,有時候會遇到這樣的情況:我們需要用到多個相同例項,最簡單直接的方法是透過多次呼叫new方法來建立相同的例項。

student s=new student();
student s1=new student();
student s2=new student();

但是有一個問題,如果我用要使用的例項建立起來十分耗費資源,或者建立起來步驟比較繁瑣,上邊的程式碼缺點就暴露出來了:耗費資源,每次建立例項都要重複繁瑣的建立過程。原始模式可以很好地解決這個問題,使用原型模式我們不需要每次都new一個新的例項,而是透過複製原有的物件來完成建立,這樣我們就不需要在記憶體中建立多個物件,也不需要重複複雜的建立過程了。下邊以克隆學生為例解釋原型模式的用法,程式碼非常簡單。

C#透過this.MemberwiseClone()實現原型模式

    /// <summary>
    /// //原型抽象類
    /// </summary>
    public abstract class StudentPrototype
    {
        public string  Name { get;  }
        public StudentPrototype(string name)
        {
            Name=name;
        }
        public  abstract StudentPrototype Clone();
    }
    /// <summary>
    /// 學生類繼承原型抽象類 並重寫Clone();
    /// </summary>
    public class Student : StudentPrototype
    {
        public Student(string name) : base(name)
        {
        }

        public override StudentPrototype Clone()
        {
            //淺複製
            //值型別成員:全都複製一份,並且搞一份新的。
            //引用型別:只是複製其引用,並不複製其物件。
            return (StudentPrototype)this.MemberwiseClone();
        }
    }
Console.WriteLine("原型設計模式");
Student student=new Student("mhg");
Student student1=(Student)student.Clone();
Console.WriteLine(student.GetHashCode());
Console.WriteLine(student1.GetHashCode());
Console.WriteLine(student1.Name);

結論:實現該原型模式,第一需要定義一個抽象類,定義一個抽象方法;第二寫一個類繼承該抽象類。重寫抽象方法即可。重寫抽象方法的邏輯使用this.MemberwiseClone();

C#自己繼承ICloneable實現原型模式

  public class Teacher:ICloneable
    {
        public Teacher(string name)
        {
            Name=name;
        }
        public string Name { get; }

        public object Clone()
        {
           return this.MemberwiseClone();   
        }
    }
Console.WriteLine("C#自己繼承ICloneable");
Teacher teacher=new Teacher("mhg2");
Teacher teacher2=(Teacher)teacher.Clone();

  Console.WriteLine(teacher.GetHashCode());
  Console.WriteLine(teacher2.GetHashCode());
  Console.WriteLine(teacher2.Name);

結論:定義一個類繼承ICloneable,然後使用this.MemberwiseClone()實現,這種方式更簡單。

這裡需要注意一點:透過this.MemberWiseClone()獲取一個物件的例項屬於淺複製,對例項的簡單型別屬性進行全值複製(包含string型別),對複雜型別屬性只複製了引用。

下面我們們驗證一下淺複製確實只對值型別成員全部複製了一份,搞成了一份新的,對於引用型別,只是複製了其引用,並不複製其物件。

我們還是繼續用Teacher這個類,在這個類裡面增加一個引用型別MyStudent,我們上程式碼。

    public class Teacher : ICloneable
    {
        public string? Name { get; set; }
        public MyStudent? MyStudent { get; set; }
        public object Clone()
        {
            return this.MemberwiseClone();
        }
        public void Show()
        {
            Console.WriteLine($"Teacher:{Name}");
            Console.WriteLine($"MyStudent name:{MyStudent.Name}");
            Console.WriteLine($"MyStudent Age:{MyStudent.Age}");
        }
    }
   
    public class MyStudent
    {
        public string Name { get; set; }
        public string  Age { get; set; }
    }

看下執行結果

透過執行克隆了一份新物件,修改了Teacher.Mystudent.Name和Teacher.Mystudent.Age的值,其teacher物件Mystudent.Name和MyStudent.Age值也會發生變化,而修改了Teacher2.Name的值,其teacher物件的name卻沒有發生變化。也就驗證我們上面所說的,原型淺複製關於值型別全部複製一份,對於引用只複製其引用,這點特別重要,很多人搞不明白,多動手實踐一下。

那如果就上面的問題而言,我們現在既想對原型裡面的值型別複製一份新的,也想把引用型別複製一份新的物件,並不僅僅只是再複製其引用,該怎麼實現呢?

透過原型偽深複製實現

    public class Teacher : ICloneable
    {
        public string? Name { get; set; }
        public MyStudent? MyStudent { get; set; }
        public Teacher()
        {
            MyStudent=new MyStudent();    
        }
        private Teacher(MyStudent myStudent)
        {
            MyStudent=(MyStudent)myStudent.Clone();
        }
        public object Clone()
        {            
            //在建立新物件的時候把工作經驗這個引用型別也複製一份
            Teacher teacher1 = new Teacher(MyStudent)
            {
                Name = Name
            };
            return teacher1;

            //如果依然呼叫this.MemberwiseClone();引用型別,就永遠不可能被複制一份新的
            //return this.MemberwiseClone(); //這種寫法只能複製值型別

        }
        public void Show(string objectName)
        {
            Console.WriteLine($"-------------{objectName}-start----------------");
            Console.WriteLine($"{objectName}:{Name}");
            Console.WriteLine($"MyStudent-name:{MyStudent.Name}");
            Console.WriteLine($"MyStudent-Age:{MyStudent.Age}");
            Console.WriteLine($"-------------{objectName}-end----------------\r\n");
        }
    }
   
    public class MyStudent:ICloneable
    {
        public string Name { get; set; }
        public string  Age { get; set; }

        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }

來看看執行結果

透過上述程式碼執行可以看出,teacher1、teacher2、teacher3幾個物件的建立...最後不僅把值型別全部複製了一份新的,引用型別也複製了一份物件,不再是複製其引用了。

目前這種原型建立還只是偽深複製,如果在MyStudent類中在出現一個引用型別,那麼就需要使用遞迴。這種方式顯而易見是有問題的,如果要真正的實現深複製,需要透過反射和序列化來實現.

總結

上述案例我們分別講了原型淺複製,原型偽深複製,如何實現真正的深複製,其實也很簡單,這次就不再往下寫了,文章寫短了沒人看,寫長了更沒人看!關於案例中的其他問題,有疑問,歡迎交流!

 

相關文章