iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

講polymorphism多型,大多數是從「拆字算命」(還好不用紫微斗數)起頭。吾從眾:


名詞解釋

  • polymorphism一詞,是polymorphism兩字的組合(註1)。
    • poly字源來自希臘的polýs,意思是many」。
    • morph亦沿自希臘的morphḗ,意思是shape形狀、形態」。
    • 兩個合起來,就是many shapes,就是多種形狀或多個型態之意。中文通常譯為「多型」或「多形」,多型較普及,所以下面都講「多型」。至於具體意義,容後詳述。
    • 以早晚(或動靜)區分,多型可以分為compile-time polymorphism和run-time polymorphism兩種:
      • compile-time多型又稱static polymorphism或early binding,一般指涉的就是本系列Day 21: Overriding文中提及的overloading(註2)。不過本章要說的多型,並非是overloading。
      • run-time多型又稱dynamic polymorphism或late binding,本章談的多型就是這種。

多型行為

  • 既稱run-time,就表示一定在程式的「執行時期」才動態決定。
  • 繼承是多型的基礎,沒有繼承就不可能產生多型。
  • 在一些靜態型別(statically-typed)的物件導向程式語言,例如Java和C#,要產生多型的效果,物件必須宣告成其父類別的型別,實際上卻new一個子類別,例如Parent obj = new Child();。這時的Parent可以說是「形式類別」,Child則是「實際類別」。
  • 呼叫obj的某個方法doSomething()時(當然這個方法在形式類別和實際類別都要存在才行),實際執行的會是「實際類別」也就是子類別的doSomething(),而不是「形式類別」(父類別)的doSomething()。

C#版的多型

  • 先看一下C#版的多型寫法:
    using System;
    namespace MyApplication
    {
      class Tree  // Base class (parent)
      {
        public virtual void bloom()   // 要加virtual
        {
          Console.WriteLine("I am a virtual Tree, so I don't know whether I will bloom.");
        }
      }
    
      class Hardwood : Tree  // Derived class (child)
      {
        public override void bloom()   // 要加override
        {
          Console.WriteLine("I am a Hardwood, so I bloom every year.");
        }
        public void doExtraWorks()   // 子類別擴充的方法
        {
            Console.WriteLine("A Hardwood is doing extra works.");
        }
      }
    
      class Conifer : Tree  // Derived class (child)
      {
        public override void bloom()   // 要加override
        {
          Console.WriteLine("I am a Conifer, so I DON'T bloom.");
        }
        public void doExtraWorks()   // 子類別擴充的方法
        {
            Console.WriteLine("A Conifer is doing extra works.");
        }
      }
    
      class VenusianTree : Tree   // 金星上的樹
      {
        public override void bloom()   // 要加override
        {
          Console.WriteLine("Being a Venusian tree, I bloom once every 100 years.");
        }
        public void doExtraWorks()   // 子類別擴充的方法
        {
            Console.WriteLine("A VenusianTree is doing extra works.");
        }
      }
    
      class MartianTree : Tree   // 火星上的樹
      {
        public override void bloom()   // 要加override
        {
          Console.WriteLine("Being a Martian tree, I don't have the concept of blooming.");
        }
        public void doExtraWorks()   // 子類別擴充的方法
        {
            Console.WriteLine("A MartianTree is doing extra works.");
        }
      }
    
      class SaturnianTree : Tree   // 土星上的樹
      {
        public override void bloom()   // 要加override
        {
          Console.WriteLine("Being a Saturnian tree, I don't have any shape(ghostlymorphism).");
        }
        public void doExtraWorks()   // 子類別擴充的方法
        {
            Console.WriteLine("A SaturnianTree is doing extra works.");
        }
      }
    
      class Program
      {
        static void Main(string[] args)
        {
          Tree tree = new Tree();                      // Create a Tree object
          Tree hardwood = new Hardwood();              // Create a Hardwood object
          Tree conifer = new Conifer();                // Create a Conifer object
          Tree venusianTree = new VenusianTree();      // Create a VenusianTree object
          Tree martianTree = new MartianTree();        // Create a MartianTree object
          Tree saturnianTree = new SaturnianTree();    // Create a SaturnianTree object
    
          tree.bloom();
          Tree[] trees = {hardwood, conifer, venusianTree, martianTree, saturnianTree};
          Random rnd = new Random();
          int thisTree = rnd.Next(0, 5);
          trees[thisTree].bloom();
          // 由於Tree類別並無doExtraWorks(),下列這行執行時會報錯。
          // trees[thisTree].doExtraWorks();
        }
      }
    }
    
  • 輸出如下:
    https://ithelp.ithome.com.tw/upload/images/20221012/201484852h0dSxe8pH.png
  • 程式說明:
    • 主程式Main()隨機呼叫五個tree物件的bloom()方法,這種方式的呼叫,顯然是在執行時期動態決定,編譯時期不可能未卜先知。事實上,即使直接寫saturnianTree.bloom();,也是在執行時期才決定呼叫父子兩類別中的哪一個bloom()。筆者使用隨機寫法,只是故意強調而已。
    • 注意Tree hardwood = new Hardwood();這個寫法。hardwood物件宣告為Tree型別,實際new的卻是Tree的子類別Hardwood
    • 因為hardwood物件的「型別」是Tree,所以它只能使用Tree所提供的屬性和方法。下面的寫法是錯的:
        class Program
        {
          static void Main(string[] args)
          {
            Tree tree = new Tree();                      // Create a Tree object
            Tree hardwood = new Hardwood();              // Create a Hardwood object
            Tree conifer = new Conifer();                // Create a Conifer object
            Tree venusianTreeTree = new VenusianTree();  // Create a VenusianTree object
            Tree martianTree = new MartianTree();        // Create a MartianTree object
            Tree saturnianTree = new SaturnianTree();    // Create a SaturnianTree object
      
            tree.bloom();
            Tree[] trees = {hardwood, conifer, venusianTreeTree, martianTree, saturnianTree};
            Random rnd = new Random();
            int thisTree = rnd.Next(0, 5);
            // 由於Tree類別並無doExtraWorks(),下列這行執行時會報錯。
            trees[thisTree].doExtraWorks();
          }
        }
      
    • 在企圖呼叫doExtraWorks()時出錯了,原因是父類別Tree並未定義這個方法:
      https://ithelp.ithome.com.tw/upload/images/20221012/20148485faPIFDx0GF.png
    • 從這個角度,多型也可以解釋成:由父類別事先規定子孫輩的所有行為(方法),亦即可以做哪些事。子孫對容許的行為,倒不必墨守成規,可以酌情變更處理方式(即修改方法內容)。但絕不能超越家規,去做父祖沒有允許的事(即執行自己擴充的方法)。違反祖宗遺訓是為不孝,可不容於世。

先小結一下

  • 宣告父類別的型別,實際指向子類別的物件。
  • 該物件只能存取父類別的屬性,以及呼叫父類別定義的方法。子類別自己擴充的方法則無法使用。
  • 如果子類別中覆寫(override)了父類別的方法,那麼在呼叫這個方法的時候,將會呼叫子類別(實際類別)中的方法,而不是父類別(形式類別)的方法。這種行為,即是多型,也可稱作「動態連接」或「動態呼叫」。

今天算是對多型的簡單介紹,明天有無辦法講得更加透徹,得看筆者的時間和精力了。


註1: 開個玩笑:

  • Constant Case: POLY_MORPHISM
  • Pascal Case: PolyMorphism
  • Camel Case: polyMorphism
  • Snake Case: poly_morphism

註2: Overloading可以細分為function overloading和operator overloading兩種。

  • 之前介紹的是function overloading(同名異式)。
  • 至於operator overloading,是將operators(運算子)的行為改變。例如Python的+運算子,可以用於int / float的相加,也可以用於字串的「連接」,同一+號,可用於不同型別資料,這就是operator overloading。不過這是Python內建的overloading,使用者無法自訂。

上一篇
繼承vs組合
下一篇
「形式型別」故意和「實際型別」不同,作用何在?
系列文
Oops! OOPP: An Introduction to Object-Oriented Programming in Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言