简体   繁体   English

继承层次结构与多重继承(C ++)

[英]Inheritance hierarchy vs. multiple inheritance (C++)

Well, I was thinking about a design decision for the past few days and since I still cannot favor one over the other I thought maybe someone else has an idea. 好吧,我在考虑过去几天的设计决定,因为我仍然不能偏爱另一个,我想也许别人有个主意。

The situation is the following: I have a couple of different interface classes abstracting several communication devices. 情况如下:我有几个不同的接口类抽象几个通信设备。 Since those devices differ in their nature they also differ in the interface and thus are not really related. 由于这些设备的性质不同,因此界面也不同,因此并不真正相关。 Lets call them IFooDevice and IBarDevice . 让我们称它们为IFooDeviceIBarDevice More device types may be added over time. 随着时间的推移可能会添加更多设备类型 The language is C++. 语言是C ++。

Since other components (called clients from now on) might want to use one or more of those devices, I decided to provide a DeviceManager class to handle access to all available devices at runtime. 由于其他组件(从现在开始称为客户端)可能想要使用其中一个或多个设备,因此我决定提供一个DeviceManager类来处理运行时对所有可用设备的访问。 Since the number of device types might increase, I would like to treat all devices equally (from the managers point of view). 由于设备类型的数量可能会增加,我想平等对待所有设备(从经理的角度来看)。 However, clients will request a certain device type (or devices based on some properties). 但是,客户端将请求某种设备类型(或基于某些属性的设备)。

I thought of two possible solutions: 我想到了两种可能的解决方案:

The first would be some kind of interitance hierarchy. 第一种是某种间歇层次结构。 All devices would subclass a common interface IDevice which would provide the (virtual) methods necessary for management and device query (like getProperties() , hasProperties() , ...). 所有设备都将子类化一个公共接口IDevice ,它将提供管理和设备查询所需的(虚拟)方法(如getProperties()hasProperties() ,...)。 The DeviceManager then has a collection of pointers to IDevice and at some point a cast from Base to Derived would be necessary - either with a template method in the manager or after the request on the client's side. 然后, DeviceManager有一个指向IDevice的指针集合,在某些时候,需要从BaseDerived的转换 - 在管理器中使用模板方法或在客户端请求之后。

From a design point of view, I think it would be more elegant to seperate the concerns of managing a device and the interface of the specific device itself. 从设计的角度来看,我认为分离管理设备和特定设备本身的界面的问题会更加优雅。 Thus it would lead to two unrelated interfaces: IManagedDevice and eg IFooDevice . 因此,它将导致两个不相关的接口: IManagedDevice和例如IFooDevice A real device would need to inherit from both in order to "be" of a specific device type and to be managaeble. 真实设备需要从两者继承以便“成为”特定设备类型并且是可管理的。 The manager would only manage pointers to IManagedDevice . 经理只管理指向IManagedDevice的指针。 However, at some point there will be the need to cast between now unrelated classes (eg from IManagedDevice to IFooDevice ) if a client wants to use a device provided by the manager. 但是,在某些时候,如果客户端想要使用管理器提供的设备,则需要在现在不相关的类之间(例如,从IManagedDeviceIFooDevice )之间进行转换

Do I have to choose the lesser of two evils here? 我必须在这里选择两个邪恶中较小的一个吗? And if so which one would it be? 如果是这样的话会是哪一个? Or do I miss something? 或者我会错过什么?

Edit: 编辑:

About the "managing" part. 关于“管理”部分。 The idea is to have library providing a variety of communication devices different (client) applications can use and share. 这个想法是让库提供各种不同的通信设备(客户端)应用程序可以使用和共享。 Managing merely comes down to the storage of instances, methods for registering a new device and looking up a certain device. 管理仅仅归结为实例的存储,注册新设备和查找某个设备的方法。 The responsibility for choosing the "right" device for the task is up to the client side because it knows best which requirements it puts on the communication. 为任务选择“正确”设备的责任取决于客户端,因为它最了解它对通信的要求。 In order to reuse and thus share available devices (and by that I mean real instances and not just classes) I need a central access point to all available devices. 为了重用并共享可用设备(并且我指的是真实实例而不仅仅是类),我需要一个到所有可用设备的中央访问点。 I'm not too fond of the manager itself but it's the only thing I could come up to in that case. 我不太喜欢经理本身,但在这种情况下,这是我唯一可以做到的。

我认为访客模式是更好的选择。

I think what Tom suggested might be altered a bit to suit your needs: 我认为汤姆建议的内容可能会有所改变,以满足您的需求:

class IManagedDevice
{
    IDevice* myDevice;

    /* Functions for managing devices... */
};

In this case IDevice is an empty interface that all devices inherit from. 在这种情况下, IDevice是一个所有设备都继承的空接口。 It gives no real benefit, just make the class hierarchy handling slightly more bearable. 它没有带来任何实际好处,只是让类层次结构处理更加可忍受。

Then, you can have then ask for the specific device ( IFooDevice or IBarDevice ), probably via some sort of device type ID. 然后,您可以通过某种设备类型ID询问特定设备( IFooDeviceIBarDevice )。

If all you need is to have a common code to manage the devices, and then pass each device to the appropriate place I think you can get away with something like this: 如果您只需要一个公共代码来管理设备,然后将每个设备传递到适当的位置,我认为您可以使用以下内容:

class IDevice
{
    virtual void Handle() = 0;
};

class IFooDevice : public IDevice
{
    virtual void Handle()
    {
        this->doFoo();
    }

    virtual void doFoo() = 0;
}

class IBarDevice : public IDevice
{
    virtual void Handle()
    {
        this->doBar();
    }

    virtual void doBar() = 0;
}

With the manager calling the Handle function. 管理员调用Handle函数。

I think I'd go for a simple solution of having a base class for Device that takes care of registering the device in the global device list and then static methods for looking them up. 我想我会想要一个简单的解决方案,为Device提供基类,负责在全局设备列表中注册设备,然后使用静态方法查找它们。 Something like: 就像是:

struct Device
{
    static Device *first;  // Pointer to first available device
    Device *prev, *next;   // Links for the doubly-linked list of devices

    Device() : prev(0), next(first)
    {
        if (next) next->prev = this;
        first = this;
    }

    virtual ~Device()
    {
        if (next) next->prev = prev;
        if (prev) prev->next = next; else first = next;
    }

    private:
        // Taboo - the following are not implemented
        Device(const Device&);
        Device& operator=(const Device&);
 };

Then you can just derive all devices from Device and them will be automatically placed in the global list on construction and removed from the global list on destruction. 然后,您可以从Device派生所有Device ,它们将自动放置在构造的全局列表中,并在销毁时从全局列表中删除。

All your clients will be able to visit the list of all devices by starting from Device::first and following device->next . 您的所有客户都可以通过从Device::first开始,然后关注device->next来访问所有设备的列表。 By doing a dynamic_cast<NeededDeviceType*>(device) clients can check if the device is compatible with what they need. 通过执行dynamic_cast<NeededDeviceType*>(device)客户端可以检查设备是否与他们需要的兼容。

Of course any method that is implemented in every device type (eg a description string, a locking method to ensure exclusive use by one client and the like) can be exported also at the Device level. 当然,也可以在Device级别导出在每种设备类型中实现的任何方法(例如,描述字符串,确保由一个客户端独占使用的锁定方法等)。

At a first glance, the first approach seems fine for me if all devices need to be managed and no other stuff can be done with an unknown device. 乍一看,如果需要管理所有设备,并且使用未知设备无法完成其他任何操作,第一种方法对我来说似乎很好。 The meta data for a general device (eg name, ...) would typically be the data one need for managing devices. 一般设备的元数据(例如,名称,......)通常是管理设备所需的数据。

However, if you need to separate the interface between the management and the device functionality, you can use virtual inheritance . 但是,如果需要分离管理和设备功能之间的接口,则可以使用虚拟继承 IManagedDevice and IFooDevice are both interfaces of the same concrete device, so they both have a common virtual base IDevice . IManagedDeviceIFooDevice都是同一具体设备的接口,因此它们都有一个共同的虚拟基本IDevice

Concretely (run code) : 具体来说(运行代码)

#include <cassert>

class IDevice {
public:
  // must be polymorphic, a virtual destructor is a good idea
  virtual ~IDevice() {}
};

class IManagedDevice : public virtual IDevice {
    // management stuff
};

class IFooDevice : public virtual IDevice {
    // foo stuff
};

class ConcreteDevice : public IFooDevice, public IManagedDevice {
    // implementation stuff
};

int main() {
    ConcreteDevice device;
    IManagedDevice* managed_device = &device;
    IFooDevice* foo_device = dynamic_cast<IFooDevice*>(managed_device);
    assert(foo_device);
    return 0;
}

when communicating with devices I separated the the device and the communication manager completely. 在与设备通信时,我完全分离了设备和通信管理器。

I had a simple communication manager that was based on Boost.Asio. 我有一个基于Boost.Asio的简单通信管理器。 The interface was something like 界面就像是

/** An interface to basic communication with a decive.*/
class coms_manager
{
public:
  virtual 
  ~coms_manager();
  /** Send a command. */
  virtual 
  void 
  send(const std::string& cmd) = 0;

  /** Receive a command.
   *  @param buffsize The number of bytes to receive.
   *  @param size_exactly True if exactly buffsize bytes are to be received.  If false, then fewer bytes may be received.
   */
  virtual 
  std::string 
  recv( const unsigned long& buffsize = 128, 
    const bool& size_exactly = false) = 0 ;

  /** Timed receive command.
   *  @param buffsize The number of bytes to receive.
   *  @param seconds The number of seconds in the timeout.
   *  @param size_exactly True if exactly buffsize bytes are to be received.  If false, then fewer bytes may be received.
   */
  virtual 
  std::string 
  timed_recv( const unsigned long& buffsize = 128, 
      const double& seconds = 5, 
      const bool& size_exactly = false) = 0;
};

I then implemented this interface for tcp (ethernet) and serial communications. 然后我为tcp(以太网)和串行通信实现了这个接口。

class serial_manager : public coms_manager {};
class ethernet_manager : public coms_manager {};

Each of the devices then contained (or pointed to) (rather than inherited) a coms_manager object For example: 然后每个设备包含(或指向)(而不是继承) coms_manager对象例如:

class Oscilloscope
{
  void send(const std::string& cmd)
  {  
     m_ComsPtr->send(cmd);
  }
private:
  coms_manager* m_ComsPtr;
};

You can then swap around the communication method by changing what the pointer points to. 然后,您可以通过更改指针指向的内容来交换通信方法。

For me, this didn't make much sense (the Oscilloscope was EITHER attached via serial OR via ethernet and so I actually opted for 对我来说,这没有多大意义(示波器是通过串行或通过以太网连接的,所以我实际上选择了

template<class Manager>
class Oscilloscope
{
  void send(const std::string& cmd)
  {  
     m_Coms.send(cmd);
  }
private:
  Manager m_Coms;
};

and I now use 我现在用

Oscilloscope<serial_manager> O1(/dev/tty1);   // the serial port
Oscilloscope<ethernet_manager> O2(10.0.0.10); //The ip address

which makes more sense. 这更有意义。

As for your suggestion as to have a generic device interface. 至于你有关通用设备接口的建议。 I started with that too, but then wasn't sure of its utility - I always wanted to know exactly what equipment I was sending a command to, I neither needed nor wanted to work through an abstract interface. 我也从那开始,但后来不确定它的实用性 - 我一直想知道我发送命令的确切设备,我既不需要也不想通过抽象接口工作。

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

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