物件導向基礎(1)--繼承 多型 重構

fan_rockrock發表於2014-03-14
好久沒有寫部落格了,最近一直忙著複習考研,前幾天聽了張老師講的設計模式的這門課,很有感觸,就簡單地整理一下學的東西。。。


這節課講的是物件導向的幾個基本概念,老師舉了個例子把物件導向的很多東西都連線起來了:動物比賽,每個不同動物都有名字,能發出叫聲等。。。
一.繼承.
             第一、子類擁有父類非private的屬性和功能;
             第二、子類具有自己的屬性和功能,即子類可以擴充套件父類沒有的屬性和功能;
             第三、子類還可以以自己的方式實現父類的功能(方法重寫)
                     除了private和public外,還有protected表示繼承時子類可以對父類有完全訪問權。 也就是說,protected修飾的類成員,對子類公開,對其它類不公開。
     來看個例子:

貓類:         

class Cat
    {
        private int shoutNum = 3;
        public int ShoutNum
        {
            get
            {
                return shoutNum;
            }
            set
            {
                if (value < 10)
                {
                    shoutNum = value;
                }
                else
                {
                    shoutNum = 10;
                }
            }
        }
        private string name = "";
        public Cat(string name)
        {
            this.name = name;
        }
        public Cat()
        {
            this.name = "無名";
        }
        public string Shout()
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
            {
                result += "喵 ";
            }
            return "我的名字叫" + name + " " + result;
        }
    }
狗類:

 class Dog
    {
        private int shoutNum = 3;
        public int ShoutNum 
        { 
            get
            {
                return shoutNum;
            }
            set 
            {
                if (value < 10)
                {
                    shoutNum = value;
                }
                else
                {
                    shoutNum = 10;
                }
            }
        }
        private string name = "";
        public Dog(string name)
        {
            this.name = name;
        }
        public Dog()
        {
            this.name = "無名";
        }
        public string Shout()
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
            {
                result += "汪 ";
            }
            return "我的名字叫" + name + " " + result;
        }
    }
我們發現這兩個類有很多相同的地方,於是我們把他們相同的地方都弄出來組成一個Animal類:

 class Animal
    {
        protected string name = "";//用了protected
        public Animal(string name)
        {
            this.name = name;
        }
        public Animal()
        {
            this.name = "無名";
        }

        protected int shoutNum = 3;
        public int ShoutNum
        {
            get
            {
                return shoutNum;
            }
            set
            {
                shoutNum = value;
            }
        }
    }
於是貓狗類就可以這樣寫:

    class Cat : Animal
     {
        public Cat() : base()
        { }

        public Cat(string name) : base(name)
        { }

        public  string Shout()
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
                result += "喵 ";

            return "我的名字叫" + name + " " + result;
        }
     }
    class Dog : Animal
    {
        public Dog() : base()
        { }

        public Dog(string name) : base(name)
        { }

        public  string Shout()
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
                result += "汪 ";

            return "我的名字叫" + name + " " + result;
        }
    }

用UML類圖表示:

繼承優點:繼承使得所有子類公共的部分都放在了父類,使得程式碼得到了共享,這就避免了重複,另外,繼承可使得修改或擴充套件繼承而來的實現都較為容易。

        缺點:父類變,則子類不得不變。繼承會破壞包裝,父類實現細節暴露給子類,這其實是增大了兩個類之間的耦合性。


雖然採用了繼承,但子類還是顯得有些複雜,假如現在又有很多牛,羊什麼的來了,那麼這個Shout()函式就會有很多種,顯得有點複雜,怎樣解決???

二.多型.

          1.概念:表示不同的物件可以執行相同的動作,但要通過它們自己的實現程式碼來執行。

          2.舉例:

                      有一位父親是有名的藝術家, 六個兒子長大成人, 模仿父親惟妙惟肖。 有一天,父親突然發燒,上不了臺,但是票已經賣了,退票影響聲譽。於是決定讓兒子化了                          妝代父親表演。

                  有以下特點:

                  第一,子類以父類的身份出現
                          兒子代表父親表演,化妝後就以父親身份出現了。
                  第二,子類在工作時以自己的方式來實現
                          兒子模仿得再好,那也是模仿,兒子只能用自己方式表演父親的作品。
                  第三,子類以父親的身份出現時,子類特有的屬性和方法不可以使用
                          兒子有自己的絕活也不能使用。

          3.虛方法和方法重寫
                        虛方法:為了使子類的例項完全接替來自父類的類成員,父類必須將該成員宣告為虛擬的。這是通過在該成員的返回型別之前新增virtual關鍵字來實現的。
                        方法重寫:子類可以選擇使用override關鍵字,將父類實現替換為它自己的實現,這就是方法重寫override      

    class Animal
    {
        ……
        public virtual string Shout()//虛方法,沒有實現具體功能
        {
            return "";
        }
    }

        class Cat : Animal//貓類
    {
        public Cat() : base()
        { }

        public Cat(string name) : base(name)
        { }

        public override string Shout()//方法重寫
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
                result += "喵 ";

            return "我的名字叫" + name + " " + result;
        }
    }

        class Dog : Animal//狗類
    {
        public Dog() : base()
        { }

        public Dog(string name) : base(name)
        { }

        public override string Shout()//方法重寫
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
                result += "汪 ";

            return "我的名字叫" + name + " " + result;
        }
    }
下面看怎麼例項化來運用這些類的:

        private Animal[] arrayAnimal;
        private void btnRigister_Click(object sender, EventArgs e)
        {
            arrayAnimal = new Animal[5];
            arrayAnimal[0] = new Cat("小花");
            arrayAnimal[1] = new Dog("阿毛");
            arrayAnimal[2] = new Dog("小黑");
            arrayAnimal[3] = new Cat("嬌嬌");
            arrayAnimal[4] = new Cat(“小咪");
        }
        private void btnMatch_Click(object sender, EventArgs e)
        {
            foreach (Animal item in arrayAnimal)
            {
                MessageBox.Show(item.Shout());
            }
        }
執行結果:


來分析一下多型在記憶體的情況:

      多型的原理是當方法被呼叫時,無論物件是否被轉換為其父類,都只有位於物件繼承鏈最末端的方法實現會被呼叫。也就是說,虛方法是按照其執行時型別而非編譯時型別進行動態繫結呼叫的。


當mybc呼叫print()函式時,首先找到父類的Print,父類的print指向子類的print,最終呼叫的是子類的print().


所以現在來了牛羊,我們只需要這樣定義類:

   class Cattle : Animal
    {
        public Cattle() : base()
        { }

        public Cattle(string name) : base(name)
        { }

        public override string Shout()
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
                result += “哞";

            return "我的名字叫" + name + " " + result;
        }
    }
    class Sheep : Animal
    {
        public Sheep() : base()
        { }

        public Sheep(string name) : base(name)
        { }

        public override string Shout()
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
                result += “咩 ";

            return "我的名字叫" + name + " " + result;
        }
    }

我們發現雖然這樣做了,但還是沒有減少子類程式碼的複雜度,Shout()函式還在子類裡,所以我們希望把Shout()函式弄到父類中去,從而引入重構的概念!

三.重構.

         1.概念:

                   兩個互為兄弟的子類程式碼類似,並非完全相同首先採用Extract Method(提煉函式)區分相同部分和差異部分程式碼,構成單獨函式然後再對提煉的程式碼使用Pull                               UpMethod(方法上移),將它移入父類

         2.對上述Animal進行改進,把子類中的shout()函式(相同部分)放到父類中,在父類中增加一個虛方法(不同部分,需子類實現)      

    class Animal
    {
        …… 
        public string Shout()
        {
            string result = "";
            for (int i = 0; i < shoutNum; i++)
                result += getShoutSound() + ",";

            return "我的名字叫" + name + " " + result;
        }

        protected virtual string getShoutSound()
        {
            return "";
        }
    }
    
    class Cat : Animal
    {
        public Cat() : base()
        { }

        public Cat(string name):base(name)
        {
        }

        protected override string getShoutSound()
        {
            return "喵";
        }
    }
        class Dog : Animal
    {
        public Dog() : base()
        { }

        public Dog(string name):base(name)
        {
        }

        protected override string getShoutSound()
        {
            return "汪";
        }
    }
這樣一來我們的目的就達成了,,由於篇幅太長,分兩篇寫的,請看下篇!

相關文章