OOP 簡介
Intoduction
物件導向程式設計(主要稱為 OOP)是解決問題的程式設計範例。
OO(物件導向)程式的美妙之處在於,我們將程式視為一組彼此通訊的物件,而不是遵循特定順序的順序指令碼。
有很多支援 OOP 的程式語言,一些流行的是:
- Java 的
- C++
- C#
眾所周知,Python 也支援 OOP,但它缺少一些屬性。
OOP 術語
OOP 中最基本的術語是 class。
一個類基本上是一個物件,它有一個狀態,它根據它的狀態工作。
另一個重要的術語是 instance。
將類視為用於建立自身例項的模板。該類是一個模板,例項是具體的物件。
從類 A 建立的例項通常被稱為“型別 A”,類似於 5 的型別是 int , abcd
的型別是字串。
建立名為 insance1 的型別(類) ClassA 的例項的示例 :
Java 的
ClassA instance1 = new ClassA();
C++
ClassA instance1;
要麼
ClassA *instance1 = new ClassA(); # On the heap
Python
instance1 = `ClassA()`
正如你在上面的示例中所看到的,在所有情況下都提到了類的名稱,之後有空括號(C++除外,如果它們為空,則可以刪除括號)。在這些括號中,我們可以將 arguments
傳遞給 constructor 我們的類。
建構函式是每次建立例項時呼叫的類的方法。它既可以參與也可以不論證。如果程式設計師沒有為他們構建的類指定任何建構函式,那麼將建立一個空建構函式(一個什麼都不做的建構函式)。
在大多數語言中,建構函式被定義為一種方法,沒有定義其返回型別和類的相同名稱(在幾個部分中的示例)。
建立名為 b1 的型別(類) ClassB 的例項的示例。 ClassB 的建構函式接受一個 int 型別的引數 :
Java 的
ClassA instance1 = new ClassA(5);
要麼
int i = 5; ClassA instance1 = new ClassA(i);
C++
ClassA instance1(5);
Python
instance1 = `ClassA(5)`
如你所見,建立例項的過程與呼叫函式的過程非常相似。
功能與方法
功能和方法都非常相似,但在物件導向設計(OOD)中它們各自都有其自身的含義。
方法是對類的例項執行的操作。該方法本身通常使用例項的狀態來操作。
同時,函式屬於類而不屬於特定例項。這意味著它不使用類的狀態或例項中儲存的任何資料。
從現在開始,我們將僅在 Java 中展示我們的示例,因為 OOP 在這種語言中非常清晰,但是相同的原則適用於任何其他 OOP 語言。
在 Java 中,函式 static 在其定義中包含單詞,如下所示:
// File's name is ClassA
public static int add(int a, int b) {
return a + b;
}
這意味著你可以從指令碼中的任何位置呼叫它。
// From the same file
System.out.println(add(3, 5));
// From another file in the same package (or after imported)
System.out.println(ClassA.add(3, 5));
當我們從另一個檔案呼叫該函式時,我們使用它所屬的類的名稱(在 Java 中,這也是檔案的名稱),這給出了函式屬於類而不是它的任何例項的直覺。
相比之下,我們可以像這樣在 ClassA 中定義一個方法 :
// File's name is ClassA
public int subtract(int a, int b){
return a - b;
}
在這個 decleration 之後我們可以像這樣呼叫這個方法:
ClassA a = new ClassA();
System.out.println(a.subtract(3, 5));
這裡我們需要建立一個 ClassA 例項,以便呼叫它的方法減法。請注意,我們不能執行以下操作:
System.out.println(ClassA.subtract(3, 5));
這行會產生一個編譯錯誤,抱怨我們在沒有例項的情況下呼叫了這個非靜態方法。
使用類的狀態
讓我們假設我們想再次實現我們的減法方法,但這次我們總是想減去相同的數字(對於每個例項)。我們可以建立以下類:
class ClassB {
private int sub_amount;
public ClassB(int sub_amount) {
this.sub_amount = sub_amount;
}
public int subtract(int a) {
return a - sub_amount;
}
public static void main(String[] args) {
ClassB b = new ClassB(5);
System.out.println(b.subtract(3)); // Ouput is -2
}
}
當我們執行這段程式碼,一個新的例項命名為 b 類的 ClassB 的建立它的構造被饋送值 5 。
建構函式現在接受給定的 sub_amount 並將其儲存為自己的私有欄位,也稱為 sub_amount (此約定在 Java 中非常有名,以將引數命名為與欄位相同)。
在那之後,我們列印到控制檯呼叫該方法的結果減去上 b 與值 3 。
請注意,在減法的實現中,我們不像建構函式那樣使用 this.
。
在 Java 中,只有在該範圍內定義了具有相同名稱的另一個變數時,才需要編寫 this
。同樣適用於 Python 的 self
。
因此,當我們在減法中使用 sub_amount 時,我們引用每個類不同的私有欄位。
另一個強調的例子。
我們只需將上面程式碼中的 main 函式更改為:
ClassB b1 = new ClassB(1);
ClassB b2 = new ClassB(2);
System.out.println(b1.subtract(10)); // Output is 9
System.out.println(b2.subtract(10)); // Output is 8
我們可以看到, b1 和 b2 是獨立的,每個都有自己的狀態。
介面和繼承
An interface 是一個契約,它定義了一個類將具有哪些方法,從而定義它的功能。介面沒有實現,它只定義了需要完成的操作。
Java 中的一個例子是:
interface Printalbe {
public void print();
}
該 Printalbe 介面定義了一個方法呼叫的列印,但它並沒有給其實施(很奇怪的 Java)。宣告自己為 implementing
此介面的每個類都必須為 draw 方法提供一個實現。例如:
class Person implements Printalbe {
private String name;
public Person(String name) {
this.name = name;
}
public void print() {
System.out.println(name);
}
}
如果 Person 宣告自己實現 Drawable 但沒有提供列印實現,則會出現編譯錯誤,程式無法編譯。
繼承是指向擴充套件另一個類的類的術語。例如,假設我們現在有一個有年齡的人。實現這樣的人的一種方法是複製 Person 類並編寫一個名為 AgedPerson 的新類,它具有相同的欄位和方法,但它有另一個屬性 -age。
這很糟糕,因為我們複製整個程式碼只是為了給我們的類新增一個簡單的特性。
我們可以使用繼承繼承 Person ,從而獲得它的所有功能,然後使用我們的新功能增強它們,如下所示:
class AgedPerson extends Person {
private int age;
public AgedPerson(String name, int age) {
super(name);
this.age = age;
}
public void print() {
System.out.println("Name: " + name + ", age:" + age);
}
}
有一些新的事情發生:
- 我們使用儲存的單詞
extends
來表示我們繼承自 Person(以及它的實現到 Printable ,所以我們不需要再次宣告implementing Printable
)。 - 我們使用儲存詞
super
來呼叫 Person 的建構函式。 - 我們用一個新的覆蓋了 Person 的列印方法。 **
這是非常熟悉的 Java 技術,所以我不會深入研究這個主題。但是我會提到在開始使用它們之前應該學習很多關於繼承和介面的極端情況。例如,哪些方法和函式是繼承的?從類繼承時,私有/公共/受保護欄位會發生什麼?等等。
抽象類
一個 abstract class 是 OOP 相當先進的術語,描述了兩個介面和繼承的組合。它允許你編寫一個同時具有已實現和未實現的方法/函式的類。在 Java 中,這是通過使用關鍵字 abstract
來完成的,我不會更多地解釋它是一個簡單的例子:
abstract class AbstractIntStack {
abstract public void push(int element);
abstract public void pop();
abstract public int top();
final public void replaceTop(int element) {
pop();
push(element);
}
}
注意:final
關鍵字表示從此類繼承時無法覆蓋此方法。如果一個類被宣告為 final,那麼任何類都不能從它繼承。