2017-06-14 62 views
4

在我的C++庫代碼中,我使用抽象基類作爲所有不同類型的支持I/O的對象的接口。目前,它看起來像這樣:如何在不破壞裝飾模式的情況下瘦身Fat界面?

// All-purpose interface for any kind of object that can do I/O 
class IDataIO 
{ 
public: 
    // basic I/O calls 
    virtual ssize_t Read(void * buffer, size_t size) = 0; 
    virtual ssize_t Write(const void * buffer, size_t size) = 0; 

    // Seeking calls (implemented to return error codes 
    // for I/O objects that can't actually seek) 
    virtual result_t Seek(ssize_t offset, int whence) = 0; 
    virtual ssize_t GetCurrentSeekPosition() const = 0; 
    virtual ssize_t GetStreamLength() const = 0; 

    // Packet-specific calls (implemented to do nothing 
    // for I/O objects that aren't packet-oriented) 
    virtual const IPAddressAndPort & GetSourceOfLastReadPacket() const = 0; 
    virtual result_t SetPacketSendDestination(const IPAddressAndPort & iap) = 0; 
}; 

這工作得很好 - 我對TCP,UDP,文件,內存緩衝區,SSL,RS232,標準輸入/輸出等各種具體子類,和我能夠編寫可與其中任何一個結合使用的I/O不可知例程。

我也有各種各樣的decorator類取得現有的IDataIO對象的所有權,並作爲該對象的行爲修改前端。這些裝飾器類很有用,因爲使用單個裝飾器類可以修改/增強任何種類的對象的行爲。這裏有一個簡單的(玩具)例如:

/** Example decorator class: This object wraps any given 
    * child IDataIO object, such that all data going out is 
    * obfuscated by applying an XOR transformation to the bytes, 
    * and any data coming in is de-obfuscated the same way. 
    */ 
class XorDataIO : public IDataIO 
{ 
public: 
    XorDataIO(IDataIO * child) : _child(child) {/* empty */} 
    virtual ~XorDataIO() {delete _child;} 

    virtual ssize_t Read(void * buffer, size_t size) 
    { 
     ssize_t ret = _child->Read(buffer, size); 
     if (ret > 0) XorData(buffer, ret); 
     return ret; 
    } 

    virtual ssize_t Write(const void * buffer, size_t size) 
    { 
     XorData(buffer, size); // const-violation here, but you get the idea 
     return _child->Write(buffer, size); 
    } 

    virtual result_t Seek(ssize_t offset, int whence) {return _child->Seek(offset, whence);} 
    virtual ssize_t GetCurrentSeekPosition() const {return _child->GetCurrentSeekPosition();} 
    virtual ssize_t GetStreamLength() const   {return _child->GetStreamLength();} 

    virtual const IPAddressAndPort & GetSourceOfLastReadPacket() const  {return _child->GetSourceOfLastReadPacket();} 
    virtual result_t SetPacketSendDestination(const IPAddressAndPort & iap) {return _child->SetPacketSendDestination(iap);} 

private: 
    IDataIO * _child; 
}; 

這是一切都很好,但什麼困擾我的是,我IDataIO類看起來像一個fat interface的例子 - 例如,一個UDPSocketDataIO類將永遠無法以執行Seek()GetCurrentSeekPosition()GetStreamLength()方法,而FileDataIO類將永遠不能執行GetSourceOfLastReadPacket()SetPacketSendDestination()方法。因此,這兩個類都被迫將這些方法實現爲存根,它們什麼都不做,並返回一個錯誤代碼 - 這是有效的,但很醜陋。

爲了解決這個問題,我想打出來的IDataIO接口爲單獨的塊,像這樣:

// The bare-minimum interface for any object that we can 
// read bytes from, or write bytes to (e.g. TCP or RS232) 
class IDataIO 
{ 
public: 
    virtual ssize_t Read(void * buffer, size_t size) = 0; 
    virtual ssize_t Write(const void * buffer, size_t size) = 0; 
}; 

// A slightly extended interface for objects (e.g. files 
// or memory-buffers) that also allows us to seek to a 
// specified offset within the data-stream. 
class ISeekableDataIO : public IDataIO 
{ 
public: 
    virtual result_t Seek(ssize_t offset, int whence) = 0; 
    virtual ssize_t GetCurrentSeekPosition() const = 0; 
    virtual ssize_t GetStreamLength() const = 0; 
}; 

// A slightly extended interface for packet-oriented 
// objects (e.g. UDP sockets) 
class IPacketDataIO : public IDataIO 
{ 
public: 
    virtual const IPAddressAndPort & GetSourceOfLastReadPacket() const = 0; 
    virtual result_t SetPacketSendDestination(const IPAddressAndPort & iap) = 0; 
}; 

....所以現在我可以從IPacketDataIO子接口繼承UDPSocketDataIO,以及來自ISeekableDataIO接口的子類FileDataIO,而TCPSocketDataIO仍可直接從IDataIO繼承子類,依此類推。這樣,每種類型的I/O對象都只將接口呈現給它實際支持的功能,而沒有人必須實現與他們所做的無關的方法的無操作/存根版本。

到目前爲止,這麼好,但現在出現的問題是裝飾類 - 我的XorDataIO子類應該在這種情況下繼承什麼接口?我想我可以編寫一個XorDataIOXorSeekableDataIO和一個XorPacketDataIO,這樣所有三種類型的接口都可以完全裝飾,但我真的寧願不 - 這看起來像是很多冗餘/開銷,特別是如果我有已經有多個不同的適配器類別,我不想將它們的數字進一步增加三倍。

是否有一些衆所周知的聰明/優雅的方式來解決這個問題,這樣我可以吃我的蛋糕,也吃了嗎?

回答

0

我不知道這是否是解決這一問題的最聰明的/優雅的方式,但經過一些更多的思考,這就是我想出了:

1)使用「虛擬繼承」兩個擴展接口:

class ISeekableDataIO : public virtual IDataIO {...} 
class IPacketDataIO : public virtual IDataIO {...} 

2)創建DecoratorDataIO類使得來自兩個這些接口的繼承,並通過所有方法調用通過對兒童IDataIO對象上的適當的,如果可能的話:

class DecoratorDataIO : public IPacketDataIO, public ISeekableDataIO 
{ 
public: 
    DecoratorDataIO(const IDataIO * childIO) 
     : _childIO(childIO) 
     , _seekableChildIO(dynamic_cast<ISeekableDataIO *>(childIO)) 
     , _packetChildIO(dynamic_cast<IPacketDataIO *>(childIO)) 
    { 
     // empty 
    } 

    virtual ~DecoratorDataIO() {delete _childIO;} 

    // IDataIO interface implementation 
    virtual ssize_t Read(void * buffer, size_t size) {return _childIO() ? _childIO()->Read(buffer, size) : -1;} 
    virtual ssize_t Write(const void * buffer, size_t size) {return _childIO() ? _childIO()->Write(buffer, size) : -1;} 

    // ISeekableDataIO interface implementation 
    virtual result_t Seek(ssize_t offset, int whence) {return _seekableChildIO ? _seekableChildIO->Seek(offset, whence) : B_ERROR;} 
    virtual ssize_t GetCurrentSeekPosition() const {return _seekableChildIO ? _seekableChildIO->GetCurrentSeekPosition() : -1;} 
    virtual ssize_t GetStreamLength() const {return _seekableChildIO ? _seekableChildIO->GetStreamLength() : -1;} 

    // IPacketDataIO interface implementation 
    virtual const IPAddressAndPort & GetSourceOfLastReadPacket() const {return _packetChildIO ? _packetChildIO->GetSourceOfLastReadPacket() : GetDefaultObjectForType<IPAddressAndPort>();} 
    virtual const IPAddressAndPort & GetPacketSendDestination() const {return _packetChildIO ? _packetChildIO->GetPacketSendDestination() : GetDefaultObjectForType<IPAddressAndPort>();} 

private: 
    IDataIO * _childIO; 
    ISeekableDataIO * _seekableChildIO; 
    IPacketDataIO * _packetChildIO; 
}; 

3)現在我的裝飾類可以直接繼承DecoratorDataIO並重寫他們選擇何種方法(調用到超類實現必要的方法)的:

class XorDataIO : public DecoratorDataIO 
{ 
public: 
    XorDataIO(IDataIO * child) : DecoratorDataIO(child) {/* empty */} 

    virtual ssize_t Read(void * buffer, size_t size) 
    { 
     ssize_t ret = DecoratorDataIO::Read(buffer, size); 
     if (ret > 0) XorData(buffer, ret); 
     return ret; 
    } 

    virtual ssize_t Write(const void * buffer, size_t size) 
    { 
     XorData(buffer, size); // const-violation here, but you get the idea 
     return DecoratorDataIO::Write(buffer, size); 
    } 
}; 

這種方法實現了我的目標,如果有是一些醜陋(即dynamic_cast <>),至少它包含在DecoratorDataIO類中,並且不會暴露給所有裝飾器子類。

相關問題