如下虛擬碼,
AService do2對P進行了操作,然後碰到的問題是PService裡對P進行操作,如果調用了AService do2那麼
由於不是對P最新的引用,save時會把AService do2的修改覆蓋掉。
//Update 2015年05月22日17:24:28
@Ke_Wu 這不應該是邏輯問題,事實上,我作為後來的調用者沒必要也不可能知道AService::do2裡的具體實現,但現在碰到問題了,那麼就是設計的問題了
class AService{ function do2(pid) { ... p = P.getById(pid); p.s = 'zz'; p.save(); ... }}class PService{ function do1(pid) { ... p = P.getById(pid); p.s = 'yy'; AService.do2(pid); ... p.a = 'a';p.b = 'b'; ... p.save();//p.s 仍舊是yy, zz被yy覆蓋 ... }}class CService{ function do4(cid) { ... c = C.getById(cid); pid = c.pid; AService.do2(pid); ... }}
回複內容:
如下虛擬碼,
AService do2對P進行了操作,然後碰到的問題是PService裡對P進行操作,如果調用了AService do2那麼
由於不是對P最新的引用,save時會把AService do2的修改覆蓋掉。
//Update 2015年05月22日17:24:28
@Ke_Wu 這不應該是邏輯問題,事實上,我作為後來的調用者沒必要也不可能知道AService::do2裡的具體實現,但現在碰到問題了,那麼就是設計的問題了
class AService{ function do2(pid) { ... p = P.getById(pid); p.s = 'zz'; p.save(); ... }}class PService{ function do1(pid) { ... p = P.getById(pid); p.s = 'yy'; AService.do2(pid); ... p.a = 'a';p.b = 'b'; ... p.save();//p.s 仍舊是yy, zz被yy覆蓋 ... }}class CService{ function do4(cid) { ... c = C.getById(cid); pid = c.pid; AService.do2(pid); ... }}
簡化下來其實問題就是:
phpp1 = P.getById(pid);p1.s = 'yy';... p2 = P.getById(pid); p2.s = 'zz'; p2.save();...p1.save();
儲存了p2的修改(可能是存到資料庫),並不意味著記憶體裡的p1隨之更新,除非你重新get一遍p1。
重構的目的是用來改善正確工作代碼的風格和設計。
這段代碼的問題是邏輯錯誤,對它而言談重構還為時過早。
你的問題的本質,是兩個“主語”(只是在你的案例中恰好都是service而已)的各自一個“行為”(do1 和 do2)含有了完全相同的一個“行動效果”(修改p.s的值)。
衝突不在於service,而在於行動效果冗餘。
試想一下,換一個案例,其中只有一個主語,兩個行為(do1 和 do2)都是它的,那麼問題也是等價的。
兩個行為有重疊的行動效果,實在太常見的了。
關鍵在於,你怎樣界定,哪種重疊是滿足需求的?哪種是錯誤、不合理的?
舉一個滿足需求的例子:
需求是:p是一個滑鼠移至上方的tips(介面組件)。先根據滑鼠座標,賦值p.top為一個值。隨後,計算tips是否超出了視窗邊緣。如果是,則計算tips的top的最大值(因為視窗大小可能會被改變,所以需要計算),然後賦值p.top為該最大值。p.left同理。
這是我做網頁前端開發時遇到過的需求。
你的解決辦法,大概可以解決你的那一個具體案例,但換成別的情況可能就又不對症了。
在我看來,關鍵在於,一個行為的源頭(往往是事件)所導致一連串列動效果,其中要避免出現重疊;除非需求要求必要的重疊。
這“一連串”的“串法”,是設計上要想清楚的。你已經在朝這個方向努力了,只是關注點稍有偏離。
至於串的過程中的對象(主語/賓語)是不是service、是何種service,倒是沒有關係。
我的解決辦法如下,有什麼缺點請指教:
Service應該分為2種:1,名詞Service; 2, 行為Service
如:UserService 與 RegisterService
對於【名詞Service】其裡面每個method都必須返回相應的對象,如UserService下的upgrade(uid)就必須返回被升級後的user對象。
對於【行為Service】只對外暴露出一個execute(data),excute(data)必須返回行為成功與否的狀態以及被施加這個行為的對象,如RegisterService下的excute(data)就必須返回註冊成功與否,以及如果成功了它影響的對象。
通常我們約定對外只調用【行為Service】,再在【行為Service】裡調用多個【名詞Service】和其他【行為Service】,如在RegisterService::execute(data)裡調用UserService::create(), UserService::markNewbee(uid),SendEmailService::execut()等;
【名詞Service】中不允許調用【行為Service】。
所有Service的每個method的入參都可以是id或者對象執行個體,如upgrade()可以接受uid也可以接受user作為入參。
回到我的提問,可以這麼寫
class AService{ function get(aid_or_object) { if (aid_or_object instanceOf A) { return aid_or_object; } return A.getById(aid); }}class PService{ function get(pid_or_object) { if (pid_or_object instanceOf P) { return pid_or_object; } return P.getById(pid); }}class Do2Service{ function execute(aid_or_object, pid_or_object = null) { a = AService.get(aid_or_object); if (pid_or_object instanceOf P) { p = pid_or_object } else { p = PService.get(a.pid); } p.s = 'zz'; p.save(); a.save(); return [:success, a, p]; }}class Do3Service{ function execute(pid_or_object) { p = PService.get(pid_or_object); p.s = 'cc'; p.save(); return [:success, p]; }}class Do1Service{ function execute(pid_or_object) { p = PService.get(pid_or_object); p.s = 'yy' if condition1 result, a, p = Do2Servce.execute(p.aid, p) if condition2 result, p = Do3Servce.execute(p) if condition3 p.a = 'a'; p.b = 'b'; p.save() return [:success, p, a]; }}
想要解決什麼問題?