[英]Inheritance hierarchy vs. multiple inheritance (C++)
好吧,我在考慮過去幾天的設計決定,因為我仍然不能偏愛另一個,我想也許別人有個主意。
情況如下:我有幾個不同的接口類抽象幾個通信設備。 由於這些設備的性質不同,因此界面也不同,因此並不真正相關。 讓我們稱它們為IFooDevice和IBarDevice 。 隨着時間的推移可能會添加更多設備類型 語言是C ++。
由於其他組件(從現在開始稱為客戶端)可能想要使用其中一個或多個設備,因此我決定提供一個DeviceManager類來處理運行時對所有可用設備的訪問。 由於設備類型的數量可能會增加,我想平等對待所有設備(從經理的角度來看)。 但是,客戶端將請求某種設備類型(或基於某些屬性的設備)。
我想到了兩種可能的解決方案:
第一種是某種間歇層次結構。 所有設備都將子類化一個公共接口IDevice ,它將提供管理和設備查詢所需的(虛擬)方法(如getProperties() , hasProperties() ,...)。 然后, DeviceManager有一個指向IDevice的指針集合,在某些時候,需要從Base到Derived的轉換 - 在管理器中使用模板方法或在客戶端請求之后。
從設計的角度來看,我認為分離管理設備和特定設備本身的界面的問題會更加優雅。 因此,它將導致兩個不相關的接口: IManagedDevice和例如IFooDevice 。 真實設備需要從兩者繼承以便“成為”特定設備類型並且是可管理的。 經理只管理指向IManagedDevice的指針。 但是,在某些時候,如果客戶端想要使用管理器提供的設備,則需要在現在不相關的類之間(例如,從IManagedDevice到IFooDevice )之間進行轉換 。
我必須在這里選擇兩個邪惡中較小的一個嗎? 如果是這樣的話會是哪一個? 或者我會錯過什么?
編輯:
關於“管理”部分。 這個想法是讓庫提供各種不同的通信設備(客戶端)應用程序可以使用和共享。 管理僅僅歸結為實例的存儲,注冊新設備和查找某個設備的方法。 為任務選擇“正確”設備的責任取決於客戶端,因為它最了解它對通信的要求。 為了重用並共享可用設備(並且我指的是真實實例而不僅僅是類),我需要一個到所有可用設備的中央訪問點。 我不太喜歡經理本身,但在這種情況下,這是我唯一可以做到的。
我認為訪客模式是更好的選擇。
我認為湯姆建議的內容可能會有所改變,以滿足您的需求:
class IManagedDevice
{
IDevice* myDevice;
/* Functions for managing devices... */
};
在這種情況下, IDevice
是一個所有設備都繼承的空接口。 它沒有帶來任何實際好處,只是讓類層次結構處理更加可忍受。
然后,您可以通過某種設備類型ID詢問特定設備( IFooDevice
或IBarDevice
)。
如果您只需要一個公共代碼來管理設備,然后將每個設備傳遞到適當的位置,我認為您可以使用以下內容:
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
級別導出在每種設備類型中實現的任何方法(例如,描述字符串,確保由一個客戶端獨占使用的鎖定方法等)。
乍一看,如果需要管理所有設備,並且使用未知設備無法完成其他任何操作,第一種方法對我來說似乎很好。 一般設備的元數據(例如,名稱,......)通常是管理設備所需的數據。
但是,如果需要分離管理和設備功能之間的接口,則可以使用虛擬繼承 。 IManagedDevice和IFooDevice都是同一具體設備的接口,因此它們都有一個共同的虛擬基本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.