菜雞與物件導向 (2): 建構式、多載
我就直接說了,有用前面的程式碼區塊的人,絕對執行不了。因為我們建立哥布林和戰士這兩張卡片的時候,根本就沒有給他們數值呀!
雖然可以先呼叫出來再賦值…
var goblin = new Card();
goblin.Name = "哥布林";
goblin.Attack = 3;
goblin.Health = 2;
/* ...其他賦值 */
這實在相當占空間,也有點奇怪。畢竟如果是阿福(狗),一出生的時候應該就確定了一些特徵才對,例如品種、血型、眼睛顏色這種。並不會出生後過一陣子,才突然決定這些東西,既然如此,我們在產生物件的時候,當然也會希望在建立的同時就先決定好一部份內容。
這時候我們就可以藉由建構式的方式,在建立物件時就進行一些我們想要的操作。
建構式
事實上,當我們呼叫 new Card()
的時候(不覺得這個 () 很有呼叫方法的感覺嗎?)我們就是正在調用 Card 的建構式。而當我們沒有特別去定義建構式的時候,就會直接使用內建的建構式去幫我們產生物件。
現在我們替 Card 新增一個建構式:
public class Card
{
public Card (string name, int attack, int health)
{
this.Name = name;
this.Attack = attack;
this.Health = health;
}
/* ... 其他屬性和方法 */
}
在 C# 的時候,建構式必須和類別同名,且不需要定義回傳類型。當我們有了建構式,剛剛的例子就可以改寫成:
var goblin = new Card(name: "哥布林", attack: 3, health: 2);
var warrior = new Card(name: "戰士" , attack: 4, health: 3);
warrior.Hit(goblin);
建構式也就是建立這個物件時執行的函式,通常會用來進行初始化,也就是做一些建立物件必要的準備。例如傳遞必要屬性或是建構需要的其他物件、或是給予私有屬性初始值等等,例如說我們的卡牌一建立,就會需要知道它的名字和戰鬥力,這樣才有卡牌的感覺,而不該像一些 壞決鬥者 邊打牌邊偷偷印卡。
註:當然有建立時執行的,也就會有消滅時執行的。請參見 解構式,由於較少用到,此處先按下不表。
多載
當然,有了建構式就會有更多問題。現在我們只有一個方法可以建立卡牌了,這無疑是相當不彈性的,例如說我希望預設的攻擊力和血量就是四呢?實際上我們經常會遇到需要用不同素材去建立一個物件的場合,這時候就必須得提到另一個要點:多載了。
多載指的就是可以有很多個同樣名字的方法,各自去接不同的參數。例如說我們的 Card 建構式就可以利用多載來改造一下:
public class Card
{
public Card (string name, int attack, int health)
{
this.Name = name;
this.Attack = attack;
this.Health = health;
}
public Card (string name)
{
this.Name = name;
this.Attack = this.Level;
this.Health = this.Level;
}
public Card ()
{
this.Name = "Noname";
this.Attack = this.Level;
this.Health = this.Level;
}
}
如此一來,我們在建立卡片的時候就能夠有更多選擇了,現在我們可以根據狀況給予需要的參數,剩下的就交給建構式去處理就好。
實務上,如果規則或是建立的步驟一致的話,為了能夠把規則集中到一個地方方便修改,並且減少多餘的程式碼。我們通常會試著讓其他的建構式去呼叫主要的建構式,在 C# 中,呼叫自己的建構式是使用 : this()
來進行的,例如:
public Card (string name, int attack, int health)
{
this.Name = name;
this.Attack = attack;
this.Health = health;
}
// 會呼叫上面那個建構式
public Card (string name) : this(name: name, attack: 5, health: 5)
{
// 呼叫完 Card(name, attack, health) 之後做的事
}
// 會呼叫上面那一個建構式
public Card () : this(name: "Noname")
{
// 呼叫完 Card(name) 之後做的事
}
如此一來只要建構的方式有變更,我們只需要集中修改第一個建構式就好了。這部份的流程也可以參照 [C#.NET] 為建構子建立正確的初始化 - 余小章 @ 大內殿堂 這篇的說明。
當然多載也不只是用在建構子,而是大多數時候都可以用的寫法。例如當你的函式雖然做同樣的事,但允許接收不同的參數來處理時,就請考慮使用多載。
例如說當你要記錄發生錯誤時的 Log ,就能允許只傳遞錯誤內容,或是傳遞錯誤內容和當時操作的參數,甚至是當下的環境資料等等。
又或者是查詢客戶資料(GetUser 之類的)的函式,提供使用 客戶代號,或是 訂單編號 等不同的方式進行查詢時,就可以考慮多載的應用。
例如說 .net 中協助類別對映的名套件 AutoMapper,在轉換類別的 Map 方法就很漂亮地使用了多載:
註:看不到圖片的可以直接看 Github
當然隨著多載的應用越來越稀鬆平常,時至今日,我們只要使用選擇性參數就可以輕鬆達到一樣的效果囉:
public Card (string name = "Noname", int attack = 5, int health = 5)
{
this.Name = name;
this.Attack = attack;
this.Health = health;
}
當有預設值的時候,該參數就會變成可選的,這時候就可以輕鬆決定要傳進來的內容了。當然,如果傳進來的並非只是數量上的差別,而是整個型別都不一樣的話,還是要回歸到多載的做法,建立兩個同名但不同傳入參數類型的方法,可讀性會比較高呦。
多載提供的好處在於:同個目標的函式可以根據傳入的參數不同做不一樣的處理。例如當我們寫了一個連線取資料的方法,可以分為
(1) 傳入連線的話,就使用連線取得資料
(2) 傳入連線字串的話,就先用連線字串開啟連線,再使用連線取得資料
等等,根據參數的場合來進行處理。
藉由傳入不同的參數類型和數量,就可以處理不同狀況的內容,既擴展了函式在使用上的彈性,同時也增加函式能派上用場的時機。
而最重要的是這將讓編寫程式的人員去思考:我設計的這個方法將能應用在什麼場景? 這將會成為一個相當優良的習慣。
那麼這次就說到這裡,我們下篇見!
本系列下一篇:菜雞與物件導向 (3): 封裝
參考資料
- Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) - 菜鳥工程師肉豬
- Object Oriented物件導向-2:建構式(Constructor)、多載(Overloading)與覆寫(Overriding) - Sian
- [C#.NET] 為建構子建立正確的初始化 - 余小章 @ 大內殿堂
- C# 解構子 Destructors - 教學筆記
- 《大話設計模式》附錄:物件導向基礎
同系列文章
- 菜雞與物件導向 (0): 前言
- 菜雞與物件導向 (1): 類別、物件
- 菜雞與物件導向 (2): 建構式、多載
- 菜雞與物件導向 (3): 封裝
- 菜雞與物件導向 (4): 繼承
- 菜雞與物件導向 (5): 多型
- 菜雞與物件導向 (6): 抽象、覆寫
- 菜雞與物件導向 (7): 介面
- 菜雞與物件導向 (8): 內聚、耦合
- 菜雞與物件導向 (9): SOLID
- 菜雞與物件導向 (10): 單一職責原則
- 菜雞與物件導向 (11): 開放封閉原則
- 菜雞與物件導向 (12): 里氏替換原則
- 菜雞與物件導向 (13): 介面隔離原則
- 菜雞與物件導向 (14): 依賴反轉原則
- 菜雞與物件導向 (15): 最少知識原則
- 菜雞與物件導向 (Ex1): 小結
其他文章
哈囉,如果你也有 LikeCoin,也覺得我的文章有幫上忙的話,還請不吝給我拍拍手呦,謝謝~ ;)