簡體   English   中英

繼承層次結構與多重繼承(C ++)

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

好吧,我在考慮過去幾天的設計決定,因為我仍然不能偏愛另一個,我想也許別人有個主意。

情況如下:我有幾個不同的接口類抽象幾個通信設備。 由於這些設備的性質不同,因此界面也不同,因此並不真正相關。 讓我們稱它們為IFooDeviceIBarDevice 隨着時間的推移可能會添加更多設備類型 語言是C ++。

由於其他組件(從現在開始稱為客戶端)可能想要使用其中一個或多個設備,因此我決定提供一個DeviceManager類來處理運行時對所有可用設備的訪問。 由於設備類型的數量可能會增加,我想平等對待所有設備(從經理的角度來看)。 但是,客戶端將請求某種設備類型(或基於某些屬性的設備)。

我想到了兩種可能的解決方案:

第一種是某種間歇層次結構。 所有設備都將子類化一個公共接口IDevice ,它將提供管理和設備查詢所需的(虛擬)方法(如getProperties()hasProperties() ,...)。 然后, DeviceManager有一個指向IDevice的指針集合,在某些時候,需要從BaseDerived的轉換 - 在管理器中使用模板方法或在客戶端請求之后。

從設計的角度來看,我認為分離管理設備和特定設備本身的界面的問題會更加優雅。 因此,它將導致兩個不相關的接口: IManagedDevice和例如IFooDevice 真實設備需要從兩者繼承以便“成為”特定設備類型並且是可管理的。 經理只管理指向IManagedDevice的指針。 但是,在某些時候,如果客戶端想要使用管理器提供的設備,則需要在現在不相關的類之間(例如,從IManagedDeviceIFooDevice )之間進行轉換

我必須在這里選擇兩個邪惡中較小的一個嗎? 如果是這樣的話會是哪一個? 或者我會錯過什么?

編輯:

關於“管理”部分。 這個想法是讓庫提供各種不同的通信設備(客戶端)應用程序可以使用和共享。 管理僅僅歸結為實例的存儲,注冊新設備和查找某個設備的方法。 為任務選擇“正確”設備的責任取決於客戶端,因為它最了解它對通信的要求。 為了重用並共享可用設備(並且我指的是真實實例而不僅僅是類),我需要一個到所有可用設備的中央訪問點。 我不太喜歡經理本身,但在這種情況下,這是我唯一可以做到的。

我認為訪客模式是更好的選擇。

我認為湯姆建議的內容可能會有所改變,以滿足您的需求:

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函數。

我想我會想要一個簡單的解決方案,為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 ,它們將自動放置在構造的全局列表中,並在銷毀時從全局列表中刪除。

您的所有客戶都可以通過從Device::first開始,然后關注device->next來訪問所有設備的列表。 通過執行dynamic_cast<NeededDeviceType*>(device)客戶端可以檢查設備是否與他們需要的兼容。

當然,也可以在Device級別導出在每種設備類型中實現的任何方法(例如,描述字符串,確保由一個客戶端獨占使用的鎖定方法等)。

乍一看,如果需要管理所有設備,並且使用未知設備無法完成其他任何操作,第一種方法對我來說似乎很好。 一般設備的元數據(例如,名稱,......)通常是管理設備所需的數據。

但是,如果需要分離管理和設備功能之間的接口,則可以使用虛擬繼承 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;
}

在與設備通信時,我完全分離了設備和通信管理器。

我有一個基於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

這更有意義。

至於你有關通用設備接口的建議。 我也從那開始,但后來不確定它的實用性 - 我一直想知道我發送命令的確切設備,我既不需要也不想通過抽象接口工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM