iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 14
0
  • 今天的主題是 Abstract class,也就是抽象類別。抽象類別跟一般類別不同之處在於,抽象類別僅定義方法,而沒有實作,因此不能直接拿來生成物件。舉個例子, Shape 是個抽象類別,裡頭定義了例如名稱、面積、周長等等方法。而 RectangleTriangle 則繼承 Shape 並實作上述的方法。因此這裡也同先前講繼承時提到的,Abstract class 和繼承 Abstract class 的 class 有層次上的關係,而通常這麼做的原因是為了實現多型。
  • 而先前我們一直提到 interface 也是在定義一些沒有實作的方法,那麼到底 Abstract class 和 interface 有什麼意義和實務上的不同呢?兩者共同之處都是抽象層,包含了抽象的方法,而且不能直接生成 instance,然而 Abstract class 比較是層次上的抽象 (根源的繼承關係),而 interface 像是對行為動作的抽象。Abstract class 是要創建一個能夠體現某些行為的類別,例如哺乳類動物有哪些行為,設計理念上表現的是 "is-a" 的概念。Abstract class 也可以包含已經有實作的方法來讓子類別去重複使用。instance 通常只有抽象的方法,設計上則是表示 "has-a" 的概念。instance 比較像是對類別的行為有約束,可以讓不同的類別去擁有相同的行為。例如"飛",而很多類動物例如哺乳類或鳥類都可能可以飛,就會用不同方式去實作 "飛"。通常一個 Class 可以實作多個 interface (不同於繼承通常都只能單繼承)。如果你的設計哲學是例如 A 是不是 B 的一種,如果是,那麼 A 就能做 B 能做的事。如果是這樣的話就考慮 Abstract class 和繼承。如果你的設計哲學是關注在動作上,例如 A 如果可以擁有某些動作我們可以把 A 拿來使用,那就用 interface,而不是關注在於說 A 是不是一定要是某一類。 Abstract class 的代價比較高,因為通常只能單一繼承,也比較不容易去設計,因為必須把子類別中共同的屬性和方法抽象出來。但 interface 只要把一些特定行為抽象出來變成一個 interface,而每個類別又可以聲明實作了多個 interface,在設計上會比 abstract 來得更有彈性跟擴充性。
  • 當然這兩者沒有誰一定好跟不好,在有些語言或一起搭配使用,例如 Java、Scala。但像是 Golang、Rust 就因為沒有 Class,他們的哲學是組合代替繼承,所以就也沒有 Abstract class。等到講 interface 的時候我們再來探討為何是組合代替繼承囉!
  • 由於這裡只有 Python 和 Scala 有 abstract class,所以今天還是只會從這兩個語言去看實際上怎麼使用 abstract class。

Python 3

from abc import ABCMeta, abstractmethod


class Book(object, metaclass=ABCMeta):
    def __init__(self, title, author):
        self.title = title
        self.author = author

    @abstractmethod
    def display(self): pass


class MyBook(Book):
    def __init__(self, title, author, price):
        super().__init__(title, author)
        self.price = price

    def display(self):
        print("Title: " + self.title + "\nAuthor: " + self.author + "\nPrice: " + str(self.price))


title = input()
author = input()
price = int(input())
new_novel = MyBook(title, author, price)
new_novel.display()
  • 在 Python 並沒有像 Java、Scala 直接有 abstract class 這樣的關鍵字來宣告抽象類別,而是透過宣告 metaclass ABCMeta 來達到這件事情。什麼是 metaclass 呢?我們可以理解為是 class 的 class,因為在 Python,什麼東西都是 object,包含 class 本身也是一個 object,那麼既然是 object,一定會有一個像是 class 的東西來產生這個 object,而這個 class 的 class 就是 metaclass 啦!而一般的 class 預設的 metaclass 是 type。當我們去宣告了一個 class 例如 A ,而要用 A 去產生 object 的時候,其實是會先 call type__call__ Method,而 __call__ 的行為是去 call A__new____init__。所以假使我們今天改變了本來預設的 metaclass,就能夠改變物件生成的行為。例如常見會去使用 metaclass 的一種情境是 Singleton object,就是利用自定義 metaclass 來避免每次都去產生一個新的 object,有興趣的可以看看這裡
  • 讓我們回到程式本身,如果要定義 abstract class 就要在宣告 class 的時候加上 metaclass=ABCMeta ,就是這裡的 class Book,這是官方的 library 所提供的 (參考)。而抽象的方法必須在其宣告的上面加上 @abstractmethod 這個 decorator (Python 的 decorator 是一種特殊的 function,把 function 當做參數傳入,再傳回 function,而在其中可以為本來的 function 加上更多能力,可以參考這裡),就像這裡的 display 這個 Method 就是抽象的方法,直接用 pass來略過實作。 class MyBook 則繼承了 Book 並且實作了 display 囉!
  • 如果我們直接對 abstract class 做 instantiate object 就會出現 TypeError ,如果子類別忘記實作所有的抽象方法,那麼該子類別也會被視為 abstract class 而在 instantiate object 時同樣出現 TypeError 囉!
  • 更多關於 abc的部分可以以參考這裡

Scala

import scala.io.StdIn

abstract class Book(val title: String, val author: String) {
    def display()
}

class MyBook(override val title: String, override val author: String, val price: Int) extends Book(title, author) {
    def display(): Unit = {
        println("Title: " + title)
        println("Author: " + author)
        println("Price: " + price)
    }
}

object Solution {
    def main(args: Array[String]) {
        val (title, author) = (StdIn.readLine(), StdIn.readLine())
        val price = StdIn.readInt()
        val newBook = new MyBook(title, author, price)
        newBook.display()
    }
}
  • Scala 的情況就比 Python 單純許多,因為 Scala 直接就有 abstract class 的關鍵字,abstract class Book 中的 def display() 就是宣告抽象的方法。而 MyBook 在繼承 Book 的方式就跟上一篇講的一樣,就是用 extends ,然後必須實作 display() 這個 Method。假使沒有實作的話,那麼 Book 仍然也還是一個 abstract class,就必須要加上 abstract 這個關鍵字,不然 compiler 會報錯 class MyBook needs to be abstract, since method display in class Book of type ()Unit is not defined
  • 一般來說在 Scala 會同時應用 abstract class 跟 interface (在 Scala 是 Trait),所以也是這四個語言在設計上要考慮更加周全的,必須要注意囉!當我們在講 interface 的時候會再多加詳述 Scala 的 Trait。如果你已經迫不及待想知道這兩者的差異可以參考這裡囉!

Golang and Rust

  • 就如同我們先前所說,在這兩個語言的設計哲學是組合取代繼承,所以也沒有 abstract class 的概念,不過別擔心,等到在講 interface 的時候,Golang 和 Rust 就會是主角啦!

結語

今天談的 abstract class 主要還是 OOP 中關於繼承的部分,也就是在於層次上的抽象,並且有效率地去複用程式碼。今天也提到了 abstract class 和 interface 的一些異同,而之後我們會再談更多關於 interface 的大小事,Golang 和 Rust 就不會寂寞啦!


上一篇
[Day 12] 如果我有富爸爸
下一篇
[Day 14] 楚河漢界劃清楚!
系列文
30 天把自己榨好榨滿的四週四語言大挑戰!30

尚未有邦友留言

立即登入留言