人們誤解了OOP

banq發表於2019-01-09

OOP死了嗎?函式程式設計是未來嗎?有些文章似乎暗示了這一點。我傾向於不同意。來!我們討論一下!
每隔幾個月我就會看到一篇部落格文章,宣稱它已成為過去,我們都應該轉向函數語言程式設計
為什麼這些文章的作者對OOP有如此多的意見?為什麼FP似乎是如此明顯的選擇呢?

如何教授OOP
當在學校教授OOP時,它通常被教導為被這四個原則所阻礙:封裝, 繼承,抽象和多型。這通常也是討論OOP消亡的文章傾向於攻擊的專案列表。
然而,與FP一樣,OOP是一種工具。這是一份工作的工具。因此,它可以被使用和濫用。例如,如果您建立了一個錯誤的抽象,那麼您就是濫用該工具。
例如,Square正方形類應該永遠不會擴充套件 Rectangle矩形類,在程式設計意義上,它們不是繼承關係;然而在數學意義上,它們當然是相關的。矩形可以具有兩個獨立的邊長,但是正方形具有嚴格的要求,即邊都是相等的。(banq:這裡引申出根據數學常識還是人類常識進行建模的問題)

繼承
讓我們再談談繼承。繼承通常被描述為OOP的A和O. 通常會看到教科書示例,其中構建了繼承類的美妙層次結構來解決問題。但是,在實踐中,您很少使用繼承。相反,經常使用組合(banq注:聚合)。
我們來看一個例子。假設我們有一個非常簡單的類,一個Web應用程式中的控制器。大多數現代框架都會要求您執行以下操作:

class BlogController extends FrameworkAbstractController {
}


據推測,這可以很容易地進行呼叫像this.renderTemplate(...),因為它們是繼承自的FrameworkAbstractController。
正如許多文章指出的那樣,這提出了一些非常有效的問題。基類的任何內部函式現在實際上都是API。它不能再改變了。基本控制器的任何受保護變數現在或多或少都是API的一部分。
這非常容易搞砸。相反,透過組合和依賴注入,您可以這樣做:

class BlogController {
    public BlogController (
        TemplateRenderer templateRenderer
    ) {
    }
}

你知道,現在你不再依賴於一些模糊的FrameworkAbstractController東西了,但是你依賴於一個非常明確和狹隘的東西,TemplateRenderer。該BlogController居然沒有業務繼承任何其他控制器的任何東西,因為它沒有繼承任何行為。

封裝
OOP的第二個經常被攻擊的特性是封裝。良好的封裝意味著資料和功能一起傳遞,類的內部狀態對外界是隱藏的。
這也可以被使用和濫用。濫用的一個主要例子是洩漏狀態。
為了引數,我們假設List<>該類包含一個元素列表,並且可以修改該列表。讓我們建立一個購物車處理類,如下所示:

class ShoppingCart {
    private List<ShoppingCartItem> items;
    
    public List<ShoppingCartItem> getItems() {
        return this.items;
    }
}

在大多數現代OOP語言中,這裡發生的是items變數將透過引用返回。所以我可以做以下事情:

shoppingCart.getItems().clear();

這將有效地清除購物車中的物品清單,而ShoppingCart不知道“清除”這個動作。但是,如果你密切關注,這甚至不是封裝原理的錯誤。這違反 了該原則,因為ShoppingCart該類洩漏了內部狀態。

在此特定示例中,ShoppingCart類的作者可以使用不變性 來解決問題並確保不違反封裝。

無經驗的程式設計師經常違反封裝的另一種方式是引入不需要的狀態。通常沒有經驗的程式設計師使用私有類變數將資料從一個函式傳遞到同一個類中的另一個函式,而不是使用資料傳輸物件DTO將複雜結構傳遞給不同的函式。這會帶來不必要的複雜性並經常導致錯誤。

通常,儘可能避免在類中儲存狀態(儲存可變資料)是個好主意。如果我們這樣做,它應該被很好地封裝並確保它不會洩漏。

抽象化
抽象再次被誤解了。您絕不應該將程式碼填充為抽象類並進行深層次結構化。
如果你沒有任何充分理由這樣做,那你只是在尋找麻煩。抽象是作為抽象類還是介面完成並不重要,這會帶來額外的複雜性。這種複雜性必須是合理的。
在外行人看來,如果您實際上要花時間記錄實現類所期望的行為,那麼您應該只建立一個介面 。不要只寫下需要實現的函式列表,記下它們應該如何表現。

多型性
我們列表中的最後一項是多型。這意味著一個類可以實現許多行為。糟糕的教科書例子是一個正方形Square可以是一個矩形Rectangle還是一個平行四邊形Parallelogram。好吧,正如上面所討論的那樣,由於他們的行為不同,這在OOP中絕不是真的。
在談論多型時,應該考慮行為而不是程式碼。一個很好的例子就是Soldier電腦遊戲中的 類class。它可以實現Movable行為(如:它可以移動)和 Enemy行為(如:射擊你)。相反,GunEmplacement可以只實現Enemy行為。
所以,僅僅因為你可以寫Square implements Rectangle, Parallelogram,這不是真的。您的抽象需要在業務意義上實際工作。你需要考慮行為而不是程式碼。

為什麼FP不是銀彈
現在我們已經完成了這四個原則,這個函數語言程式設計是什麼,為什麼它不能解決我們所有的程式碼問題呢?

在許多FP信徒的眼中,Class是可憎的,程式碼應該被表示為函式。根據語言,資料可以使用原始型別或結構化資料集(陣列,對映等)在它們之間傳遞。
此外,大多數函式不應該有副作用。換句話說,它們不應該在後臺的某些其他位置修改某些資料,而只是處理輸入引數以產生輸出。
這種方法將資料與函式分開,乍一看與OOP方法有根本的不同。它的吸引力在於它使事情變得簡單。你想做點什麼,寫一個函式,結束故事。
當函式需要相互依賴時,問題就出現了。當函式A呼叫函式B和函式B 呼叫其他六個函式時,在鏈的末尾有一個可以突然中斷的 left-pad 函式,你有問題啦。
如果您想要程式碼變得可維護,則最好遵守清潔程式碼原則。這包括使用 依賴性反轉,這使得FP使用起來也複雜得多。

OOP還是FP?
OOP和FP是工具。你使用什麼程式設計範例並不重要。大多數文章中提出的問題都與組織程式碼有關。
對我來說,應用程式的宏觀結構更重要:模組是什麼?他們如何相互溝通?什麼是常見的資料結構?這些記錄如何?有哪些重要的業務物件?
這些都是與使用的程式設計範例無關的問題,程式設計範例甚至都沒有解決。一個優秀的程式設計師將學習正規化作為知道工具的問題,並將使用適合於給定任務的範例。

相關文章