如何在不破坏装饰模式的情况下瘦身Fat界面?
在我的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
,这样所有三种类型的接口都可以完全装饰,但我真的宁愿不 - 这看起来像是很多冗余/开销,特别是如果我有已经有多个不同的适配器类别,我不想将它们的数字进一步增加三倍。
是否有一些众所周知的聪明/优雅的方式来解决这个问题,这样我可以吃我的蛋糕,也吃了吗?
我不知道这是否是解决这一问题的最聪明的/优雅的方式,但经过一些更多的思考,这就是我想出了:
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
类中,并且不会暴露给所有装饰器子类。