物件導向基礎

📢 本文由 gemini-3-flash-preview 翻譯

物件導向 (Object-Oriented, OO) 是一種非常實用的系統化軟體開發方法

程序導向與物件導向

以一個問題引入:把大象裝進冰箱,需要幾步?

一般先打開冰箱,然後把大象裝進冰箱,最後關上冰箱

程序導向

關心我該怎麼做?一步步去實現這個功能

對於上述問題:

  1. 我打開冰箱
  2. 我把大象裝進冰箱裡
  3. 我關上冰箱門

物件導向

關心我該讓誰去做?去呼叫物件的操作來實現這個功能

對於上述問題

建立物件:大象,冰箱

  1. 冰箱打開門
  2. 大象鑽進冰箱
  3. 冰箱關上門

物件導向基礎

物件導向 = 物件 + 分類 + 繼承 + 透過訊息的通訊

可以說,採用這四個概念開發的軟體系統是物件導向的

物件

客觀世界由許多具體的事物、事件、概念和規則組成,這些均可被看成物件

在物件導向的系統中,物件是基本的執行時實體,它既包括資料 (屬性),也包括作用於資料的操作 (行為)。一個物件通常可由物件名稱、屬性及方法 3 個部分組成

訊息

物件之間進行通訊的一種構造叫做訊息。當一個訊息發送給某個物件時,包含要求接收物件去執行某些活動的資訊。接收到資訊的物件經過解釋,然後予以回應

類似於方法呼叫的傳遞參數

類別

一個類別定義了一組大體上相似的物件。一個類別所包含的方法和資料描述一組物件的共同行為屬性。把一組物件的共同特徵加以抽象並儲存在一個類別中是物件導向技術最重要的一點。是否建立了一個豐富的類別庫,是衡量一個物件導向程式設計語言成熟與否的重要指標

類別是在物件之上的抽象物件是類別的具體化,是類別的實例。在分析與設計時,通常把注意力集中在類別上,而不是具體的物件。也不必逐個定義每個物件,只需對類別做出定義,而對類別的屬性進行不同賦值即可得到該類別的物件實例

類別可以分為三種:實體類別、介面類別 (邊界類別) 和控制類別。控制類別的物件用來控制活動流,充當協調者

有些類別之間存在一般和特殊關係,即一些類別是某個類別的特殊情況,某個類別是一些類別的一般情況。特殊類別是一般類別的子類別,一般類別是特殊類別的父類別

通常,把一個類別和這個類別的所有物件稱為「類別及物件」或物件類別

方法多載

方法多載 (Method Overloading) 方式

  1. 方法名稱相同,參數個數不同
  2. 方法名稱相同,參數型別不同
  3. 方法名稱相同,參數型別順序不同

以 Java 為例,Java 的函式 (方法) 格式如下

1
2
3
4
5
6
/*
存取修飾詞 回傳值型別 方法名(參數型別1 參數名1, 參數型別2 參數名2,···)
{
	方法體
}
*/

以下為範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test
{
    // 原方法
    public void sum(int a, double b)
    {
        System.out.println(a + b);
    }
    // 1. 方法名稱相同,參數個數不同
    public void sum(int a, double b, int c)
    {
        System.out.println(a + b + c);
    }
    // 2. 方法名稱相同,參數型別不同
    public void sum(int a, int b)
    {
        System.out.println(a + b);
    }
    // 3. 方法名稱相同,參數型別順序不同
    public void sum(double a, int b)
    {
        System.out.println(a + b);
    }
}

物件導向三大特徵

物件導向的三個基本特徵是:封裝、繼承、多型

封裝

封裝 (Encapsulation) 是一種資訊隱蔽技術,它的目的是使物件的使用者和生產者分離,使物件的定義和實現分開。從程式設計者來看,物件是一個程式模組;從使用者來看,物件為他們提供了所希望的行為

也就是把客觀事物封裝成抽象的類別,並且類別可以把自己的資料和方法只讓可信的類別或者物件操作,對不可信的進行資訊隱藏

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Person{
    private String name;	// 使用 private 限制權限
    private int age;
    
    public void setName(String name){
        // 透過 public 方法提供修改物件屬性的途徑
        this.name = name;
    }
    public String getName(){
        // 透過 public 方法提供獲取物件屬性的途徑
        return name;
    }
    
    public void setAge(int age){
        if (age >= 0 && age <= 150)
        	this.age = age;
    }
    public int getAge(){
        return age;
    }
    
    public void run(){
        System.out.println("跑路!");
    }
}

繼承

繼承 (Inheritance) 是父類別和子類別之間共享資料和方法的機制。這是類別之間的一種關係,在定義和實現一個類別的時候,可以在一個已經存在的父類別的基礎上進行,把這個已經存在的類別所定義的內容作為自己的內容,並加入若干新的內容

一個父類別可以有多個子類別,這些子類別都是父類別的特例,父類別描述了這些子類別的公共屬性和方法。一個子類別可以繼承它的父類別 (或祖先類別) 中的屬性和方法,這些屬性和操作在子類別中不必定義,子類別中還可以定義自己的屬性和方法

如果子類別只從一個父類別得到繼承,稱為「單一繼承」。如果一個子類別有兩個或更多個父類別,則稱為「多重繼承」

註:Java 中一個子類別只能有一個父類別

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 接上一段程式碼看
public class Student extends Person{
    // 使用關鍵字 extends 指明要繼承的父類別
    private int id;
    
    public void setId(int id){
        this.id = id;
    }
    public int getId(){
        return id;
    }
    
    public void study(){
        System.out.println(getName() + "正在學習");
    }
    
    // 覆寫父類別方法
    public void run(){
        System.out.println(getName() + "想跑路");
    }
}

多型

在收到訊息時,物件要予以回應。不同物件收到同一訊息可以產生完全不同的結果,這一現象稱為多型 (Polymorphism)。在使用多型的時候,使用者可以發送一個通用的訊息,而實現的細節則由接收物件自行決定。這樣,同一訊息就可以呼叫不同的方法

多型的實現受到繼承的支持,利用類別的繼承層次關係,把具有通用功能的訊息存放在高層次,而不同的實現這一功能的行為放在較低層次,在這些低層次上生成的物件能夠給通用訊息以不同的回應

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 父類別
public class Person{
    public void work(){
        System.out.println("工作");
    }
}

// 子類別 1
public class Student extends Person{
    // 覆寫父類別方法
    public void work(){
        System.out.println("上學");
    }
    
    public void run(){
        System.out.println("Only the young CAN RUN!");
    }
}

// 子類別 2
public class Worker extends Person{
    // 覆寫父類別方法
    public void work(){
        System.out.println("上班");
    }
    
    public void sleep(){
        System.out.println("睡覺");
    }
}

// main
public class main{
    public static void main(String[] args){
        // 編譯看左邊,執行看右邊
        Person stu = new Student();
        stu.work();
        // 不可呼叫 stu.run(); 方法
        
        Person wok = new Worker();
        wok.work();
        // 不可呼叫 wok.sleep(); 方法
    }
}

// 執行輸出:
// 上學
// 上班

多型 hedge 形式

多型有不同的形式,Cardelli 和 Wegner 把它分為 4 類

image

參數多型 (Parametric Polymorphism):應用比較廣泛的多型,被稱為最純的多型

包含多型 (Inclusion Polymorphism):在許多語言中都存在,最常見的例子就是子類型化 (Subtyping),即一個型別是另一個型別的子型別

過載多型 (Overloading Polymorphism):同一個名字在不同的上下文 (Context) 中所代表的含義不同

動態繫結和靜態繫結

繫結 (Binding) 是一個把程序呼叫和回應呼叫所執行的程式碼加以結合的過程。在一般的程式設計語言中,繫結是在編譯時進行的,叫做靜態繫結動態繫結則是在執行時進行的。因此,一個給定的程序呼叫和程式碼的結合直到呼叫發生時才進行

動態繫結是和類別的繼承以及多型相聯繫的。在繼承關係中,子類別是父類別的一個特例,所以父類別可以出現的地方,子類別物件也可以出現。因此在執行過程中,當一個物件發送訊息請求服務時,要根據接收物件具體情況將請求的操作與實現的方法進行連接,即動態連接

物件導向分析

物件導向分析 (Object-Oriented Analysis, OOA) 的目的是為了獲得對應用問題的理解。理解的目的是確定系統的功能、效能要求

物件導向分析包含 5 個活動:認定物件、組織物件、描述物件間的相互作用、確定物件的操作、定義物件的內部資訊

物件導向設計

物件導向設計 (Object-Oriented Design, OOD) 是將 OOA 所建立的分析模型轉化為設計模型,其目標是定義系統構造藍圖。通常的情況是,由概念模型生成的分析模型被裝入到相應的執行環境中時,需要考慮實現問題加以調整和增補,如根據所用程式設計語言是否支援多重繼承或繼承,而調整類別結構。OOA 與 OOD 之間不存在鴻溝,採用一致的概念和一致的表示法,OOD 同樣應遵循抽象、資訊隱蔽、功能獨立、模組化等設計準則

物件導向設計的活動

OOD 在複用 OOA 的模型之基礎上,包含與 OOA 對應如下五個活動

  1. 識別類別及物件
  2. 定義屬性
  3. 定義服務
  4. 識別關係
  5. 識別套件 (Package)

物件導向設計原則

  1. 單一職責原則 (Single Responsibility Principle)

​ 就一個類別而言,應該僅有一個引起它變化的原因。即,當需要修改某個類別的時候原因有且只有一個,讓一個類別只做一種型別職責

  1. 開放-封閉原則 (Open-Closed Principle)

​ 軟體實體 (類別、模組、函式等) 應該是可以擴展的,即開放的;但是不可修改的,即封閉的

  1. 里氏替換原則 (Liskov Substitution Principle)

​ 子型別必須能夠替換掉他們的基 (父) 型別。即,在任何父類別可以出現的地方,都可以用子類別的實例來賦值給父型別的參照。當一個子型別的實例應該能夠替換任何其超類別的實例時,它們之間才具有是一個 (is-a) 關係

  1. 依賴反置原則 (Dependency Inversion Principle)

​ 抽象不應該依賴於細節,細節應該依賴於抽象。即,高層不應該依賴於底層模組,二者都應該依賴於抽象

  1. 介面隔離原則 (Interface Segregation Principle)

​ 不應該強迫客戶依賴於它們不用的方法。介面屬於客戶,不屬於它所在的類別層次結構。即:依賴於抽象,不要依賴於具體,同時在抽象級別不應該有對於細節的依賴。這樣做的的好處就在於可以最大限度地應對可能的變化

以上為物件導向方法中的五大原則。除了這五大原則之外,Robert C. Martin 提出的物件導向設計原則還包括以下幾個

  1. 再用發佈等價原則 (Release-Reuse Equivalency Principle)

​ 再用的顆粒就是發佈的粒度

  1. 共同封閉原則 (Common Closure Principle)

​ 套件中的所有類別對於同一類性質的變化應該是共同封閉的。一個變化若對一個套件產生影響,則將對該套件中的所有類別產生影響,而對於其他的套件不造成任何影響

  1. 共同再用原則 (Common Reuse Principle)

​ 一個套件中的所有類別應該是共同再用的。如果再用了套件中的一個類別,那麼就要再用套件中的所有類別

  1. 無環依賴原則 (Acyclic Dependencies Principle)

​ 在套件的依賴關係圖中不允許存在環,即套件之間的結構必須是一個直接的無環圖形

  1. 穩定依賴原則 (Stable Dependencies Principle)

​ 朝著穩定的方向進行依賴

  1. 穩定抽象原則 (Stable Abstractions Principle)

​ 套件的抽象程度應該和其穩定程度一致

物件導向測試

就測試而言,用物件導向方法開發的系統測試與其他方法開發的系統測試沒有什麼不同

一般來說,對物件導向軟體的測試可分為下列 4 個層次進行

  1. 演算法層
  2. 類別層
  3. 範本層
  4. 系統層

物件導向程式設計

程式設計範式 (Programming Paradigm) 是人們在程式設計時所採用的基本方式模型,決定了程式設計時採用的思維方式、使用的工具,同時又有一定的應用範疇。其發展經歷了程序程式設計、模組化程式設計、函式程式設計、邏輯程式設計,發展到現在的物件導向程式設計範式

物件導向程式設計 (Object-Oriented Programming, OOP) 的實質是選用一種物件導向程式設計語言 (Object-Oriented Programming Language, OOPL),採用物件、類別及其相關概念所進行的程式設計。它的關鍵在於加入了類別和繼承性,從而進一步提高了抽象程度。特定的 OOP 概念一般是透過 OOPL 中特定的語言機制來體現的

OOP 現在已經擴展到系統分析和軟體設計的範疇,出現了物件導向分析和物件導向設計的概念,這部分在前面已經有所體現