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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

類別最常看到的比喻,就是物件的設計圖。我們藉由類別去定義我們要的物件有什麼特徵、有什麼功能,再從類別根據設計圖產生物件出來使用。

這個從類別中產生的物件,就等於是這個類別定義的一個實際的例子,所以我們也會把類別產生的物件叫做實例。而從類別產生物件的過程,就叫做實例化。

也就是說,類別實際上就是我們認知中對這個物件的定義,我們篩選出我們需要的、我們認為這個物件應該具有的這些特徵和功能,按照我們的認知去設計了類別

接著我們再利用這個類別告訴程式如何建立出我們認為的這個物件,最終我們才能在程式中使用我們需要的這個物件。

當我們定義了一個狗的類別,我們實際上是在描述我們眼中的、我們歸納出來的、我們需要的「狗」,我們認為狗就是會吃東西。

接著,我們再從我們設計好的這份定義,去實例化出我們需要的狗:阿福,於是阿福就有了吃東西的能力。

我們有了抽象化的設計圖之後,就可以利用這個設計圖去建立多個符合這個設計的物件。例如說前面的狗,我們就可以建立出黑色的阿福,黃色的阿黃等等。

在現實中,我們將阿福、阿黃等等歸類為狗這個概念,而到了程式裡,我們利用這個狗的概念定義出類別,進而產生阿福、阿黃。

從這邊也能察覺到:類別是一個歸納好的概念,這概念中包含許多獨立的個體,也就是物件,而這些物件之間的差異則從我們定義類別時選擇的特徵去區分。

因此,一個人設計的類別,和他使用物件的方式,反映了他對於這個物件的看法和他覺得需要的內容。

同時,使用物件導向也意味著:比起 EatFood(dog, food) 而言,我們認同 Dog.Eat(food) 更直覺和易於理解。

那麼這邊就讓我們以卡牌遊戲舉例,理所當然卡牌遊戲不能沒有卡牌。

對我來說卡牌通常都需要這些特徵,我們在物件裡稱為屬性

  • 卡片名稱
  • 攻擊力
  • 防禦力
  • 卡牌描述

等等,另外卡牌也應該能作出某些動作,也就是這個物件的方法

  • 攻擊

我們確認了這些要素以後,就可以把它設計成一個類別 Card 如下

public class Card
{
    public string Name;
    public int Level;
    public int Attack;
    public int Health;
    public string Description;

    // 攻擊目標卡片
    public void Hit(Card target) 
    {
        target.damage(this.Attack)
    }

    // 被攻擊的時候扣血
    public void damage(int attack) 
    {
        this.Health -= attack;
        if (this.Health <= 0) 
        { 
            /* 可能呼叫死翹翹方法? */ 
        }
    }
}

抱歉我菜,如果有真的設計卡牌遊戲的工程師經過拜託不要打我。

接著我們就能在需要的時候藉由這個類別來實例化我們的卡牌:

var goblin = new Card();
var warrior = new Card();
warrior.Hit(goblin);

再提醒一次:

  • 類別是定義、是設計圖、是描述;物件是類別產生的實體、是實際上的執行者
  • 類別是抽象化的資訊,例如「狗」;物件則是一個特定的實例,例如「叫做阿福的狗」
  • 承上,狗的類別用來告訴程式什麼是狗;叫做阿福的物件則是程式根據我們的指示,建立出來的一條指定的狗

到此應該能夠初步掌握物件和類別的概念了。這邊推薦一下可以閱讀保哥的這篇 物件導向基礎:何謂類別(Class)?何謂物件(Object)?,裡面除了對物件和類別有更易懂的介紹和舉例以外,還有十題概念題可以幫助你搞懂物件和類別的意義與差異,相當值得一看。另外,也可以參考這幾篇的說明:

物件的部份由於是最初的概念,不免多廢話了一些。下一篇開始就讓我們快速看過物件導向的幾項功能和特性。

本系列下一篇:菜雞與物件導向 (2): 建構式、多載

2022.12.02 補充:感謝公司前輩簡潔有力的說明,修正了開頭時對物件的介紹

參考資料

同系列文章