Re: [請益] Spring boot的依賴注入降低耦合的例子

看板Soft_Job (軟體人)作者 (最後的六年級生)時間3年前 (2022/04/15 09:46), 3年前編輯推噓20(20058)
留言78則, 17人參與, 3年前最新討論串7/8 (看更多)
※ 引述《ntpuisbest (阿龍)》之銘言: : 推文有個連結有解答我的疑惑 : 感謝bron大 : 文章有點長 : 先說說我對依賴注入的理解 : Spring boot : 依賴注入大致有三種方式 : 透過建構子的 透過setter的 或是 field : 這三種都可以透過@Autowired註解來達到依賴注入的效果 在這個時代依賴注入最重要的用途,特別是在後端開發是讓Application 在多個不同的 環境下(Development, Production, local, etc) 能夠根據profile 組出能正確執行的Application 多型在這裡當然有他的地位,但是一般來說,大部分不接觸system boundary的service objects 是不太需要多型的,如果是java,那種一個interface 只有一個implementation class 的情況其實就代表著那個interface 沒有用 倒是如何搭配Annotation 與aspect programming在後端開發是更重要的 : 我自己想到的建構子的舉例是 : 假設有兩個類 Address 和 Employee好了 : 1. : public class Address { : String Country; : String City; : String Street; : public Address(String country, String city, String street) { : Country = country; : City = city; : Street = street; : } : } : 2. : public class Employee { : String sex; : String name; : Address address; : // 沒有依賴注入的方式 : public Employee(String Country,String City,String Street,String : sex, String name ) { : this.sex=sex; : this.address = new Address( Country, City,Street ); : this.name=name; : } : // 有依賴注入的方式 : public Employee(String sex, String name, Address address) { : this.sex = sex; : this.name = name; : this.address = address; : } : } : 在上面的例子當中可以發現,如果哪一天 : Address這個類新增了一個屬性叫 phoneNumber好了 : 沒有依賴注入的方式,必須要更改 Employee 的 : this.address =new Address(Country,City,Street,phoneNumber) : 而有依賴注入的方式確實降低了耦合 : 因為他不用更改Employee的建構方式 : 所以我理解依賴注入可以降低耦合 這是資料物件,資料物件頂多實現封裝而已,這也不是什麼依賴注入,這只是單純 的物件組裝 當你說降低『耦合』的時候,你覺得是什麼東西的『耦合』需要降低? : 但我的問題是Spring boot 的 autowird annotation 有幫助我們降低耦合嗎 : 在常見的開發中 我們經常都會有 Dao 以及 Service : 假設我有兩個 Dao 好了 分別是 Dao1 和 Dao2 : 以及一個Service : Dao1 : public class Dao { : public void sayhi() { : System.out.println("hello"); : } : } : Dao2 : public class Dao2 { : public void saygoodbye() { : System.out.println("say goodbye"); : } : } Data Access Object 是要去存取Data source的,你取這個名字,就表示後面存在 某種DB、file 等總之是需要透過IO去存取獲得資料的東西 如果你的service 就是需要存取特定的Data source 才能工作,那你當然沒辦法 讓這兩個Dao 消失啊,你頂多再插入一層service ,把他們封裝進其他的service object 裡 : 如果我不在service上面使用autowired : 我的service會是 : public class Service { : Dao1 dao=new Dao1(); : Dao2 dao2=new Dao2(); : public void sayhi() { : dao.sayhi(); : } : public void saygoodbye() { : dao2.saygoodbye(); : } : } : 如果我使用了@Autowired註解 : 那我只是將 : Dao1 dao=new Dao1(); : Dao2 dao2=new Dao2(); : 替換成 : @Autowired : Dao1 dao : @Autowired : Dao2 dao2 : 我想請問所以我使用了Autowired註解 : 我知道我可以不需要使用new 來建構實體 : 但 Spring 真的有幫我降低耦合嗎 : 即使我換成 setter 配合 autowired的方式好了 : 那個 setter也是要我自己去撰寫 : Spring 幫我降低了耦合甚麼? Spring 在這裡幫你: 1. 把物件組裝起來 2. DAO 是要存取Data source 的,所以會開Connection,有Connection 就有 lifecycle 要管,而Spring 可以幫你把lifecycle 管好 3. 某些Datasource 的互動是需要transaction 來保證資料一致性的,Spring 也幫你做了 4. localhost、Dev、Production 要連不同的datasource,要開大小不同的connection pool 這Spring 幫你做了 5. 哪裡連不上出差錯了,Spring 報給你知 他幫你做了這麼多、唯一沒辦法幫你做的就是你期待的降低耦合(事實上你期待的是 零耦合),但因為你的service 業務邏輯實作就是需要跟這兩個Dao 互動啊,那是要 怎麼降? 問題不是出在Spring,而是出在你對Framework 有錯誤的認識與期待 更深入探討,如果你不用Spring 這類的DI 容器,那你肯定就要自己Construct 這兩個 DAO,如果你直接在你的Service constructor 創建這兩個Dao,那我列的那五件事, 就得完全歸service 自己管了,在採用Spring 以至於你不用擔心dao 到底該怎麼baby sitting就可以像自來水電一樣轉開就用的情況下,你的service 就已經在一個夠低的 耦合水平下與這兩個dao 互動了 吃米要知道米價啊 : 我的問題簡單來說就是 : 我知道依賴注入可以降低耦合 : 但Spring boot透過 @Autowired註解幫我降低耦合在哪 : 謝謝 : p.s 因為面試的時候常常被面試官問說懂不懂甚麼是 : 控制反轉還有DI,我基本上舉例都舉 Address還有 Employee的例子 你舉這個例子,我是面試官大概就會直接當作你不懂 因為Address 還有Employee 都是資料物件,而控制反轉與DI要解決的問題是: 『如何組織程序?如何把系統中許許多多需要執行的procedure適當的切分,然後 在適當的地方、時機,可以有適當的Context去執行?』 這與資料物件如何透過實現多型而可以任意組裝沒有關係 我們要注入給Framework或是Container 的不是資料物件,而是行為物件,是那種suffix 會叫做:Filter、Listener、Consumer、Interceptor 的『行為』物件 你要我舉例,我大概會用HttpClient 當例子 當我的系統中到處都用得上httpClient,而且他需要可以Configure Circuit Breaker 、有Rate Limiter、遇到response error 知道要retry、而且有預設的Http status handling 但仍舊可以客製化、response deserialization 要可以從json 與form間 自由切換,然後系統中每個地方用HttpClient 的情境都不太一樣,請問要怎麼設計這 個HttpClient? : 但當我反問下面例子的時候,他們好像也說要再回去想一下... : 只有其中一個就說更複雜的例子會用到,但也沒說甚麼是更複雜的例子QQ 控制反轉在實作上,最直白的做法,就是業務邏輯不是由programmer 直接撰寫 imperative procedure 執行,而是把這些業務邏輯(一般實現成各種services)包裹 成各式各樣代表行為的物件,比如說:Listener、Consumer、Filter 、Interceptor、 visitor 等等,然後實際在什麼時間點、在什麼地方這些物件會被呼叫是由Application 內某種processor、Container 根據當初的Configuration來決定 這些東西應該書上都有寫的,我建議你還是要找本書好好的看過一遍才比較會有全面 的認識 -- 『你知道人有腦子,所以不要只是單純的滿足它,偶爾也要使用它啊。』 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 67.188.38.188 (美國) ※ 文章網址: https://www.ptt.cc/bbs/Soft_Job/M.1649987202.A.87B.html ※ 編輯: zanyking (67.188.38.188 美國), 04/15/2022 09:47:47

04/15 10:36, 3年前 , 1F
推喔我會舉那個例子是因為蠻多部落格或是stackoverflo
04/15 10:36, 1F

04/15 10:36, 3年前 , 2F
w都是說di
04/15 10:36, 2F

04/15 10:36, 3年前 , 3F
分成三種注入,field setter還有constructer
04/15 10:36, 3F

04/15 10:38, 3年前 , 4F

04/15 10:38, 3年前 , 5F

04/15 10:38, 3年前 , 6F
s-dependency-injection-and-inversion-of-control-in
04/15 10:38, 6F

04/15 10:38, 3年前 , 7F
-spring-framework
04/15 10:38, 7F

04/15 10:39, 3年前 , 8F
看起來外國人說的好像也不見得對
04/15 10:39, 8F
Blog文章舉例舉不好,但他們有很多的篇幅去講他們真正想講的東西 你去面試,面試官是聽你說而不是看你寫的文章,他沒有辦法來回重複聽你講的東西 所以用對例子在你的情境就比寫文章的重要性要大得太多了

04/15 11:15, 3年前 , 9F
釣出大神
04/15 11:15, 9F

04/15 11:36, 3年前 , 10F
可以從各模組內含的知識與責任來看這件事
04/15 11:36, 10F

04/15 11:36, 3年前 , 11F
即使只有一個實作,interface 抽象化在知識面上還是有用
04/15 11:36, 11F

04/15 11:37, 3年前 , 12F
DI 主要價值在於 dependency 下游不用認識上游實作
04/15 11:37, 12F

04/15 11:40, 3年前 , 13F
所以認真講有幫到解耦也不能說它錯,只是不用太糾結這點 XD
04/15 11:40, 13F

04/15 12:25, 3年前 , 14F
DI核心還是在於不用顧慮class內的其他實例只需專注介面
04/15 12:25, 14F
你說的沒有錯,問題在於如果跟不懂的人用這個說法,他也還是不會懂 所以我才花了大篇幅用他的Dao 去說明他的service 哪邊已經透過Spring 獲得好處了

04/15 12:38, 3年前 , 15F
通常只有一個業務實作的情況,另一個實作是MOCK
04/15 12:38, 15F

04/15 19:57, 3年前 , 16F
推這篇感覺比較有打到我的點
04/15 19:57, 16F

04/15 21:35, 3年前 , 17F
非常同意第二段 太多工程師一天到晚要抽象 要interface
04/15 21:35, 17F

04/15 21:35, 3年前 , 18F
但根本不知道介面的真正用途為何 有時候看到一個module
04/15 21:35, 18F

04/15 21:35, 3年前 , 19F
裡 明明很多元件都沒有expose給其他module使用 也被inter
04/15 21:35, 19F

04/15 21:36, 3年前 , 20F
face的到處都是 然後問那些工程師為什麼這邊要介面 每個
04/15 21:36, 20F

04/15 21:36, 3年前 , 21F
都說clean code 但問他到底哪裡clean了 就支支吾吾了...
04/15 21:36, 21F

04/15 21:43, 3年前 , 22F
同意樓上,一個介面一個實體這種設計到底有什麼必要…
04/15 21:43, 22F

04/15 23:11, 3年前 , 23F
等等...你們的franework mock都不用interface?
04/15 23:11, 23F

04/15 23:30, 3年前 , 24F
如果用interface的目的是單純為了mock... 這我不太認同
04/15 23:30, 24F

04/15 23:30, 3年前 , 25F
啦.. 如果composition做得好 DI graph漂亮 你不需要inter
04/15 23:30, 25F

04/15 23:30, 3年前 , 26F
face也能測試 我以前也有一樣的想法 有interface mock起
04/15 23:30, 26F

04/15 23:30, 3年前 , 27F
來很爽 後來還因為這原因跟另一位資深的同事爭執了一陣子
04/15 23:30, 27F

04/15 23:30, 3年前 , 28F
後來才發現我是錯的..
04/15 23:30, 28F

04/15 23:32, 3年前 , 29F
不用interface就用cglib動態代理生成mock物件
04/15 23:32, 29F

04/15 23:33, 3年前 , 30F
如果讓程式去迎合測試 本身很容易進入誤區 而是應該思考
04/15 23:33, 30F

04/15 23:33, 3年前 , 31F
怎麼在省去所有不必要的介面以及抽象化時 還能完整的測
04/15 23:33, 31F

04/15 23:33, 3年前 , 32F
04/15 23:33, 32F
我自己做開發的原則:邊界存在所以介面才存在,觀察不到邊界就不該亂創介面 而邊界存在是因為有兩個不同的域(抱歉我找不到合適的詞彙)需要互動,而必須訂出雙方 都能同意去使用的一套語言,也就是一系列行為與資料的組合 為了測試去開新的interface 也不是不行,但通常會發生在系統對外的交界處,比如說 對DB、對Downstream service 的呼叫的那些client or repository

04/15 23:41, 3年前 , 33F
我覺得介面主要的用處不在於有幾個實作, 即使 0 個都行
04/15 23:41, 33F

04/15 23:42, 3年前 , 34F
主要看兩點, 一是有沒有提供對某部份系統恰當的定義, 二
04/15 23:42, 34F

04/15 23:43, 3年前 , 35F
是所定義的範圍是不是在恰當的邊界
04/15 23:43, 35F

04/15 23:44, 3年前 , 36F
以 JPA 為例, 就是提供了 APP 及資料庫間的定義
04/15 23:44, 36F
同意你的定義,但不太可能零個實作,如果是SPI的情況,那還是預期有實作可以介入的 如果是透過annotation 或Naming convention 搞meta programming,後面都還是有 code generator在生成行為的,所以有實作,只是不是programmer 自己寫而已

04/16 00:01, 3年前 , 37F
樓上正解 如果有跨模組間的使用需求 介面的使用是必要的
04/16 00:01, 37F

04/16 00:01, 3年前 , 38F
同時也能縮短dependence間的critical path 然而很常看
04/16 00:01, 38F

04/16 00:01, 3年前 , 39F
到一個類別只有一個實作 也沒有跨模組間的使用 還偏偏要
04/16 00:01, 39F

04/16 00:01, 3年前 , 40F
介面化... 就讓人很無語
04/16 00:01, 40F
我工作的地方有些team 的工作習慣是開interface 就得加個I當開頭 所以我code review 的時候就常常碎碎唸: 你們這個開一個class 就要I一下到底是什麼病? ※ 編輯: zanyking (67.188.38.188 美國), 04/16/2022 06:00:21

04/16 07:34, 3年前 , 41F
HttpClient的例子好貼切,不過寫不夠多或是沒有遇到情
04/16 07:34, 41F

04/16 07:34, 3年前 , 42F
境,光看書或解釋,真的不太能體會,所以我也很常用moc
04/16 07:34, 42F

04/16 07:34, 3年前 , 43F
k當說法
04/16 07:34, 43F

04/16 08:14, 3年前 , 44F
每個class都要I個大概也是職業病了,cglib 動態代理幫你I
04/16 08:14, 44F

04/16 08:14, 3年前 , 45F
好I滿,但也得看框架運用場合適用
04/16 08:14, 45F

04/16 12:50, 3年前 , 46F
一個interface只有一個implement 不是為了讓interface 可
04/16 12:50, 46F

04/16 12:50, 3年前 , 47F
以作為參數被傳遞嗎?好像叫做behaviour parameterizatio
04/16 12:50, 47F

04/16 12:50, 3年前 , 48F
ns ,重點在於能夠reused same method and give it diffe
04/16 12:50, 48F

04/16 12:50, 3年前 , 49F
rent behaviors.
04/16 12:50, 49F

04/16 12:57, 3年前 , 50F
主要是回覆發文者說「一個interface 只有一個實作沒用」
04/16 12:57, 50F

04/16 12:57, 3年前 , 51F
的這句話,因為之前有看過書(java in action)有強調這個
04/16 12:57, 51F

04/16 12:57, 3年前 , 52F
用法。
04/16 12:57, 52F

04/16 14:20, 3年前 , 53F
reuse same method and give it diff behavior. 就表示這
04/16 14:20, 53F

04/16 14:20, 3年前 , 54F
個介面 在不同的情況下 會有不同的行為 但如果確定只有一
04/16 14:20, 54F

04/16 14:20, 3年前 , 55F
個實作 就表示這個介面永遠只會有一個行為 那介面的意義
04/16 14:20, 55F

04/16 14:20, 3年前 , 56F
不大 除非是這個介面會被其他跨模組的功能所使用 那介面
04/16 14:20, 56F

04/16 14:20, 3年前 , 57F
的確需要 當然也有的人會習慣都預先做好介面化 以便將來s
04/16 14:20, 57F

04/16 14:20, 3年前 , 58F
cale的時候不需要再花時間 這也是可理解的 最怕的是有一
04/16 14:20, 58F

04/16 14:20, 3年前 , 59F
些很愛什麼都要interface, abstract 類繼承的depth超深
04/16 14:20, 59F

04/16 14:20, 3年前 , 60F
但最後都只有一個實作... 然後也沒有模組化因為什麼東
04/16 14:20, 60F

04/16 14:20, 3年前 , 61F
西都塞在一個module裡 這種的看了頭真的很痛...
04/16 14:20, 61F

04/16 14:33, 3年前 , 62F
推樓上
04/16 14:33, 62F

04/16 14:33, 3年前 , 63F
但很多大廠的SDK也是都這樣就是了
04/16 14:33, 63F

04/16 14:34, 3年前 , 64F
包含古哥~~~~
04/16 14:34, 64F

04/16 16:09, 3年前 , 65F
推baobomb大的說法,其實我也曾寫文章聊這件事情
04/16 16:09, 65F

04/16 16:09, 3年前 , 66F
讓我偷渡一下 https://reurl.cc/Dyn8Gm
04/16 16:09, 66F

04/16 19:41, 3年前 , 67F
介面一定要I開頭這點真的是…Cleancode第一章命名就直
04/16 19:41, 67F

04/16 19:41, 3年前 , 68F
接強調不要這樣了XD 但是舊程式碼很常見
04/16 19:41, 68F

04/16 19:43, 3年前 , 69F
另外推樓上大大說的,這也是舊程式碼常見的一個介面一
04/16 19:43, 69F

04/16 19:43, 3年前 , 70F
個實作的狀況…
04/16 19:43, 70F

04/17 02:05, 3年前 , 71F
哇....現在才知道只有一個實作的介面是錯的
04/17 02:05, 71F

04/17 09:35, 3年前 , 72F
嚴格來說應該是像樓主所說 介面的必要性在於恰當的邊界
04/17 09:35, 72F

04/17 09:35, 3年前 , 73F
如果合理 那只有一個實作也是合理的 但不要為了介面而
04/17 09:35, 73F

04/17 09:35, 3年前 , 74F
介面
04/17 09:35, 74F

04/18 02:36, 3年前 , 75F
其實就是對耦合這個字有誤解 但是表達應該是維護的方
04/18 02:36, 75F

04/18 02:39, 3年前 , 76F
便 spring做了不代表是絕對的最佳範例 根據需要更改
04/18 02:39, 76F

04/18 02:44, 3年前 , 77F
沒錯 但有些人會認爲這樣一勞永逸 個人不理解 因爲擴
04/18 02:44, 77F

04/18 02:44, 3年前 , 78F
充性有很多方法
04/18 02:44, 78F
文章代碼(AID): #1YMCw2Xx (Soft_Job)
討論串 (同標題文章)
文章代碼(AID): #1YMCw2Xx (Soft_Job)