简体   繁体   English

如何在不破坏Decorator模式的情况下减少胖界面?

[英]How to slim down a Fat Interface without breaking the Decorator pattern?

In my C++ library code I'm using an abstract base class as an interface to all different kinds of I/O-capable objects. 在我的C ++库代码中,我使用抽象基类作为所有不同类型的I / O对象的接口。 It currently looks like this: 它目前看起来像这样:

// 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;
};

This works pretty well -- I have various concrete subclasses for TCP, UDP, files, memory buffers, SSL, RS232, stdin/stdout, and so on, and I'm able to write I/O-agnostic routines that can be used in conjunction with any of them. 这很好用 - 我有TCP,UDP,文件,内存缓冲区,SSL,RS232,stdin / stdout等各种具体的子类,我可以编写可以使用的I / O不可知的例程与他们中的任何一个一起。

I also have various decorator classes that take ownership of an existing IDataIO object and serve as a behavior-modifying front-end to that object. 我还有各种装饰器类,它们拥有现有IDataIO对象的所有权,并充当该对象的行为修改前端。 These decorator classes are useful because a single decorator class be used to can modify/enhance the behavior of any kind of IDataIO object. 这些装饰器类很有用,因为可以使用单个装饰器类来修改/增强任何类型的IDataIO对象的行为。 Here's a simple (toy) example: 这是一个简单的(玩具)示例:

/** 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;
};

This is all well and good, but what's bothering me is that my IDataIO class looks like an example of a fat interface -- for example, a UDPSocketDataIO class will never be able to implement the Seek() , GetCurrentSeekPosition() , and GetStreamLength() methods, while a FileDataIO class will never be able to implement the GetSourceOfLastReadPacket() and SetPacketSendDestination() methods. 这一切都很好,但令我困扰的是我的IDataIO类看起来像胖接口的一个例子 - 例如, UDPSocketDataIO类永远无法实现Seek()GetCurrentSeekPosition()GetStreamLength()方法,而FileDataIO类永远SetPacketSendDestination() GetSourceOfLastReadPacket()SetPacketSendDestination()方法。 So both classes are forced to implement those methods as stubs that just do nothing and return an error code -- which works, but it's ugly. 所以这两个类都被强制实现那些方法作为存根,只是什么也不做,并返回一个错误代码 - 这是有效的,但它很难看。

To solve the problem, I'd like to break out the IDataIO interface into separate chunks, like this: 为了解决这个问题,我想将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;
};

.... so now I can subclass UDPSocketDataIO from the IPacketDataIO sub-interface, and subclass FileDataIO from the ISeekableDataIO interface, while TCPSocketDataIO can still subclass directly from IDataIO , and so on. ....所以现在我也可以继承UDPSocketDataIOIPacketDataIO子接口,并继承FileDataIOISeekableDataIO接口,而TCPSocketDataIO还可以直接从子类IDataIO ,等等。 That way each type of I/O object presents the interface only to the functionality it can actually support, and nobody has to implement no-op/stub versions of methods that are irrelevant to what they do. 这样,每种类型的I / O对象仅向其实际支持的功能提供接口,并且没有人必须实现与其所做的无关的无操作/存根版本的方法。

So far, so good, but the problem that arises at this point is with the decorator classes -- what interface should my XorDataIO subclass inherit from in this scenario? 到目前为止,这么好,但此时出现的问题是装饰器类 - 我的XorDataIO子类应该在这种情况下继承什么接口? I suppose I could write a XorDataIO , a XorSeekableDataIO , and a XorPacketDataIO , so that all three types of interface can be fully decorated, but I'd really rather not -- that seems like a lot of redundancy/overhead, particularly if I have multiple different adapter classes already and I don't want to multiple their numbers further by a factor of three. 我想我可以写一个XorDataIO ,一个XorSeekableDataIO和一个XorPacketDataIO ,这样所有三种类型的接口都可以完全装饰,但我真的不愿意 - 这似乎是很多冗余/开销,特别是如果我有已经有多个不同的适配器类,我不想将它们的数字进一步倍增三倍。

Is there some well-known clever/elegant way to address this problem, such that I can have my cake and eat it too? 是否有一些众所周知的聪明/优雅的方法来解决这个问题,这样我就可以吃蛋糕了?

I don't know if this is the most clever/elegant way to address this problem, but after some more reflection, here's what I came up with: 我不知道这是否是解决这个问题的最聪明/最优雅的方法,但经过一些反思,这就是我想出的:

1) Use "virtual inheritance" for the two extended interfaces: 1)对两个扩展接口使用“虚拟继承”:

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

2) Create a DecoratorDataIO class that inherits from both of those interfaces, and passes all method calls through to the appropriate ones on the child IDataIO object, if possible: 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) Now my decorator classes can just subclass DecoratorDataIO and override whichever methods they choose to (calling up to the superclass implementation of the method as necessary): 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);
   }
};

This approach accomplishes my goals, and if there is some ugliness (ie dynamic_cast<>), at least it is contained within the DecoratorDataIO class and not exposed to all the decorator subclasses. 这种方法实现了我的目标,如果有一些丑陋(即dynamic_cast <>),至少它包含在DecoratorDataIO类中,并且不会暴露给所有装饰器子类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM