如何在不破坏装饰模式的情况下瘦身Fat界面?

如何在不破坏装饰模式的情况下瘦身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子类应该在这种情况下继承什么接口?我想我可以编写一个XorDataIOXorSeekableDataIO和一个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类中,并且不会暴露给所有装饰器子类。