【正文】
f f ( )L a m p– Button依賴于抽象的接口ButtonServer(向該接口發(fā)消息)。 ButtonServer提供一些抽象的方法, Button類通過這些接口可以開啟或關(guān)掉一些東西。 – Lamp也依賴于 ButtonServer接口(從此接口派生),提供具體的實現(xiàn)。 部分代碼: // include class Button{ ButtonServer * bs。 public: void poll( )。 }。 // void Button::poll( ){ if(/* mechanism for detecting turnOn mand */) bsturnOn( )。 else if((/* mechanism for detecting turnOff mand */) bsturnOff( )。 } // class ButtonServer{ public: virtual void turnOn()=0。 virtual void turnOff()=0。 }。 // class Lamp:public ButtonServer{ public: void turnOn()。 void turnOff()。 }。 // void Lamp::turnOn(){ /*codes for turn on a specific device*/ } void Lamp::turnOff(){ /*codes for turn off a specific device*/ } ? 分析: – 上述設(shè)計使得 Button可以控制所有愿意實現(xiàn) ButtonServer接口的設(shè)備,甚至是一個尚未開發(fā)出來的設(shè)備。 質(zhì)疑: – 這樣的設(shè)計是不是強加了這樣一個約束 ——所有需要被 Button控制的對象一定要實現(xiàn) ButtonServer類。 – 如果我的設(shè)備還希望能夠被另一個對象控制,比如 Switch控制,怎么辦? – 這種設(shè)計是不是將 Button對 Lamp的依賴轉(zhuǎn)嫁成了 Lamp對 Button的依賴呢?(畢竟Lamp只能被一種 Button控制也是不好的) 抗辯: – 上述質(zhì)疑不成立。 Button依賴于 ButtonServer接口,但是接口并不依賴于 Button,也就是說任何知道如何操作 ButtonServer接口的對象都可以操作 Lamp。 – 也許需要改進的僅僅是 ButtonServer這樣一個有些 “ 誤導(dǎo)性 ” 的名字,我們可以將這個名字該得更加抽象一些,例如: SwitchableDevice 5. 接口隔離原則( ISP) ? 陳述: – 不應(yīng)該強迫客戶依賴于他們不用的方法 – 一個類的不內(nèi)聚的 “ 胖接口 ” 應(yīng)該被分解成多組方法,每一組方法都服務(wù)于一組不同的客戶程序。 ? 先說一個例子: class Door{ public: virtual void Lock( )=0。 virtual void Unlock( )=0。 virtual bool IsDoorOpen( )=0。 }。 ? Door可以加鎖、解鎖、而且可以感知自己是開還是關(guān); ? Door是抽象基類,客戶程序可以依賴于抽象而不是具體的實現(xiàn) 增加功能 如果門打開時間過長,它就會報警。(比如賓館客房的門) 為了實現(xiàn)上述新增功能,我們要求 Door與一個已有的 Timer對象進行交互 class Timer{ public: void Register(int timeout,TimerClient* client)。 }。 class TimerClient{ public: virtual void TimerOut( )。 }。 ?如果一個對象希望得到超時通知,它可以調(diào)用 Timer的 Register函數(shù)。 ?該函數(shù)有兩個參數(shù),一個是超時時間,另一個是指向TimerClient對象的指針,此對象的 TimerOut函數(shù)會在超時時被調(diào)用 我們?nèi)绾螌?TimerClient和 TimedDoor聯(lián)系起來? 問題 一種常見的解決方案如下: T i m e r i n t e r f a c e T i m e r C l i e n tD o o r+ T i m e o u tT i m e d D o o r?問題 ——接口污染 在 Door接口中加入新的方法( Timeout),而這個方法僅僅只為它的一個子類帶來好處。 ——如果每次子類需要一個新方法時它都被加到基類接口中,基類接口將很快變胖。 胖接口將導(dǎo)致 SRP, LSP被違反,從而導(dǎo)致脆弱、僵化 ? 客戶的反作用力: – 通常接口的變化將導(dǎo)致 client的改變 – 但是很多時候,接口之所以變化是因為客戶需要他們變化 ? Client對 interface具有反作用力! class Timer{ public: void Register(int timeout, int timeOutId, TimerClient* client)。 }。 class TimerClient{ public: virtual void TimerOut(int timeOutId )。 }。 TimedDoor的多個超時請求問題,導(dǎo)致 Timer接口做出下面的調(diào)整: TimedDoor對 Timer接口的影響會傳遞到 Door接口,從而導(dǎo)致所有 Door都受到此影響,而且這一影響還會影響到 Door的所有 Clients——牽一發(fā)動全身 ? 解決之道: – 使用委托 T i m e r i n t e r f a c e T i m e r C l i e n t+ T i m e o u tD o o r T i m e r A d a p t e r+ T i m e o u tT i m e d D o o r+ D o o r T i m e O u tD o o r C r e a t e s Class TimedDoor:public Door{ public: virtual void DoorTimeOut(int timeOutID)。 }。 class DoorTimeAdapter:public TimerClient{ TimedDooramp。 itsTimedDoor。 public: DoorTimerAdapter(TimedDooramp。 theDoor):itsTimedDoor(theDoor){} vitual void TimeOut(int timeOutId){ (timeOutId)。 } }。 ? 解決之道(續(xù)): – 使用多繼承 T i m e r i n t e r f a c e T i m e r C l i e n t+ T i m e o u tT i m e d D o o r+ T i m e O u tD o o rClass TimedDoor:public Door, public TimerClient{ public: virtual void DoorTimeOut(int timeOutID)。 }