Re: [J2SE] 談談Service的設計
我是覺得這篇的重點在於
static public <T extends Object> T getXXXX(Class<T> clazz)
然後這行程式碼要放在哪裡?
java管理物件因為靜態特性的原因,導致整天轉來轉去的
導入泛型之後,算是解決這方面的困擾
然後加入injection之後,對於物件管理就更好了
不過我從本篇的範例
看到實作物件及實作管理的解法:繼承
將那行程式碼利用繼承放到抽象那層,並透過建構子的方式來管理物件
但我自己是會採用組合方式的建構工廠樣式的方式來提供
例如推文提到的google guice中的範例
Injector injector = Guice.createInjector(new BillingModule());
BillingService billingService = injector.getInstance(BillingService.class);
管理物件還是交由某個專門物件來負責,而且也不需要因為要管理物件而繼承
因為不管是service handler也好
其繼承的理由不應該是為管理其子類別或是該package底下的物件
而利用物件的建構式來作為物件管理的入口也容易會遭到繼承的實作破壞
而物件管理也必要得要透過該物件的建構式才能的話,也是有點不確定
因為我想物件實體的產生應該會有特定時機跟限制
也就是不會沒事就去new一下,也不會到處都在new,或者忘了new
所以我應該會採用工廠的概念
然後用guice去實作
並且制定限制
(其實我已經習慣bridge了,而非adapter了)
對外限制,對內隨便
class SystemContext{
//這行可以改在建構子
Injector injector = Guice.createInjector(new BillingModule());
public <T extends IService> T getService(Class<T> clazz){
//其實這裡或許可以做點事...
return injector.getInstance(clazz.class);
}
public <T extends IHandler> T getHandler(Class<T> clazz){
return injector.getInstance(clazz.class);
}
}
又或者我可以自己簡單DIY
class SystemContext{
//這行是放物件的map
static public <T extends IService> T getService(Class<T> clazz){
return 物件;
}
static public <T extends IHandler> T getHandler(Class<T> clazz){
return 物件;
}
}
然後SystemContext可以變成介面
從而提供更大的彈性
把物件管理的要點從各IService的實作抽離出來
如此IService的繼承架構就會單純許多,僅只在於本身的意義
(因為java沒有多重繼承,萬一IService下面某代需要範本或是抽象的話...)
繼承的子類別的建構式也不會要遷就物件管理的選項
其實我只想表達的就是,我不會用繼承來做物件管理就是
最後在此僅為賺P幣的參考
謝謝
※ 引述《Killercat (殺人貓™)》之銘言:
: 首先先感謝你提出的指教,我發現這邊的板友這點真的非常眼利
: 把Service該設計上沒有提到的疏漏都有看到
: 而Service該怎麼設計是我另外一篇文會提到的東西,
: 這篇其實只是以Service為範例聊generic technique,很明顯是我標題下的不好 =P
: 應該題目是generic techique,而不是打上Service設計 XD
: 讓我考慮一下正確的題目該下什麼好....
: btw,您們提到的都是在真正跑專案會碰到的問題,well done
: 我也會針對這部分在這篇文章說明一下
: ※ 引述《sbrhsieh (十年一夢)》之銘言:
: : 看完整篇文,我的心得是:
: : 的確有善用到 Java generics 這個 feature,但你在文中強調的型別安全實際上
: : 在 compile-time 是沒有保證的,純視這個設計正不正確罷了。
: 這個...我想強調的compile-time是相對於framework user,也就是client。
: "使用者不會因為任何原因拿錯東西"(這概念很相似於C++的exception netural)
: "使用者可以完全正確的由Auto Complete取得功能"(好聰明的eclipse)
: "使用者完全不可能接錯Service"
: 至於該轉對與否,其實是我們creator應該去做的,我個人是覺得這點出發點就夠了
: -------不過其實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所以有疑慮
: : 你的做法的優點在於你把風險集中在一處,你透過讓他人遵守一個 convension,
: : 並讓轉型的風險全落在一個(或一組)較有經驗的人手上;缺點是暴露實作的細節(
: : 至少以文內的範例來說),client code 要使用一個 service 他必需要知道實作
: : 這個功能的 class 是哪一個。
: 這點其實我不太懂為什麼叫做暴露細節
: 大多數來講Service都有自己獨特的功能,除非像是CRUDService而且僅僅被限制在CRUD
: 我們來分兩部分來講,比方說你提到的以及CRUD,這種Service的特徵是
: "我服務的介面完全相同,差別僅在於我處理的Bean(我們稱為Marsheled Bean)"
: 這也是為什麼範例裡的CRUD有這Marshel Bean的原因 -- 其實就是當時懶
: 隨手拷貝我自己的專案的Service再塗塗改改 XD
: 事實上我會在下一篇Service設計提到,我試圖在這裡聊聊這個先
: 你可以完全不知道Service是哪個的情況下做到完全的CRUD
: PersonalRecord pr = new PersonalRecord();
: ...
: ... //fill the data in
: ...
: BaseCRUDService.create(pr);
: ----
: AccessContext<PersonalRecord> ac = new AccessContext<PersonalRecord>("John");
: PersonalRecord pr = BaseCRUDService.read(ac);
: BaseCRUDService.delete(ac);
: 完全不需要知道Service,我們只需要Bean就可以正確無誤地找到該負責的Service
: 不知道是不是就是你提的這個"只靠interface就能做事"
: (當然 我的例子是只靠BaseCRUDService就能做事...他也是繼承IService)
: 這些都是已經寫好了,而且正在跑的code,所以我很確定做得到(炸)
: 再來看第二種Service
: 這些Service只有部分method是每個Service都相同的
: 但是各個Service有自己獨特的不同,那我們的目標就不是"透過interface操作"
: 我們必須讓使用者看到service獨有的功能才能做事
: 就有如我blog裡面提到的第一個NameCardService
: 只有他有sort跟getDuplicated,其他Service都沒有
: 這算是惡性的expose嗎?我還真的想不到不expose的方法 XD
: 如果你說的是我們應該要有一個INameCardService的話,正好,我們現在的設計有
: 不過是為了另外一個的理由,詳見下一段
: : 如果目標是讓 client 只透過 initerface 使用 service,在取用 service 時又
: : 不需要指名道姓實作 service 的 class,那麼你這樣的設計該怎麼去改良以
: : 符合目標?
: 真是銳利的眼神 XD
: 我不記得我有在裡面提過"用interface存取service"
: 但是的確,實作設計上是這樣 -- 為了thread safe做了一些很精美的調整
: 上面那句話其實我已經在上一段回答,在能夠被ICRUDService涵蓋的範圍內
: 的確可以相當輕易的連PersonalRecordService都不需要import 就能操作CRUD
: 所以我想上面那句大多數的部分我已經回答了 =P
: 問題在於不能被涵蓋的情況(不過我相信你一開始想問的應該不是這種case)
: 我順便回答一下上一篇忘了是推文還是回文提到的Multi Thread問題
: <下面文章很長而且很雜亂,慎入>
: 事實上在真正的系統裡面這個Service其實長的是這樣
: (這本來是下一篇Service設計的主題,我簡略的試圖在bbs上面介紹一下)
: 順便介紹一下我自己的PR Client Service設計克服multi thread的作法
: (PR = Peer2Peer Remote)
: 因為我的PR這部分是用C++寫的,所以我只能在概念上把他轉成java
: 而沒辦法直接拿code過來用(畢竟template跟generic大不同....)
: 所以要是下面有語法上的錯誤請不吝指正
: interface IService {
: //common service method, getName(), getServiceID(), getReferenceCount()...
: }
: interface IMasterChannelService extends IService{
: void A();
: void B();
: }
: class MasterChannelService extends QueueServiceBase
: implements IMasterChannelService {
: public void A();
: public void B();
: }
: QueueServiceBase等等會提到這是幹嘛的,他跟我們主題有很大的關係
: 他一部分的功能其實跟上面的BaseCRUDService有點像,不過不全是
: 題外話:
: 他在C++裡面是用Policy方式實作出來的(請見Modern C++ Design第二章)
: 但是在java裡面只能用可憐兮兮的QueueServiceBase的方式實現
: 這就是為什麼當初這塊用C++來寫的一部分原因 XD
: C++ Template可以做出太多奇妙的事情了
: 我們要怎麼使用這個Service呢?由提供者提供一個Handler
: 假設我們已經有一個實體MasterChannelService mcs;
: //很像getService吧?
: IMasterChannelService handler =
: QueueServiceBase.getHandler(MasterChannelService.class);
: sh.A();
: sh.B();
: QueueServiceBase.transit(sh); //把handler排進去,等著真正執行
: getHandler會拿回一個"實作IMasterChannelService"的東西
: 他做的事情僅僅只有一項,就是把使用者對於Master Channel Service的動作記錄下來
: 紀錄完畢以後再transit回mcs這個service instance
: 由mcs instance來排時間執行他,可能可以是Thread pool,可能可以是single thread
: 但是我們可以做multi thread不停丟quest進去
: 這部份則是我們怎麼用interface來避開multi thread的方法,
: 也經由handler這個完全不相干的實作避免暴露MasterChannelService的細節
: 希望這部分有回答到上面兩個人的問題
: 應該說,我是這樣設計的,跟大家分享一下 =P
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 111.241.124.167
→
07/20 04:18, , 1F
07/20 04:18, 1F
→
07/20 04:20, , 2F
07/20 04:20, 2F
→
07/20 04:21, , 3F
07/20 04:21, 3F
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 6 之 9 篇):