方格精選

閒談軟體設計:發生關係

閱讀時間約 8 分鐘
圖片來源:www.freepik.com

圖片來源:www.freepik.com

上一篇閒談軟體設計:友善的距離意外在 Facebook 上引起不少人的回應 (是的,我不但偷偷加了副標,還擴充了些內容),不過首先要澄清一下,接下來一系列文章 (如果能堅持住繼續寫的話),我不會討論哪個架構比較好,情境不同,本來對架構的選擇就不同,架構師本身也有所謂的偏好,所以討論哪種架構比較好,這種吃力不討好的筆戰,只是自找麻煩。因此,我只是把在一個抽象的架構中實作上可能會遇到的技術選擇或是設計選擇的種種因素放在文章中讓大家思考,嗯,通常我也不會寫答案,頂多是某些過去從經實驗的經驗分享。

關係無法避免

在落落長的引言結束,還是回到本文來吧!如果覺得這篇文章的副標題有點エロ的人,應該都是被標題騙進來的,如果是被騙進來的,可以按上一頁離開,或繼續看完假裝自己不是XD,好啦,玩笑開完了。不管用哪種語言開發軟體,除非是那種一個 function 寫個幾萬行的人 (來人啊,把這種人拖去砍了),不然,一般都會根據某些因素,切割成模組或是特定功能的區塊 (一個 class 或是一個 function),但要完成一個特定功能,這些模組或區塊勢必要一起合作,因此這些模組與區塊就發生了關係

以目前主流的語言來說,大多可以用物件的方式描述可被使用的區塊,UML 的 class diagram 將物件之間的關係分成兩大類:實體階層的關係與類別階層的關係,實體階層有 dependency、association、aggregation 和 composition 四種,類別階層有 generalization 和 realization (implementation) 兩種,共計六種,雖說是六種,但在不同語言哩,實現的方式也不逕相同,所以我也不打算談怎樣的實作才算是哪一種關係。

我們就單純討論該如何讓一個物件知道另一個物件?你在開玩笑嗎?這不是很簡單嗎?別看這這樣,Martin Fowler 可是寫了篇 《Inversion of Control Containers and the Dependency Injection pattern》文章描述幾種 dependency injection 的方法: constructor injection、setter injection 和 interface injection,以及用來查找物件的 service locator。不過既然 Martin Fowler 寫得這麼詳細了,我又有什麼好寫的呢?就三個:自己的習慣、 container-based annotated dependency injection 和 singleton,後面兩種是我覺得有意思的東西。

個人偏好

先看我自己的習慣吧!我通常將必要的物件關係使用 constructor injection,例如 A 物件需要 B 和 C 物件才能運作,便會在 constructor 宣告 B 和 C 物件的參數,在建立 A 物件時,就要傳入 B 與 C 物件,但 constructor injection 會有缺點,首先,當需要的物件關係越多,常會造成 constructor 出現 long parameter list 的 bad smell,不過這也好,提醒自己,代表著 A 物件似乎做太多事了,可能也出現 low cohesion 的問題;再者,是建立物件的先後順序會受限,甚至會出現建立 A 物件時需要物件 C,建立 C 物件可能會需要 D 物件,但建立 D 物件時需要 A 物件,此時就出現雞生蛋、蛋生雞的問題了,這時,就只能用 setter injection 解開這個迴圈了。

Setter injection 則用在預計執行期間會換掉的關係上,例如根據外部輸入的條件,更換不同的演算法,像是根據購買物品的種類或是數量,使用不同折扣計算的演算法。至於,interface injection 則只用過二次,一次是使用 plug-in 架構時,為了讓 plug-in 能取得可能需要的 host resource 物件,只要載入的 plug-in 有實作特定 interface,就替該 plug-in 注入所需的物件 (參閱閒談軟體設計:Plug-in)。至於另一次經驗,等以後有機會再提。

接著看 Container-based annotated dependency injection,有在用 Spring framework 或是使用 J2EE container 的開發者對於 @Autowired 或是 @Inject 等 annotation 應該很眼熟,自己在用 Spring framework 開發 Web service 時也用很多,像下面的程式,只要一個單純的 annotation,Spring framework 就會幫開發者注入合適的物件:

但要能讓 Spring framework 注入物件, UserManager 本身必須是個 Spring bean 物件,不論是用 @Bean 或是用 xml 讓 Spring framework 將 UserManager 建立為 bean 物件,這些關係的注入才會生效。就如同《Spring Boot in Action》書中所說的:

Like any framework, Spring does a lot for you, but it demands that you do a lot for it in return.

不過,就如前篇閒談軟體設計:友善的距離,我對框架都會保持友善的距離,因此,我很少會向上例那樣,直接將 @Autowired 這類框架專屬的 annotation 加到 domain 物件中,那該怎麼辦呢?一般來說 domain 物件我習慣放在有 -core 後綴詞修飾的 projet 中,真正提供服務或是與 Spring framework 整合的物件則放在 -ws 或 -spring 修飾的對應 project 中,例如下圖:

raw-image

UserManager 在 acl-core 的 project 中,使用 Spring framework 提供 Web Service 服務的 UserManagerBean 則是在 acl-spring 的 project 中。

雖然,UserManagerBean 沒有任何特殊的 method,只是一個單純的繼承,有點多餘的感覺,卻也多了點距離。另外,也讓測試變得簡單一點,因為測試時,不需要 Spring framework 的介入,在以前還沒有 SSD 的時候,光是等待 Spring framework 啟動然後注入物件就要十幾秒,但一個單元測試 method 可能才執行 0.x 秒,即便有 SSD 將啟動時間縮短到數秒,整體來說,這個代價實在太高了,而且當測試越多,代價就跟著提高。所以測試 domain 物件的邏輯時,雖然 annotation 很方便,我還是喜歡回歸到單純的 constructor injection 或 setter injection。

最後,就是 singleton 了,我很刻意不使用的 design pattern,或是說,我通常只有在真的只允許一個物件實體的情況下才會使用,但若看網路上 iOS 或 Android 大量的範例程式碼,singleton 被大量被當成 service locator 使用,特別是 Activity 是一個生命週期完全被 Android 掌控的物件,開發者既無法自己建立 Activity 物件,也不知道該在什麼時候使用 setter injection 注入所需要的 domain object,或是想在不同的 Activity 間傳遞複雜的物件,又或是不想一路傳遞物件到較深的物件中,於是能以 static 方式或是從全域取得物件的 singleton 就被大量使用了,但我覺得這完全不是 singleton 原本的意圖

在最近的專案中,我試著用 interface injection 的方式,搭配 Android 對 Activity 的 lifecycle callback,在有實作特定 interface 的 Activity 注入需要的物件,同樣地,Fragment 也能用這種方式注入物件,因此不需要依賴 singleton 作為 service locator。

小結

以上,就是和大家分享的三個有趣的設計決策思考。雖然,目前規劃中的主題,大概還有四篇,如果平時還有想到有趣的東西會再加入,但如果有希望我分享和討論的主題,可以留言給我,若能幫上忙,有空就分享出來。

avatar-img
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Spirit的沙龍 的其他內容
這本書其實是參加 Agile Taipei 2018 時買的,還跟作者簽名合照,回到家後很『不』快地看完,大概是因為自己喜歡待在新創公司,有點難體會『大』企業的轉型困難點,現在回頭看一下當年畫的筆記,多了不少感受。
這書是在 Agile Meetup 2016年二月聚會聽完作者本人的演講才知道的,但手上的書一堆,也就一直沒有再買進,最近碰巧想試著練練鋼筆字養心,就跟著練字帖一起買回來看,因為已經聽過演講,看起來也就很快,但這次感觸卻更多,因為裡面很多內容都可以套用在自己身上,試著去分析自己的盲點,挺有意思的。
工作中,Scrum 跑的對不對,不是最重要的事了。在看這一本書時,想到的大多是 2016 在某公司推廣 Scrum 的經歷,很多是在這本書都提到了,很適合想要推廣敏捷前,先讀的一本好書。
不重新造輪子,我們使用第三方函式庫,聽起來很合理,但每個被引入的函式庫意味著一種 coupling,看到套件管理工具下載眾多第三方函式庫,意味著不用重寫這些東西,開發效率能提升數倍甚至數百倍,但我們真的都能掌握這些 coupling 嗎?當這其中任何一個環節出錯,我們的系統架構真的很優雅地應付嗎?
Offline first 的設計最近有越來越多的感覺,但好的 Offline first 設計要解決蠻多的問題,是否使用 offline first 設計真的需要好好思考,不然可能得不到好處,反而還引起一堆 bug,本篇先探討在 client 端可能會遇到的問題與一些可能的解法。
會後,我與其中一位創辦人聊聊他們 scrum 怎麼跑,以及程式、美術與企劃,這三種技能差異甚大的成員怎麼合作,他也苦笑,其實他們也花了很多時間磨合,但我們都提到,要引導團隊需要的不是 process,而是很多的軟技能,讓團隊自己能夠成長。
這本書其實是參加 Agile Taipei 2018 時買的,還跟作者簽名合照,回到家後很『不』快地看完,大概是因為自己喜歡待在新創公司,有點難體會『大』企業的轉型困難點,現在回頭看一下當年畫的筆記,多了不少感受。
這書是在 Agile Meetup 2016年二月聚會聽完作者本人的演講才知道的,但手上的書一堆,也就一直沒有再買進,最近碰巧想試著練練鋼筆字養心,就跟著練字帖一起買回來看,因為已經聽過演講,看起來也就很快,但這次感觸卻更多,因為裡面很多內容都可以套用在自己身上,試著去分析自己的盲點,挺有意思的。
工作中,Scrum 跑的對不對,不是最重要的事了。在看這一本書時,想到的大多是 2016 在某公司推廣 Scrum 的經歷,很多是在這本書都提到了,很適合想要推廣敏捷前,先讀的一本好書。
不重新造輪子,我們使用第三方函式庫,聽起來很合理,但每個被引入的函式庫意味著一種 coupling,看到套件管理工具下載眾多第三方函式庫,意味著不用重寫這些東西,開發效率能提升數倍甚至數百倍,但我們真的都能掌握這些 coupling 嗎?當這其中任何一個環節出錯,我們的系統架構真的很優雅地應付嗎?
Offline first 的設計最近有越來越多的感覺,但好的 Offline first 設計要解決蠻多的問題,是否使用 offline first 設計真的需要好好思考,不然可能得不到好處,反而還引起一堆 bug,本篇先探討在 client 端可能會遇到的問題與一些可能的解法。
會後,我與其中一位創辦人聊聊他們 scrum 怎麼跑,以及程式、美術與企劃,這三種技能差異甚大的成員怎麼合作,他也苦笑,其實他們也花了很多時間磨合,但我們都提到,要引導團隊需要的不是 process,而是很多的軟技能,讓團隊自己能夠成長。
你可能也想看
Google News 追蹤
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
本章節是Java入門的第八天,主要介紹物件導向的概念。這包括了類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、Lambda表達式、泛型和反射等主題。每個主題都配有相關的程式碼範例,以協助讀者更好地理解這些概念。
Thumbnail
這個章節主要介紹了Swift程式語言中物件導向程式設計的基本概念,包括類別、建構子、公開、私有、受保護等等的概念。同時,也介紹了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射等進階特性。
Thumbnail
這篇文章介紹了面試時以及開始工作後可能會遇到的問題,包括物件導向OOP、SOLID 設計原則、測試方式,以及 Cookie、Session 與 Cache 的相似處與不同處。提供了豐富的相關資訊。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
※ OPP第一大核心-封裝 封裝的精神在於將「方法」、「屬性」和「邏輯」包裝在類別裡面,透過類別的實例來實現。這樣外部物件不需要了解內部的實現細節,只需要知道如何使用該類別提供的接口即可。換句話說,封裝是將內部細節隱藏起來,只暴露必要的部分給使用者。 封裝的核心概念是,使用者如果想要接觸資料,只
※ Object(物件) & Constructor Function(建構式函式) Object(物件)是什麼? 物件是一種「可以將資料、程式碼包含在其中」的資料結構。 Object(物件)的兩種創造方式: 匿名物件:直接使用"{}"。沒有特別的名字,直接從Object中繼承過來的一個物件
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。
Thumbnail
本階段深掘PHP中類別與物件的應用,從基本定義到屬性與方法的運用,並特別著重於訪問控制和靜態成員的概念。學生將學會如何有效地利用公開、保護、私有屬性,以及如何在不實例化的情況下透過類別名稱直接訪問靜態屬性和方法,進一步鞏固物件導向程式設計的核心知識。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
本章節是Java入門的第八天,主要介紹物件導向的概念。這包括了類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、Lambda表達式、泛型和反射等主題。每個主題都配有相關的程式碼範例,以協助讀者更好地理解這些概念。
Thumbnail
這個章節主要介紹了Swift程式語言中物件導向程式設計的基本概念,包括類別、建構子、公開、私有、受保護等等的概念。同時,也介紹了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射等進階特性。
Thumbnail
這篇文章介紹了面試時以及開始工作後可能會遇到的問題,包括物件導向OOP、SOLID 設計原則、測試方式,以及 Cookie、Session 與 Cache 的相似處與不同處。提供了豐富的相關資訊。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
※ OPP第一大核心-封裝 封裝的精神在於將「方法」、「屬性」和「邏輯」包裝在類別裡面,透過類別的實例來實現。這樣外部物件不需要了解內部的實現細節,只需要知道如何使用該類別提供的接口即可。換句話說,封裝是將內部細節隱藏起來,只暴露必要的部分給使用者。 封裝的核心概念是,使用者如果想要接觸資料,只
※ Object(物件) & Constructor Function(建構式函式) Object(物件)是什麼? 物件是一種「可以將資料、程式碼包含在其中」的資料結構。 Object(物件)的兩種創造方式: 匿名物件:直接使用"{}"。沒有特別的名字,直接從Object中繼承過來的一個物件
Thumbnail
在物件導向程式設計的進階階段,學生將學習繼承、介面、抽象類別等核心概念。繼承允許類別共享屬性和方法,介面確保實現類別提供特定的方法實現,而抽象類別定義了基本結構供子類別擴展。這些知識點有助於提升程式碼的重用性、擴展性和維護性。
Thumbnail
本階段深掘PHP中類別與物件的應用,從基本定義到屬性與方法的運用,並特別著重於訪問控制和靜態成員的概念。學生將學會如何有效地利用公開、保護、私有屬性,以及如何在不實例化的情況下透過類別名稱直接訪問靜態屬性和方法,進一步鞏固物件導向程式設計的核心知識。
物件導向設計的一個重點就是封裝,這有很多層面上的意義,但基本上就是控制物件的成員變數和方法的存取權。物件導向的封裝還跟繼承機制有關,這使得有一些時候我們逼不得已必須把函式定義在類別上,這種做法使得物件的功能變得難以拆解。封裝應該是模組的職責,並不需要再給物件相同的能力。 一般的模組系統就是把相