少用繼承,多用介面

更新於 發佈於 閱讀時間約 6 分鐘

承接上一篇文章,現代的物件導向已經走偏了,他就像null pointer,很容易出現不好的設計。自從我深入學習函數式編程後,漸漸發現物件導向的不合理的設計,而學習rust之後更讓我開始討厭物件導向,rust幾乎把所有我認為不好的地方都修正了。這個系列的文章我將會一一比較物件導向與rust的差異。這篇文章將關注於繼承機制。


物件導向的設計方式通常是先預想多個任務或情境,並設計一個物件來統一處理它,之後若是有新的情境,可以藉由繼承來延伸功能。這種設計方式好處是非常直觀且容易擴展,但這反而容易累積各種技術債。設計物件時一般都是以可擴展為前提,但我們往往無法預測它會怎麼擴展,有可能之後的擴展打破了一些原本的假設,使得之前寫的程式邏輯出現錯誤。這種「向後的抽象化設計」很容易出現這種情況,因為我們總是以「這部分的程式碼可以重用」來實作父類別,但並沒有考慮到應該在怎樣的情況下才能使用。正確的設計方式應該要寫明繼承這個類別應該符合哪些規則,也就是關注約束(constraint)而非延展(extension)。抽象化就是對未知的事物建立模型,這個過程很容易會將先入為主的假設帶入其中,而關注約束更容易發現那些未察覺的潛在資訊(unknown knowns),讓你更能夠意識到你的程式邏輯運用了哪些假設。


這種關注約束的設計方式更適合使用介面而非類別來定義規則,尤其是當你想要抽象化的行為不侷限於一種類別時。例如,當你想要設計一個可以連線的物件,但又不想限制在某種情境下運作,你應該設計一個介面Connectable而非抽象類別Connection。介面一般不帶有預設實作,因此本身並不具有重用程式碼的能力。若是要重用程式碼,不應依賴於繼承機制,而是使用高階函式或是組合物件等方法,如此更容易把規則理清。除去延伸功能後,繼承仍可以用來細分類別,也就是讓物件在已知的情境下做出不同的行為,而這個機制應當只在內部使用,不應讓它在你不知道的地方被繼承改寫。


rust的trait system類似於介面,但並不完全一樣。trait(特性)跟介面一樣都是用於描述行為,也一樣都是關注於約束而非延展。實作介面一般需要在定義類別時實作介面的方法,也就是介面是類別的一部分。而trait描述的是特性本身,並不需要在定義類別時實作。trait所描述的特性並不侷限於單個類別,它也可以描述多個類別之間的特性,例如A: Add<B>描述的是A與B之間的加法關係。rust甚至可以為符合某些條件的所有類別實作trait,例如我們可以為所有擁有特性A: Add<A> 的類別A實作A: Mul<uint>。基於以上能力,我們不應該把trait當作類別的一部分,而是特性本身。然而rust把trait的語法設計的跟介面一樣,其實它應該寫作Add<A,B>,也就是「存在一個trait描述A與B之間的加法特性」,編譯器會根據上下文去搜尋合適的trait。trait 的這種設計把類別的定義與trait的定義分開,讓我們可以把不同的關注點分離開來,而非像介面必須綁在類別的定義之上。


rust跟物件導向的另一個很大的不同是rust沒有類似於繼承的子類關係,但是有基於泛型的子類關係。在有繼承特性的程式語言中,當你拿到了一個類別為A的參數,它可能其實是B的物件實體,只是它的類別繼承自A。這種子類關係使得類別無法描述實際的物件實體,迫使我們必須以抽象的方式思考程式邏輯,而非基於實作細節。這種隱藏部分細節的做法讓我們能夠把雜音去除,讓我們專注於更重要的概念與程式邏輯。然而我們沒辦法限制變數只能是哪種實體,有些程式語言甚至沒辦法阻止類別被繼承,就算能也不適用於所有情況,這使得我們需要額外注意上下文才能判斷變數到底是不是延展過的物件。


基於泛型的子類關係則是利用類型參數與類型約束界定類別的範圍,例如List<A> where A: Comparable 代表由所有內容可比較的列表的集合,也就是List<int>, List<string>, List<List<int>> 等類別的聯集(正確來說是indexed family)。他的子類關係建立在約束的強度,例如它是 List<A> where A: Equal 的子類,因為所有可比較的類型都是可以判斷相等的,因此其組成的列表也有子類關係。類型參數明確地表明我們不知道它到底是什麼,只知道它有什麼特性,因此更能區別抽象與實作的邊界。你可以把類型參數想像成某個類別「未知」的部分,比起宣告x: List<Comparable>(變數x是內容可以比較的列表,但我不知道他的內容到底是什麼),不如宣告A: Comparable跟x: List<A>(變數x是內容可以比較的列表,但我不知道他的內容到底是什麼,姑且叫他A)。透過泛型,我們可以把未知的部分串聯起來,並使程式邏輯更加一致。


rust的trait在意義上跟類型有很大的不同,他更像是類型的種類而非一種特殊的類型,因此不能直接宣告變數為 Comparable,必須藉由類型參數的約束描述物件的抽象行為。因此trait用來描述抽象的概念,而型別則用於描述實際的資料結構。這一點跟介面很不一樣,我們一般都會把介面當作一種純抽象類別,因而用類似繼承的概念思考。在trait system中,A: Comparable代表的不是「類型A是類型Comparable的子類」,而是類型本身是Comparable裡的一個類別。Comparable可以看作是類別的集合,而我們透過這些集合界定我們想要的類型,就像是我們可以透過類型界定我們想要的物件,因此trait被稱為二階類型(2-types, kinds)。正因為它們不是包含關係(⊆)而是隸屬關係(∈),抽象的概念才能完全從類別獨立出來。

avatar-img
4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
在現代,物件導向雖然仍是主流,函數式慢慢得到關注。物件導向並不適合所有的程式邏輯,但在像是Java的物件導向的程式語言中,幾乎所有值都被當作物件,因此在一些情境下Java寫起來會非常冗余。物件導向流行的原因大概是因為它的思考方式比較符合我們對於世界的認知,但邏輯推理與解決問題的方式卻不一定符合我們的
我學習程式語言的動機並不是想要做什麼遊戲,而是單純覺得很有趣。除去國小自己寫過網頁的經歷,我
What Color is Your Function? 一文中 Async function 被比喻成「紅色函式」 紅色函式不能在普通的函式裡(即「藍色函式」)使用 因此紅色具有感染性 定義函式之前必須先決定是否要染成紅色 作者不喜歡這種麻煩的規則 但我認為這種麻煩是有道理的
'方格子在 linux/firefox 上的 bug 超煩 '若在每行第一句打中文會被吃掉 'Ctrl+V還會複製貼上之後的文字貼上之後的文字
在現代,物件導向雖然仍是主流,函數式慢慢得到關注。物件導向並不適合所有的程式邏輯,但在像是Java的物件導向的程式語言中,幾乎所有值都被當作物件,因此在一些情境下Java寫起來會非常冗余。物件導向流行的原因大概是因為它的思考方式比較符合我們對於世界的認知,但邏輯推理與解決問題的方式卻不一定符合我們的
我學習程式語言的動機並不是想要做什麼遊戲,而是單純覺得很有趣。除去國小自己寫過網頁的經歷,我
What Color is Your Function? 一文中 Async function 被比喻成「紅色函式」 紅色函式不能在普通的函式裡(即「藍色函式」)使用 因此紅色具有感染性 定義函式之前必須先決定是否要染成紅色 作者不喜歡這種麻煩的規則 但我認為這種麻煩是有道理的
'方格子在 linux/firefox 上的 bug 超煩 '若在每行第一句打中文會被吃掉 'Ctrl+V還會複製貼上之後的文字貼上之後的文字
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本章節是Java入門的第八天,主要介紹物件導向的概念。這包括了類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、Lambda表達式、泛型和反射等主題。每個主題都配有相關的程式碼範例,以協助讀者更好地理解這些概念。
Thumbnail
本章節的目的是介紹 Kotlin 中的物件導向概念。這包括了類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型以及反射等概念。每一個概念都會透過範例程式碼來解釋其功能和用法。
Thumbnail
這個章節主要介紹了Swift程式語言中物件導向程式設計的基本概念,包括類別、建構子、公開、私有、受保護等等的概念。同時,也介紹了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射等進階特性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
※ OPP第三大核心-多型 ※ 多型的基本定義: 多型是利用繼承的特性,讓不同的子類別可以實現相同的介面,但在呼叫這些介面的方法時會表現出不同的行為。這使得程式設計更具彈性和擴展性,避免了複雜的條件判斷式,同時促進了代碼的重用。 class Animal { makeSound() {
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
物件導向(Object-Oriented Programming,OOP) 可以用來提高程式碼的可讀性、可維護性和可擴展性,同時還能夠促進程式的重用和組織。
Thumbnail
本文介紹了Python中的物件導向程式設計的重要概念,包括類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射。每個概念都有對應的程式碼範例來說明其用法和功能。這些概念對於理解和使用Python進行物件導向程式設計至關重要。
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本章節是Java入門的第八天,主要介紹物件導向的概念。這包括了類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、Lambda表達式、泛型和反射等主題。每個主題都配有相關的程式碼範例,以協助讀者更好地理解這些概念。
Thumbnail
本章節的目的是介紹 Kotlin 中的物件導向概念。這包括了類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型以及反射等概念。每一個概念都會透過範例程式碼來解釋其功能和用法。
Thumbnail
這個章節主要介紹了Swift程式語言中物件導向程式設計的基本概念,包括類別、建構子、公開、私有、受保護等等的概念。同時,也介紹了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射等進階特性。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
※ OPP第三大核心-多型 ※ 多型的基本定義: 多型是利用繼承的特性,讓不同的子類別可以實現相同的介面,但在呼叫這些介面的方法時會表現出不同的行為。這使得程式設計更具彈性和擴展性,避免了複雜的條件判斷式,同時促進了代碼的重用。 class Animal { makeSound() {
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
物件導向(Object-Oriented Programming,OOP) 可以用來提高程式碼的可讀性、可維護性和可擴展性,同時還能夠促進程式的重用和組織。
Thumbnail
本文介紹了Python中的物件導向程式設計的重要概念,包括類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射。每個概念都有對應的程式碼範例來說明其用法和功能。這些概念對於理解和使用Python進行物件導向程式設計至關重要。
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。