物件導向不好用

閱讀時間約 5 分鐘

讓我在這篇文章總結一下前面對物件導向設計的討論,我們討論了物件導向的四個特性:繼承、抽象、多型、封裝,分析了它們的問題,並跟函數式編程的思維做比較。我們引入了與之相對應的特性:泛型、特性系統、模組化,有些特性雖然跟那四個特性很像,但在一些細微的地方有不同的詮釋,使得整體思考方式很不一樣。


「繼承」是物件導向最重要的特性,它提供一種方法建立子類關係,並將父類別的成員、方法與介面的實作繼承下來。這種繼承機制讓物件成員與方法得以增加,並讓方法變得可以被改寫。並不是所有方法都適合被繼承,例如物件的複製方法應該要以實體類型為回傳類型,但這個方法會因為繼承變得不符合規則,實際上它的方法實作也不適合透過繼承被重用。允許子類的類型讓物件的實體類型可以和變數類型不一樣,因而造成類型資訊的丟失,因此物件導向很依賴動態類型檢查。這種子類關係是物件導向的抽象化的根基。物件導向的「抽象」是向外的抽象,它把相同的功能抽取出來成為父類,並把其他不同的部分當成它的延伸。它透過子類轉換將不重要的部分抹除,因而形成抽象的概念。這種基於抹除的抽象化讓類型資訊遺失,使得延伸的部分在一些情況變得難以掌握,尤其是定義容器類的資料結構時。相反的,函數式編程使用向內的抽象,它將不同的部分抽換成類型參數,因此可以保證未知類型的同調性,還可以透過類型約束限制延伸的方向。泛型可以取代大部分透過繼承能做到的事情,但在解決問題上會很不一樣,因為物件導向鼓勵開發者使用繼承為問題建模,而只使用泛型就必須以組合建構模型。物件導向的類別只專注於已知的部分,這很容易會讓我們忽略掉對子類做了哪些假設,使得未來做延伸時常常打破約束。而泛型的類型變數描述的是未知的部分,因此可以明確的知道你不知道什麼,透過了解知與未知的邊界更能有效控制抽象化的模型。


物件導向的「多型」指的是子類多型,它透過繼承類別/實作介面讓相同的方法根據物件的實體類型擁有不同的行為,從而實現類別的延展性。延展性讓函式或物件有辦法與未知的程式碼合作,只要未知的部分透過介面表明它符合某些特性。介面用來描述一個類別是否符合某些特性,它是物件導向多型的基礎,在這裡類別也可以看作一種帶有預設實作的介面。因為介面的方法是屬於物件的,一個變數的介面實作是由它的實體類型決定而非變數類型,因此我們很難保證兩個相同類型的變數是否具有相同的特性,例如比較兩個變數時使用哪個物件的比較方法會影響結果。特性本來就不應該被繼承,子類轉換會使特性的意義改變。rust沒有類似繼承的子類關係,物件的實體類型基本上就是變數的類型,因此一個變數的特性可以靜態地決定,使我們更容易控制未知的程式碼。trait的語法雖然看起來像是介面,但它實際上更像Haskell的typeclass,特性是獨立於資料類型的存在,這使得我們可以在某種程度上附加任意特性給已有的類型。


模組可藉由存取權控制依賴關係的範圍,這讓我們得以將危險的函式或複雜的實作細節隱藏起來,而不會受到外部引用。這種隱藏細節的概念稱作封裝。合適的依賴關係範圍非常重要,它決定了我們對這個函式、類型的看法,如果它只能被很小範圍的函式引用,那我們就不用太擔心它的情境。物件導向的「封裝」把這個概念放到類別上,讓我們可以把類別的實作細節封裝在類別的定義範圍內,還增加一種只讓子類存取的控制權限。這種存取權限讓類別具有感染性,使得很多方法只能放在類別內定義,因而破壞單一職責原則。


物件導向的類別結合了定義資料結構、定義抽象行為、封裝複雜情境與實作細節三種功能。物件導向的繼承做了三件事:增加資料的成員變數、繼承抽象行為的實作、延伸抽象行為。除了定義資料結構,抽象行為應交由介面定義描述,而封裝應是模組的職責。比起擴充資料結構成員變數,組合才是組成複雜結構的最好方法,要延伸抽象行為應使用帶有約束的介面建立依賴關係。類別的每個特性在大部分情況都有其他機制可以取代,甚至可以做的更好。物件導向看起來好用只是因為它把各種功能加到一個語法上,讓我們照著某種範式設計程式時比較方便。如果把這些看似新潮的功能從類別上拔掉,只留下最基礎的機制,大部分物件導向的問題都將不復存在,而golang就是這麼做的成果。從這個結果來看,物件導向的機制真的好用嗎,還是只是你沒有理解為何要這麼設計。


trait/typeclass等特性系統依賴於靜態的類型推論,因此只有存在語言上的支援才有辦法實現。而物件導向的介面是基於子類關係的特性,只要物件本身能帶有方法就能實現,因此它很適合應用在動態類型系統上。有人說「物件導向是一種態度」,只要我們有心就可以在任何程式語言使用物件導向編程,但事實上每個程式語言都有最合適的思考方式。我認為認識物件導向的機制與函數式編程的機制之間的不同非常重要,因為語言特性能形塑我們的思考方式。我相信程式語言有一個最好的典範模式,物件導向這種直覺的思考方式在某種程度上抓到了它的部分精髓,但在很多地方搞砸了。物件導向的程式語言把物件導向的概念作為基礎機制,讓我們必須以物件導向的方式思考,但也使得我們難以意識到它在哪裡搞砸了。這種以直覺想法作為根基的機制本來就不穩固,它的機制大多依賴於某種特定的使用方式,而非提供一種能力讓我們可以做到什麼事,例如繼承機制本質上是不需要的,我們可以用原型鏈達到一樣的效果。程式語言應該根據能做到什麼事而非想要怎麼做而加入機制,如此才能抓到程式語言的典範模式,而函數式編程就是它的實現。我把它稱呼為「函數式編程」而非「函數式導向」就是因為它不是因為想怎麼做而發明出來的(至少我是這麼認為的),而是透過最大化語言特性的用處所得到的編程範式。因此語言特性的邊界特別重要,這種邊界才是形塑核心概念的關鍵,前面的文章一直在討論這些細微的差異就是這個原因。


4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
發表第一個留言支持創作者!
have bear的沙龍 的其他內容
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相
類似於trait/typeclass的特性系統能提供程式「延展性」,它能讓函式針對不同的類型做出不同的行為。這種機制與物件導向的繼承非常像,然而特性系統的彈性比較大一點,而且概念上也有一些差別。為了探討討論這些差異,我們必須深入了解繼承機制到底是什麼。 繼承並不是建立子類關係的唯一方法。所謂的
所謂的多型是讓一個函式或是資料結構能擁有多個不同的類型,其中上一篇文章所談的就是參數多型(parametric polymorphism),這篇文章將繼續討論特設多型(ad hoc polymorphism)。特設多型跟泛型的差別在於:泛型函式對於所有的類型只能有一種實作,而特設多型會根據類型有不同
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相
類似於trait/typeclass的特性系統能提供程式「延展性」,它能讓函式針對不同的類型做出不同的行為。這種機制與物件導向的繼承非常像,然而特性系統的彈性比較大一點,而且概念上也有一些差別。為了探討討論這些差異,我們必須深入了解繼承機制到底是什麼。 繼承並不是建立子類關係的唯一方法。所謂的
所謂的多型是讓一個函式或是資料結構能擁有多個不同的類型,其中上一篇文章所談的就是參數多型(parametric polymorphism),這篇文章將繼續討論特設多型(ad hoc polymorphism)。特設多型跟泛型的差別在於:泛型函式對於所有的類型只能有一種實作,而特設多型會根據類型有不同
前幾篇文章討論了類型系統的合理性,而這會影響我們對於變數與函式是什麼的理解。其中泛型是當中很重要的一個元素,很多討論都是基於泛型的使用。泛型會大大地增加類型系統的複雜度,因此有些語言選擇不提供泛型(go),但缺少泛型又會使簡單的容器都無法用類型精確描述。泛型的強大必須結合有紀律的類型系統才能顯現,但
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
你可能也想看
Google News 追蹤
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
單一職責原則(Single Responsibility Principle) 里氏替換原則(Liskov Substitution Principle) 依賴反轉原則(Dependence Inversion Principle) 最少知識原則(得墨忒耳定律)(Law Of Demeter)
Thumbnail
本筆記除了以文字說明SOLID設計原則以外,並以Java code實際舉例。 Single Responsibility Principle (SRP) 單一職責原則 每個人負責屬於自己的職責,不該承擔太多職責,大家各自做自己應該做的事情,且不會互相干擾。 續上程式碼,修改如下: Output:
Thumbnail
「繼承」顧名思義就是有一個或多個類別延續了某個類別的特性,就如同在人類社會裡,兒女接收了父母的財產、承襲了上代的技能、延續了前一輩的事業。在Python的語言裡,能夠繼承的特性為類別的屬性與方法,繼承的類別稱為子類別(child class / subclass)或衍伸類別(derived clas
Thumbnail
在類別一節中,我們可以用Student類別的實體來存取類別中的name變數、score字典、以及其中的所有方法,這些可以被類別以外的程式碼所直接存取的屬性稱為公有屬性(public attribute)、可以被類別以外的程式碼所直接呼叫的方法稱為公有方法(public method)。
Thumbnail
到目前為止,我們所學習的都是程序性的程式設計(procedural programming),也就是程式碼是透過一連串的指令組成的程序或函數,由上而下依序執行不同的程序或是呼叫函數來完成程式的功能。 Python其實是一種物件導向的程式(object oriented programming, 簡稱
Thumbnail
物件導向的概念,以python程式為範例。
Thumbnail
圖書館借的一本書,原本只是打算找Unity相關的書,但找不到的前提下隨意挑了一本書。 基本資料 書名: 作者:鄭仲平,高煥堂 出版日期:2019/01/31
也許有人聽過, Kotlin 是物件導向語言。 既然都特別提了,肯定還有其他種語言,那就是程序導向語言。 最大的差別就是因為物件導向語言有了物件的概念,所以有了三大能力:繼承(Inheritance)、封裝(Encapsulation)和多型(Polymorphism)。
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
單一職責原則(Single Responsibility Principle) 里氏替換原則(Liskov Substitution Principle) 依賴反轉原則(Dependence Inversion Principle) 最少知識原則(得墨忒耳定律)(Law Of Demeter)
Thumbnail
本筆記除了以文字說明SOLID設計原則以外,並以Java code實際舉例。 Single Responsibility Principle (SRP) 單一職責原則 每個人負責屬於自己的職責,不該承擔太多職責,大家各自做自己應該做的事情,且不會互相干擾。 續上程式碼,修改如下: Output:
Thumbnail
「繼承」顧名思義就是有一個或多個類別延續了某個類別的特性,就如同在人類社會裡,兒女接收了父母的財產、承襲了上代的技能、延續了前一輩的事業。在Python的語言裡,能夠繼承的特性為類別的屬性與方法,繼承的類別稱為子類別(child class / subclass)或衍伸類別(derived clas
Thumbnail
在類別一節中,我們可以用Student類別的實體來存取類別中的name變數、score字典、以及其中的所有方法,這些可以被類別以外的程式碼所直接存取的屬性稱為公有屬性(public attribute)、可以被類別以外的程式碼所直接呼叫的方法稱為公有方法(public method)。
Thumbnail
到目前為止,我們所學習的都是程序性的程式設計(procedural programming),也就是程式碼是透過一連串的指令組成的程序或函數,由上而下依序執行不同的程序或是呼叫函數來完成程式的功能。 Python其實是一種物件導向的程式(object oriented programming, 簡稱
Thumbnail
物件導向的概念,以python程式為範例。
Thumbnail
圖書館借的一本書,原本只是打算找Unity相關的書,但找不到的前提下隨意挑了一本書。 基本資料 書名: 作者:鄭仲平,高煥堂 出版日期:2019/01/31
也許有人聽過, Kotlin 是物件導向語言。 既然都特別提了,肯定還有其他種語言,那就是程序導向語言。 最大的差別就是因為物件導向語言有了物件的概念,所以有了三大能力:繼承(Inheritance)、封裝(Encapsulation)和多型(Polymorphism)。