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

发布于 2024-11-05 10:19:25 字数 1276 浏览 1 评论 0原文

好吧,过去几天我一直在考虑一项设计决策,由于我仍然不能偏爱其中一个,所以我想也许其他人有一个想法。

情况如下:我有几个不同的接口类,抽象了多个通信设备。由于这些设备的性质不同,它们的接口也不同,因此并不真正相关。我们将它们称为 IFooDeviceIBarDevice。随着时间的推移,可能会添加更多设备类型。语言是C++。

由于其他组件(从现在起称为客户端)可能想要使用一个或多个这些设备,因此我决定提供一个 DeviceManager 类来处理运行时对所有可用设备的访问。由于设备类型的数量可能会增加,我想平等地对待所有设备(从管理者的角度来看)。但是,客户端将请求特定的设备类型(或基于某些属性的设备)。

我想到了两种可能的解决方案:

第一个是某种内部层次结构。所有设备都将继承一个公共接口IDevice,该接口将提供管理和设备查询所需的(虚拟)方法(例如getProperties()hasProperties(),...)。然后,DeviceManager 拥有指向 IDevice 的指针集合,并且在某个时刻从BaseDerived 的转换将是必要的 - 使用管理器中的模板方法或在客户端发出请求之后。

从设计的角度来看,我认为将管理设备的关注点与特定设备本身的界面分开会更优雅。因此,它将导致两个不相关的接口:IManagedDevice 和例如IFooDevice。真实的设备需要继承两者,才能“成为”特定的设备类型并易于管理。管理器仅管理指向IManagedDevice的指针。然而,在某些时候,如果客户端想要使用管理器提供的设备,则需要在现在不相关的类之间进行转换(例如从IManagedDeviceIFooDevice)。

我必须在两害相权取其轻吗?如果是的话,会是哪一个呢?或者我错过了什么?

编辑:

关于“管理”部分。这个想法是让库提供不同(客户端)应用程序可以使用和共享的各种通信设备。管理仅仅归结为实例的存储、注册新设备和查找特定设备的方法。为任务选择“正确”设备的责任取决于客户端,因为它最了解它对通信提出的要求。为了重用并共享可用设备(我指的是真实实例而不仅仅是类),我需要一个对所有可用设备的中央访问点。我不太喜欢经理本身,但在这种情况下这是我唯一能想到的。

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. More device types may be added over time. The language is 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. 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(), ...). 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.

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 e.g. 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. However, at some point there will be the need to cast between now unrelated classes (e.g. from IManagedDevice to IFooDevice) if a client wants to use a device provided by the manager.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

甜心小果奶 2024-11-12 10:19:25

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

I think the visitor pattern is a better choice for this.

北座城市 2024-11-12 10:19:25

我认为 Tom 的建议可能会稍微改变一下以满足您的需求:

class IManagedDevice
{
    IDevice* myDevice;

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

在这种情况下,IDevice 是一个所有设备都继承自的空接口。它没有带来任何真正的好处,只是使类层次结构的处理稍微更容易忍受。

然后,您可以通过某种设备类型 ID 来请求特定设备(IFooDeviceIBarDevice)。

如果您所需要的只是有一个通用代码来管理设备,然后将每个设备传递到适当的位置,我认为您可以这样做:

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

通过管理器调用 Handle 函数。

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. 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.

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.

野の 2024-11-12 10:19:25

我想我会寻求一个简单的解决方案,即拥有一个 Device 基类,负责在全局设备列表中注册设备,然后使用静态方法来查找它们。类似于:

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&);
 };

然后您可以从 Device 派生所有设备,它们将在构造时自动放置在全局列表中,并在销毁时从全局列表中删除。

您的所有客户端都可以通过从 Device::first 开始并遵循 device->next 来访问所有设备的列表。通过执行dynamic_cast(device),客户端可以检查设备是否与他们需要的兼容。

当然,在每种设备类型中实现的任何方法(例如,描述字符串、确保一个客户端独占使用的锁定方法等)也可以在设备级别导出。

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. 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.

All your clients will be able to visit the list of all devices by starting from Device::first and following device->next. By doing a dynamic_cast<NeededDeviceType*>(device) clients can check if the device is compatible with what they need.

Of course any method that is implemented in every device type (e.g. a description string, a locking method to ensure exclusive use by one client and the like) can be exported also at the Device level.

旧街凉风 2024-11-12 10:19:25

当与设备通信时,我将设备和通信管理器完全分开。

我有一个基于 Boost.Asio 的简单通信管理器。该接口类似于

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

我随后为 TCP(以太网)和串行通信实现的接口。

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

然后,每个设备都包含(或指向)(而不是继承)一个 coms_manager 对象
例如:

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

然后,您可以通过更改指针指向的内容来交换通信方法。

对我来说,这没有多大意义(示波器是通过串行或通过以太网连接的,所以我实际上选择了

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

并且我现在使用

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

这更有意义。

至于你关于拥有通用设备接口的建议。我开始也有这个,但后来不确定它的实用性 - 我总是想确切地知道我正在向哪些设备发送命令,我既不需要也不想通过抽象接口进行工作。

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. 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.

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:

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.

淑女气质 2024-11-12 10:19:25

乍一看,如果所有设备都需要管理并且无法使用未知设备完成其他操作,那么第一种方法对我来说似乎很好。通用设备的元数据(例如名称等)通常是管理设备所需的数据。

但是,如果您需要分离管理功能和设备功能之间的接口,则可以使用虚拟继承< /a>.
IManagedDeviceIFooDevice 都是同一具体设备的接口,因此它们都有一个共同的虚拟基 IDevice

具体(运行代码)

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

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 (e.g. 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.

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文