在我的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
子類應該在這種情況下繼承什麼接口?我想我可以編寫一個XorDataIO
,XorSeekableDataIO
和一個XorPacketDataIO
,這樣所有三種類型的接口都可以完全裝飾,但我真的寧願不 - 這看起來像是很多冗餘/開銷,特別是如果我有已經有多個不同的適配器類別,我不想將它們的數字進一步增加三倍。
是否有一些衆所周知的聰明/優雅的方式來解決這個問題,這樣我可以吃我的蛋糕,也吃了嗎?