0%

抽象是什麼?聽起來好抽象

前言

當我在學 Design Pattern 時,請教前輩:「抽象是什麼?」對方熱心的一陣瘋狂輸出解釋並舉些範例,我的心中最後冒出一個想法:「聽起來好抽象。」同時依然無法體會抽象的含義,這便是我的親身經歷。就是這麼奇怪,我知道什麼場景可以用這個詞,但背後含意卻不清楚。為了徹底弄懂「抽象」的含義,特別寫成文章強迫自己去弄懂「抽象」的意思。

辭典解釋

根據教育部國語辭典,「抽象」有兩個解釋:

  1. 哲學上指從個別的、偶然的不同事物中,分析出其共同點的思想活動。相對於具體而言。
  2. 泛指籠統概括。亦相對於具體而言。

這才明白,原來日常生活中所說的「抽象」,指的是第二個解釋「泛指籠統概括」。當所說的話語過於籠統不夠具體,導致聽者不懂時,聽者通常會說「聽起來好抽象」,如同我的親身經歷。

接下來讓我們回到正題,抽象是什麼?

抽象是什麼?

國語辭典的第一個解釋則與程式設計中的「抽象」沾到邊了!「抽象」指的是一種思想活動,從個別的、偶然的不同事物中分析出其共同點的思考過程。但為什麼我說只是沾到邊呢?因為還少了一個重點,就是抽象還隱藏了不相關的細節

我在 Abstractions in Digital World 發現一個對於抽象 (Abstraction) 一詞不錯的解釋

Abstraction:
Alternative representation of a concept that hides irrelevant details and exposes only the properties that we’re interested in.

翻譯蒟蒻:抽象為一個概念的替代性表述,隱藏不相關的細節,只暴露我們感興趣的屬性。

簡單來說抽象主要做了兩件事

  1. 分析不同東西之間的共同點
  2. 隱藏不相關的細節,只暴露感興趣的屬性

生活中的範例

學習一個觀念要能夠舉出例子,才算真的明白它的道理,這裡舉出兩個常見但沒有仔細思考過的範例:

  1. 奇數:若看到一串數字 1, 3, 5, 7, 9, ...,會覺得這串數字背後所想表達的含義是什麼?也許會說「這些數字都是奇數 (正奇數、正整數或整數 ... 等等)」,不論如何,這些都是我們針對不同的數字進行分析,發現它們共通點所產生的結論。

    • 可以說奇數是使用來替代性的表示這些數字的一個概念 (抽象),我們感興趣的並非數字本身 (細節),而是它們整體的特質,例如皆不能被 2 整除 (感興趣的屬性)。

    • 將其抽象為奇數後,我們便能捨棄原本的數字,直接對奇數進行更高層次的討論。

  2. 通訊設備:手機可以透過 4G LTE 通話、電腦可以透過 Ethernet 通話、平板可以透過 Wi-Fi 通話,它們的共通點都是可以通話,差別只在於實現通話的方式不同。

    • 我們可以使用通訊設備這個概念 (抽象) 來替代性的表示手機、電腦與平板,不去理會通話的原理 (細節),就能夠用它來撥通號碼 (感興趣的屬性)。

在 OOP 應用抽象的範例

情境:寶可夢中的小智 (Ash Ketchum) 想要計算背包中的寶貝球數量。

(為說明方便,只取兩種寶貝球:超級球 (Great Ball)、大師球 (Master Ball))

為了讓小智可以取得不同的寶貝球,定義了兩個函式分別為 StoreGreatBallStoreMasterBall,並使用兩個陣列各自存放不同的寶貝球,最後將各個陣列拿來算出長度再加起來,得出總共有多少寶貝球。

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
package main

import "fmt"

type GreatBall struct{}

func (b GreatBall) CatchPokemon() {
fmt.Print("great ball catching...")
}

type MasterBall struct{}

func (b MasterBall) CatchPokemon() {
fmt.Print("master ball catching...")
}

type AshKetchum struct {
GreatBalls []GreatBall
MasterBalls []MasterBall
}

func (a *AshKetchum) StoreGreatBall(b GreatBall) {
a.GreatBalls = append(a.GreatBalls, b)
}

func (a *AshKetchum) StoreMasterBall(b MasterBall) {
a.MasterBalls = append(a.MasterBalls, b)
}

func (a *AshKetchum) BallCount() int {
return len(a.GreatBalls) + len(a.MasterBalls)
}

func main() {
ash := AshKetchum{}
ash.StoreGreatBall(GreatBall{})
ash.StoreGreatBall(GreatBall{})
ash.StoreMasterBall(MasterBall{})

fmt.Printf("Ash Ketchum has %d poké balls\n", ash.BallCount())
}

僅僅是兩種寶貝球而已,就讓我們感覺到很多重複的程式碼出現,若再加 10 種寶貝球怎麼辦?還好,我們學過抽象,仔細觀察可以發現 GreatBallMasterBall 其實有一個共通點,就是他們都可以抓寶可夢 (擁有 CatchPokemon 函式)。

事實上,在我們淺意識中就是根據是否具備抓寶可夢的能力來判斷這樣的球是否為寶貝球,只是沒有特別強調過,而現在為了實作而歸納出這個結論是非常合理的。

接下來可以利用抽象出來「寶貝球」的這個概念,讓我們的程式更簡化,先定義出寶貝球的概念 (對應於 interface),其說明了只要包含 CatchPokemon 這個方法的球都是寶貝球。這樣做可以讓小智不需要為了一種球就定義一個函式、陣列,計算總數也只需要針對儲存所有寶貝球的那個陣列做運算即可!

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
package main

import "fmt"

type PokeBall interface {
CatchPokemon()
}

type GreatBall struct{}

func (b GreatBall) CatchPokemon() {
fmt.Print("great ball catching...")
}

type MasterBall struct{}

func (b MasterBall) CatchPokemon() {
fmt.Print("master ball catching...")
}

type AshKetchum struct {
PokeBalls []PokeBall
}

func (a *AshKetchum) StorePokeBall(b PokeBall) {
a.PokeBalls = append(a.PokeBalls, b)
}

func (a *AshKetchum) BallCount() int {
return len(a.PokeBalls)
}

func main() {
ash := AshKetchum{}
ash.StorePokeBall(GreatBall{})
ash.StorePokeBall(GreatBall{})
ash.StorePokeBall(MasterBall{})

fmt.Printf("Ash Ketchum has %d poké balls\n", ash.BallCount())
}

哪一種寶貝球並不重要 (細節),我們只關心它是不是能抓寶可夢的球 (感興趣的屬性,也是共通點),因為能抓寶可夢的球都是好球,這就是抽象的結果。

結語

其實要理解抽象並不難,會覺得難的是因為我們平常把「抽象」理解為「籠統概括、艱澀難懂」,而不知道它還有另一個含義「尋找共通點、隱藏細節」,所以抽象真的抽象嗎?聽起來又沒這麼抽象了!

參考文獻

  1. 教育部國語辭典

  2. SOLID Principles of Object-Oriented Design and Architecture - Abstractions in Digital World

很高興能在這裡幫助到您,歡迎登入 Liker 為我鼓掌 5 次,或者成為我的讚賞公民,鼓勵我繼續創造優質文章。
以最優質的內容回應您的鼓勵