橋接模式的定義:將抽象部分與它的實現部分分離,是他們都可以獨立地變化。
這樣的定義,對於初學者的我,簡直就是一頭霧水。什麼叫做抽象與實現分離?
其實上網查閱相關資料,再加上自己工程實踐,才慢慢明白這句話的意思。
所謂的抽象就是某個類暴露給外界的接口(說白了就是可調用的函數);
實現就是這些接口(函數)如何實現。
通常當一個抽象有多個實現時,使用繼承就可以了。但是繼承機制將抽象部分與實現部分固定在一起,使得難以對抽象部分和實現部分獨立地進行修改、擴充和重用。(書中原話)
其實也就是說,如果你修改基類,例如增加了一個接口,那麼所有的派生類都可能對這個函數加以實現。這樣抽象部分就影響了實現部分。而橋接模式就是將這種耦合解耦。
下面說一下我的應用場景,當然這並不是一個非常經典的案例,我只是爲了聯繫橋接模式而這樣設計的:
現在需要一個通用網絡通信類庫,但通信大致分爲UDP和TCP通信,不管哪種通信機制,這個類庫並不關心,他只想知道實現通信需要哪些接口。然後我們草擬了這份接口
class CSimpleSocket
{
protected:
CSimpleSocket(void);
public:
virtual ~CSimpleSocket(void){};
public:
virtual BOOL Initialize() = 0;
virtual BOOL IsInitialize()= 0;
virtual VOID UnInitialize()= 0;
virtual BOOL Connect(const char * pAddr,UINT uPort/*,int nSocketType/ *=SOCK_STREAM* /*/)= 0;
virtual int Send(const char * pSendData,int nLen)= 0;
//如果pRecvData是NULL則返回剩餘可讀取的大小
virtual int Recv(char * pRecvData,int nLen)= 0;
virtual INT ShutDown()= 0;
virtual BOOL IsContected()= 0;
virtual BOOL SetSendTimeOut(int nTimeOutSec)= 0;
virtual BOOL SetRecvTimeOut(int nTimeOutSec)= 0;
virtual BOOL SetReceiveBufferSize(int nRecvBufferSize)= 0;
virtual int GetReceiveBufferSize()= 0;
virtual BOOL SetSendBufferSize(int nSendBufferSize)= 0;
virtual int GetSendBufferSize()= 0;
virtual BOOL SetReuseAddress(BOOL bReuse)= 0;
virtual BOOL GetReuseAddress()= 0;
virtual BOOL SetKeepAlive(BOOL bKeepAlive)= 0;
virtual BOOL GetKeepAlive()= 0;
INT GetLastErrorCode() {return WSAGetLastError();} ;
VOID SetLastErrorCode(INT nErrCode) {WSASetLastError(nErrCode);} ;
protected:
virtual CSimpleSocketImp * GetSocketImp();
//設置新的CSimpleSocketImp類,並返回設置之前的CSimpleSocketImp類
CSimpleSocketImp *SetSocketImp(CSimpleSocketImp * newSockImp);
private:
CSimpleSocketImp * m_socketImp;
};
當客戶端調用函數的時候只需調用上面的函數即可,而無需關心到底是TCP還是UDP。
但光有接口是不行的,還必須有實現,實現也要有一定的規範,例如必須有startup、cleanup等函數。實現部分的規範和預定義的接口不必完全一致,只要實現的規範可以完成預定義接口的功能即可。
class CSimpleSocketImp
{
protected:
CSimpleSocketImp(){};
public:
virtual ~CSimpleSocketImp(){};
public:
int StartUp();
int CleanUp();
SOCKET CreateSocket(int nAf,int nType,int nProtocol);
int CloseSocket(SOCKET s);
int BindToSocket(SOCKET sock,const struct sockaddr * sAddr,int nNameLen);
int ConnectToSocket(SOCKET s,LPSOCKADDR lpName,int nNameLen);
int SetSocketOption(SOCKET s,int nLevel,int nOptName,const char * pOptValue,int nOptLen);
int GetSocketOption(SOCKET s,int nLevel,int nOptName,char FAR * pOptValue,int FAR *nOptLen);
int GetErrorCode();
VOID SetErrorCode(int nErrorCode);
// int getsockopt( SOCKET s, int level, int optname, char FAR* optval,
// int FAR* optlen);
virtual int ListenSocket(SOCKET s,int nQueueSize)=0;
virtual SOCKET AcceptFromSocket(SOCKET s,LPSOCKADDR lpAddr,LPINT lpAddrLen)=0;
virtual int ShutDown(SOCKET s)=0;
virtual int SendData(SOCKET s,const char * lpBuffer,int nBufferLen,const sockaddr * sockAddr,int sockAddrLen)=0;
virtual int RecvData(SOCKET s,char *lpBuffer,int nBufferLen,sockaddr * sockAddr,int* pSockAddrLen)=0;
virtual int PeekData(SOCKET s,char *lpBuffer,int nBufferLen,sockaddr * sockAddr,int* pSockAddrLen)=0;
};
這個類的實例來實現具體的通信,這個類有兩個子類,TCPSocket,UDPSocket。
抽象接口將client的請求轉發給他的Implementor,Implementor將請求轉發給他的子類,子類完成具體的功能。
這樣做有什麼好處呢?
其實可以很容易發現,接口和實現可以同時進行開發。開發通信類庫的接口,由A組來實現;
接口的實現有B組來實現,實現的規範由B組統一之後,分兩撥人開發具體的TCPSocket,UDPSocket子類。上層調用通信類庫接口的應用程序只需按照接口部分就可以了。這樣是不是很不錯的選擇呢?
其實說白了,就是將可改變的部分延後實現,這個延後可以使時間的延後也可以是空間的延後。時間的延後就是運行的時候可以選擇具體實現,只需要調用TCPSocket,UDPSocket子類就可以了,具體怎麼選擇對於上層就是透明的了。空間的延後就是繼承,由上層決定是用哪個子類。