Re: [J2SE] 談談Service的設計
已明瞭重點不在於 service 該怎麼設計,現在只針對一些細節說明(回覆)我的看法。
※ 引述《Killercat (殺人貓™)》之銘言:
: -------不過其實framework creator端是否真的沒做到轉型安全? 我們看一下
: 如果說是要完全的compile-time check safe,那那隻Supress Warning一定要消掉
: 不過仔細看就可以發現,這是完全語法上的問題,而不是轉型的問題...
: private static Map<Class<? extends IService>, IService> serviceMap = new
: HashMap<Class<? extends IService>, IService>();
: @SuppressWarnings("unchecked")
: static public <T extends IService> T getService(Class<T> clazz) {
: return (T)serviceMap.get(clazz);
: }
: 我們把最關鍵轉型的兩行貼上來看一下
: 乍看之下getService是不安全的,因為他強轉了T回去。但是,仔細看我們就會發現,
: 經由精巧的generic限制,所以我們的傳回值理論上不該強轉也該能傳回去。
: 1. T被限制住一定是IService(所以傳入錯誤的.class會編譯爆炸)
: 2. T被限制住一定試傳入的Class<T>
: 在這兩個條件的限制下去拿取service map,其實已經有足夠的明確性以及把握去指向
: 正確的instance,而這個(T)強轉其實只是語法上不得不為
: 我們在寫Generic的時候其實很多時候都會被迫莫名其妙的強轉T
: 完全只是因為compiler time沒辦法去得到T之間的關係而已,所以即使在Framework side
: 我相信這邊也是符合了型別安全的要求,而非因為SupressWarning所以有疑慮
我不明白第二點的意思。T被限制住一定是傳入的Class<T>,這與從 serviceMap 取出
一個 value 會是 T 的關聯是?
compiler 會給 warning 就是因為它沒有辦法確定他所產生的 code 能夠保證
型別上的正確。
getService method 宣稱它會傳回完全相容於參數所指定的型別的物件,而其實作
是從一個 Map 的 value 而來,依照 serviceMap 的 generics 宣告,只能確定 value
定是 IService 相容的物件(當然這裡不考慮使用 casting 亂搞的行為),getService
的實作中 compiler 所產生的 guarding code 是沒有辦法確定它一定能夠傳回相容於
參數所指定的型別,需要你要求 supress warning 並不是莫名其妙的事情。
編譯器沒有在 compile-time 做到型別上的保證,同樣地你也沒有辦法,你會覺得
你有信心還是在於你去 reasoning 你的程式流程,認定這樣子沒錯。如果這個
serviceMap 不是完全由你掌控(比如它從別處而來或是它的 reference 可擴散到
其他地方),你就不會認為這個 SupressWarning 是沒有疑慮的,不是嗎?
: : 你的做法的優點在於你把風險集中在一處,你透過讓他人遵守一個 convension,
: : 並讓轉型的風險全落在一個(或一組)較有經驗的人手上;缺點是暴露實作的細節(
: : 至少以文內的範例來說),client code 要使用一個 service 他必需要知道實作
: : 這個功能的 class 是哪一個。
: 這點其實我不太懂為什麼叫做暴露細節
: 大多數來講Service都有自己獨特的功能,除非像是CRUDService而且僅僅被限制在CRUD
: 我們來分兩部分來講,比方說你提到的以及CRUD,這種Service的特徵是
: "我服務的介面完全相同,差別僅在於我處理的Bean(我們稱為Marsheled Bean)"
:[略]
: 再來看第二種Service
: 這些Service只有部分method是每個Service都相同的
: 但是各個Service有自己獨特的不同,那我們的目標就不是"透過interface操作"
: 我們必須讓使用者看到service獨有的功能才能做事
: 就有如我blog裡面提到的第一個NameCardService
: 只有他有sort跟getDuplicated,其他Service都沒有
: 這算是惡性的expose嗎?我還真的想不到不expose的方法 XD
: 如果你說的是我們應該要有一個INameCardService的話,正好,我們現在的設計有
我認為 service(or plugin) 都是可以在 runtime 去抽換的,那麼 client 一定是
以 interface 去操作之,不可能在編譯期去知道 service 確切實作者(class)。
即便不考慮 runtime 抽換 service/plugin,client 要使用到 class name 來取用
service 的做法,不論是在開發上還是架構設計上,已放棄了許多彈性。
service 由 interface 來定義它的功能,每個 service 都是一個獨立的
interface,不會有"多種 service 由一個 interface 定義共通部分,然後特定
service 有的功能就直接將 service 視為特定 class",而是若 service B 比
service A 多一項功能,就應該有一個 interface 是比 service A interface 多
一項功能的 interface 來定義 service B。(多個 service interface 彼此間可以
行成一組階層)
: : 如果目標是讓 client 只透過 initerface 使用 service,在取用 service 時又
: : 不需要指名道姓實作 service 的 class,那麼你這樣的設計該怎麼去改良以
: : 符合目標?
我提出這一點的目的是:依你的範例來說它是個很單純的(陽春),可是即便如此你
還是用到兩個 SupressWarning,這 OK,若你能夠驗證你的設計定不會出錯。
可是若考量其他的需求來改良你的設計,當它變得複雜了,你可能會發現利用參數
來 infer return type 的做法,變得不太容易寫,SupressWarning 越來越難避免
且 SupressWarning 會擴散,情況不再是只有你需要 SupressWarning 你確定它是
無害的,也許 service 的實作者都需要用到 SupressWarning,那麼你就要要求
他們也能夠保證他們的 code 的正確性,否則你當初希望 client 可以很簡單地
取用到有正確編譯期型別的目標就失去了。
*利用 argument 來 infer return type 的做法不算罕見,是不多但我認為這是
因為能這樣用的場合很有限,要寫多這樣的設計又不用到 SupressWarning 更是少,
如果要求實作中所用到的 code(library, core classes)的實作也不使用
SupressWarning 則更少。
在很單純的應用中的話,想要 client 在取用 service 可以有正確的編譯期型別,
為什麼不考慮由你來寫個 Services 這樣的 utility class 來給 client 使用。
class Services {
IServiceA getServiceA(...);
IServiceB getServiceB(...);
...
}
由你來提供這些 method 裡的 wrapper code。
或者是設計像 ServiceRegistry 這種東西,有註冊/取得各種 Service 介面的實作品
的 method,實際使用註冊部分的 code 由你來寫,也可以簡單做到你要的效果。
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 1.172.243.180
※ 編輯: sbrhsieh 來自: 1.172.243.180 (07/21 03:30)
討論串 (同標題文章)