物件導向不好用

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

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


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


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


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


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


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


avatar-img
4會員
28內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
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
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
兩大基礎概念 類別Class 物件Object 三大特性 封裝 繼承 多型 類別 Class 在程式語言中,類別定義一件事物的抽象特點。類別的定義包含了資料的形式(屬性, Field)以及對資料的操作(方法, Method)。我們也可以想像成類別是汽車的設計藍圖(blueprint
Thumbnail
這個單元我一直很想學習,物件導向 Object Oriented Programming 以前一直以為是一種程式碼,其實是設計程式的觀念,文中我分享了程式碼還有自己想的比喻讓讀者更好理解。除了物件導向,我還介紹其他四種風格的程式碼設計,跟大家一起學習。
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
本章節是Java入門的第八天,主要介紹物件導向的概念。這包括了類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、Lambda表達式、泛型和反射等主題。每個主題都配有相關的程式碼範例,以協助讀者更好地理解這些概念。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
※ OPP第三大核心-多型 ※ 多型的基本定義: 多型是利用繼承的特性,讓不同的子類別可以實現相同的介面,但在呼叫這些介面的方法時會表現出不同的行為。這使得程式設計更具彈性和擴展性,避免了複雜的條件判斷式,同時促進了代碼的重用。 class Animal { makeSound() {
Thumbnail
※ OPP第一大核心-封裝 封裝的精神在於將「方法」、「屬性」和「邏輯」包裝在類別裡面,透過類別的實例來實現。這樣外部物件不需要了解內部的實現細節,只需要知道如何使用該類別提供的接口即可。換句話說,封裝是將內部細節隱藏起來,只暴露必要的部分給使用者。 封裝的核心概念是,使用者如果想要接觸資料,只
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
讓我在這篇文章總結一下前面對物件導向設計的討論,我們討論了物件導向的四個特性:繼承、抽象、多型、封裝,分析了它們的問題,並跟函數式編程的思維做比較。我們引入了與之相對應的特性:泛型、特性系統、模組化,有些特性雖然跟那四個特性很像,但在一些細微的地方有不同的詮釋,使得整體思考方式很不一樣。 「繼
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
兩大基礎概念 類別Class 物件Object 三大特性 封裝 繼承 多型 類別 Class 在程式語言中,類別定義一件事物的抽象特點。類別的定義包含了資料的形式(屬性, Field)以及對資料的操作(方法, Method)。我們也可以想像成類別是汽車的設計藍圖(blueprint
Thumbnail
這個單元我一直很想學習,物件導向 Object Oriented Programming 以前一直以為是一種程式碼,其實是設計程式的觀念,文中我分享了程式碼還有自己想的比喻讓讀者更好理解。除了物件導向,我還介紹其他四種風格的程式碼設計,跟大家一起學習。
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
本章節是Java入門的第八天,主要介紹物件導向的概念。這包括了類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、Lambda表達式、泛型和反射等主題。每個主題都配有相關的程式碼範例,以協助讀者更好地理解這些概念。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
※ OPP第三大核心-多型 ※ 多型的基本定義: 多型是利用繼承的特性,讓不同的子類別可以實現相同的介面,但在呼叫這些介面的方法時會表現出不同的行為。這使得程式設計更具彈性和擴展性,避免了複雜的條件判斷式,同時促進了代碼的重用。 class Animal { makeSound() {
Thumbnail
※ OPP第一大核心-封裝 封裝的精神在於將「方法」、「屬性」和「邏輯」包裝在類別裡面,透過類別的實例來實現。這樣外部物件不需要了解內部的實現細節,只需要知道如何使用該類別提供的接口即可。換句話說,封裝是將內部細節隱藏起來,只暴露必要的部分給使用者。 封裝的核心概念是,使用者如果想要接觸資料,只
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
讓我在這篇文章總結一下前面對物件導向設計的討論,我們討論了物件導向的四個特性:繼承、抽象、多型、封裝,分析了它們的問題,並跟函數式編程的思維做比較。我們引入了與之相對應的特性:泛型、特性系統、模組化,有些特性雖然跟那四個特性很像,但在一些細微的地方有不同的詮釋,使得整體思考方式很不一樣。 「繼