分類 Object-Oriented 中的文章

菜雞與物件導向 (6): 抽象、覆寫

備註:這邊的抽象是指程式語言中的抽象類別,而非抽象化

抽象的概念很直接,請回想一下前面的例子就可以了:

當我們在用卡牌的例子時,雖然怪獸卡跟魔法卡都繼承了 Card 這個類別,但是我們仍然能
new Card() 來建立一張新卡牌,那…怪怪的吧,這張卡牌到底是什麼呀,空白的卡片嗎?

又或是動物的例子,我們的狗跟貓都繼承了哺乳類,那我們能實例化一個哺乳類嗎?我們的狗跟鳥都是動物,那我們能實例化一個動物嗎?

小明跟小華都繼承了工程師,那我們能 new 一個工程師嗎…?

有些類別就是這樣,它們負責定義共通的那些特性,然而它們本身不應該被實體化成一個物件,這種類別我們就應該把它們標記為抽象類別

抽象類別在 C# 裡用 abstract 這個修飾詞來表示,可以加在類別或方法上。例如 abstract class Animal 就代表動物這個類別是個抽象類別,它不能被實例化。

而當加在方法上時,例如 public abstract void Eat() 就是代表這個進食的方法無法被叫用,只能由繼承者去重新定義這個方法。

abstract class Animal
{
    public string color { get; set; }
    public abstract void Eat();
}

那麼繼承者們,也就是衍生類別如何去重新定義父類別的方法呢?

所謂「欲戴王冠,必 Override」,這時候就必須使用覆寫(override

覆寫是指對於像是前述的抽象方法時,在同名的方法前加上 override 關鍵字就可以讓程式知道你要覆寫這個方法(你不覆寫的話,編譯器還會生氣)。

例如前述的 Eat,狗就可以用 public override void Eat() 的方式去覆寫吃東西這個方法:

public class Dog : Animal
{
    public string color { get; set; } = "Black";
    public override void Eat()
    {
        /* 嚼嚼嚼 */
    }
}
……

閱讀全文


菜雞與物件導向 (5): 多型

多型算是比較三特性之中給人感覺比較溫和的了,不如說只要有了繼承,那麼多型的到來就是必然的。多型的定義是:不同的物件能夠做出一樣的行為,但必須由他們自己的程式碼來實作。

白話一點說就是:一樣的事,不同做法

多型相對是比較好理解的,畢竟每個人做同一件事的方法本來就不太一樣。例如一樣是泡奶茶,英國就正常地泡,美國就會用微波爐;一樣是肉粽,有些人就是比較愛吃油飯;到了程式的世界裡也是,即使繼承了同一個物件,實現這個行為的方式也可以不同。

在此可以先推菜鳥工程師肉豬的這篇 Java 什麼是多載(Overload), 覆寫(Override), 多型(Polymorphism) 中的說明。其中可以從例子看到儘管狗跟鳥都是繼承自動物這個類別,但對於「移動」這個方法,他們實作的方式並不一樣。這就是多型的範例。

我個人更喜歡用職位的方式去理解多型。

被繼承者就像是該職位的工作,例如說:Programmer 要會寫 C#、要會寫 SQL、要會 VB…等等,於是老闆就貼出了徵人啟示,要求新來的員工必須要有 Programmer 寫的能力:

public class Programmer
{
    public virtual string WriteCSharp()
    {
        /* 努力地寫 C# */
    }
    public virtual string WriteSQL()
    {
        /* 努力地寫 SQL */
    }
    public virtual string WriteVB()
    {
        /* 努力地寫 VB */
    }
}
……

閱讀全文


菜雞與物件導向 (4): 繼承

接著要介紹的是繼承 aka 物件導向三大特性之王 aka 濫用榜 Ko.1 ,繼承的強大幾乎和它的惡名一樣可怕,給一個從聊聊程式的這篇 [心得整理] c# 物件導向程式 - 2.封裝、繼承、多型的三大特性 摘過來的例子就可以略知一二了:

什麼也不做,僅僅只是繼承而已,就取得了繼承對象(C# 中稱為基底類別)近乎全部的內容,真是太可怕了。在 C# 中,繼承可以取得基底類別除了 Private 以外所有的內容,例如 Protected 更是表明就是只給繼承使用的。

由此可見,在減少重複程式碼的路上,繼承無疑達到了全新的高度。

那麼繼承代表的是什麼意思呢?大多的網站都能直接說明:繼承是一種「is-a」的關係。當你能說出A是一個B的時候,就代表你認為A可以繼承自B

最直覺的繼承例子就是物種的分類。舉例來說,狗跟貓都是哺乳類,因此他們都可以繼承到一些哺乳類共通的特徵(例如哺乳、用肺呼吸)。藉由繼承,我們可以把這些哺乳類共有的特徵全部放在哺乳類這個物件,再由狗和貓分別去繼承哺乳類,藉此讓他們都能得到哺乳類的特徵,再進一步發展出自己的特徵和行為,甚至重新定義基底類別的方法為自己所用。因此,像大話設計模式就將繼承說明如:繼承者是對於被繼承者的一種特殊化。

如此一來,當我們需要修改哺乳類的定義的時候,只需要修改一個地方,而繼承了哺乳類的這些物件(C# 中稱為衍生類別)全都能夠一起修改到,大大地減少了跑來跑去修改的次數,也讓程式碼的重複大幅度地減少

然而也因為如此,繼承最大的惡名出現了:繼承享受了取用基底類別內容的好處,卻也必須背負牽一髮動全身的風險

……

閱讀全文


菜雞與物件導向 (3): 封裝

封裝包含了兩個重要的觀念:

  • 控制物件和外部進行互動的出入口
  • 隱藏物件內部的細節資訊

強者我同事整理的文章裡的例子就舉得不錯:當你按下鍵盤的A鍵,螢幕隨即出現了A,你不必知道中間發生了什麼事,你只需要知道怎麼操作和最後得到什麼就可以了。

其中鍵盤提供的按鍵,就是我們對電腦進行互動的出入口;而電腦實際上做了什麼事情,也被隱藏了起來,讓我們只需要關注結果就好。

此外我也看到過販賣機的例子,當你去販賣機買飲料,你也不需要知道裡面的構造,只要知道你選了飲料投了錢,飲料就會跑出來就行

從上面的兩個例子,相信大家已經掌握到封裝的概念了:將物件視作一個整體,把內部的實作內容隱藏起來,讓使用者只需要知道怎麼使用這個物件即可。(相似的思路,我們後續的介面會再提到)

如果封裝做得夠好,除了可以將程式碼整理得井井有條以外,也能讓物件內部的修改不會直接影響到使用物件的地方,達成了降耦合的目標

並且也能讓物件的使用者直覺地知道如何使用物件提供的方法,如此使用者就可以專注在更高層次的抽象,而不用被物件內部的細節所干擾。

最後,從上面的敘述中我們可以察覺到要實現封裝,最重要的就是:對外的開放程度(存取範圍)的控制。或是套一句前輩的說法:給程式碼隱私的空間

補充:如果想問「什麼是耦合?」的朋友,建議可以看看這篇:實務上的高內聚與低耦合

或是參照本系列後續的 內聚與耦合

存取範圍與存取子

先讓我們從存取範圍開始說起吧,因為我個人慣用的是 C#,因此就介紹一下 C# 是怎麼控制存取範圍的。

在 C# 之中,類別裡控制可見度是使用修飾子來定義存取範圍,也就是當我們替類別宣告欄位時常看到的 PublicPrivate

  • Public: 這是公開的,所有人都看得到
  • Private: 這是私有的,只有自己看得到

除了最常用的這兩個以外,還有其他的修飾子可以先知道一下:

  • Protected: 這是受到保護的,只有自己和繼承的孩子們看得到
  • internal: 這是內部的,只有身為同一個組件的朋友們看得到
  • Protected internal:組合上面兩個,也就是可以給同個組件的朋友們,或是其他組件繼承的孩子們看見

接下來的部分會以最常見的 PublicPrivate 來繼續說明,對存取範圍的這些修飾子有興趣的朋友,可以參照 存取範圍層級 的說明。

現在我們已經知道了有哪些修飾子可以用來控制存取範圍,但為什麼我們會需要宣告存取範圍的大小呢?其根本是為了將控制權掌握在物件本身

就像大話設計模式比喻的:物件就像間房子,我們不希望被看光光,可以看見的 Public 就像門和窗,而不該看見的 Private 則是用牆壁隱藏起來,而對於這間房子而言,門窗是可以控制的。

……

閱讀全文


菜雞與物件導向 (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);

建構式也就是建立這個物件時執行的函式,通常會用來進行初始化,也就是做一些建立物件必要的準備。例如傳遞必要屬性或是建構需要的其他物件、或是給予私有屬性初始值等等,例如說我們的卡牌一建立,就會需要知道它的名字和戰鬥力,這樣才有卡牌的感覺,而不該像一些 壞決鬥者 邊打牌邊偷偷印卡。

……

閱讀全文


菜雞與物件導向 (1): 類別、物件

直覺上你當然知道什麼是物件;物件就在你的身邊。

汽車、iPhone、收音機、吐司機、廚房用具等等,你說得出來的都是。

  ——《深入淺出學會編寫程式》

什麼是物件?一切都是物件。

物件導向試圖讓抽象的程式碼,更貼近於我們的實際生活,其認為一切是由各式各樣的人事物互動所組成的,因此有了物件這個共通、最基本的概念。

假設現實世界存在一頭狗,叫做阿福。而我們想要在虛擬世界裡表達「有一隻叫做阿福的狗」這件事

這時候就要在系統裡有一個能代表「阿福(狗)」的東西存在,也就是「阿福」這個物件。

關於「把現實世界的物件,抽象化成程式世界裡的物件」的邏輯,可以參考 一個語言如果不改變你的思考方式,就不值得學?談程式語言的本質 這篇,尤其是選擇保留哪些資訊的部份我認為描述得很好。

現在我們知道,物件就是用來在虛擬世界中代表「某個特定的東西」,例如說叫做阿福的狗就是一個物件,阿福今天晚上要吃的飼料罐也是一個物件。

理解物件的概念是相當直覺且迅速的,畢竟你我身邊有著數不清的東西,它們都是一件一件的物件,但這樣的理解還不夠明確。

就像前文所引的 談程式語言的本質 文中所提到的,在抽象化的同時我們必然要選擇保留哪些資訊。

例如說阿福這隻狗,是一個物件;飼料罐也是一個物件

而這些物件之間還會彼此互動,例如說阿福是一隻狗,而我們觀察到狗都有吃東西這個動作,例如「阿福吃了飼料」

同時物件也會有一些專屬於它的特徵,例如說阿福是黑色的,我們就知道狗有毛色的差別。

那麼我們要怎麼表達「阿福」作為一隻「狗」擁有的那些動作和特徵呢?狗的毛色?狗可以吃飼料?

我們需要選擇怎麼去描述「狗」--也就是阿福這隻狗,被我們抽象化後的樣子,我們需要將它用程式碼的方式定義出來。

這時候我們就會需要類別,來定義出我們觀察到同一類的物件該有哪些特徵和動作,也就是我們替物件「分門別類」後、篩選出特定資訊的抽象化結果

延續前面的例子,假設今天我們從阿福身上觀察到進食跟毛色兩個狗的重要資訊,我們就可以建立類別 Dog:

public class Dog
{
    public string color;
    public void Eat(IFood food) { /* 進食與消化之類的 */ };
}

藉由我們定義的類別,就可以從類別中實例化(=建立)出物件。

也就是說,現在我們終於可以用「狗」這個類別,來表達出我們需要的「阿福」了:

Dog afu = new Dog(); // 阿福是一隻狗
Console.Write(afu.Color); // 阿福是黑色的
afu.Eat(food); // 阿福會吃食物
……

閱讀全文


菜雞與物件導向 (0): 前言

在訂便當告一段落之後,其實就已經和同事約好要來整理公司新訓的筆記。但儘管已經到職快一年了,有些工具已經在專案碰過好幾次。但遇到需要跟朋友討論,或是聽前輩說明觀念的時候,還是不自主地會想「我真的懂嗎?」故一直是挺畏懼的。

但幸虧同事的鼓勵和以身作則,最終還是開啟了這個新系列,決定直接開坑把當初前輩新訓指導過的部分整理下來,也算是讓自己能趁著這機會好好複習一番,把自己的想法跟心得記錄下來。

另外,如果你是真心希望弄懂物件導向的朋友,這邊推薦《大話設計模式》的附錄,內容對物件導向的介紹清晰易懂且循序漸進,非常適合作為了解物件導向的起頭。

本篇的段落將會分成以下幾個部份,由於只是筆記一下,因此會附上一些知識點的參考資料,看見的時候可以先行閱讀;末尾也會附上有關的參考資料及文章,對於這類概念性的東西,一向是推薦多方閱讀以增強理解,就像保哥寫的:「如果你問 100 個人這個問題,可能會得到 200 個答案,所以你一定要有自己獨到、有自信、精闢的見解或描述方式。」如果有寫得不錯的文章想推薦給我,或是有地方需要補充和指證,還請不吝指教。共勉之。

……

閱讀全文