好吧,我在考虑过去几天的设计决定,因为我仍然不能偏爱另一个,我想也许别人有个主意。
情况如下:我有几个不同的接口类抽象几个通信设备。由于这些设备的性质不同,因此界面也不同,因此并不真正相关。让我们称之为 IFooDevice 和 IBarDevice 。随着时间的推移可能会添加更多设备类型语言是C ++。
由于其他组件(从现在开始称为客户端)可能想要使用其中一个或多个设备,因此我决定提供 DeviceManager 类来处理运行时对所有可用设备的访问。由于设备类型的数量可能会增加,我想平等对待所有设备(从经理的角度来看)。但是,客户端将请求某种设备类型(或基于某些属性的设备)。
我想到了两种可能的解决方案:
第一种是某种间歇层次结构。所有设备都将子类化一个通用接口 IDevice ,它将提供管理和设备查询所需的(虚拟)方法(如 getProperties(), hasProperties(),...)。 DeviceManager 然后有一个指向 IDevice 的指针集合,在某些时候,从 Base 到 Derived 的演员阵容将是必要的 - 使用管理器中的模板方法或客户端的请求之后。
从设计的角度来看,我认为分离管理设备和特定设备本身界面的问题会更加优雅。因此,它将导致两个不相关的接口: IManagedDevice ,例如的 IFooDevice 即可。真实设备需要从两者继承以便“成为”特定设备类型并且是可管理的。经理只会管理指向 IManagedDevice 的指针。但是,如果客户端想要使用管理器提供的设备,则在某些时候需要在现在不相关的类之间进行转换(例如,从 IManagedDevice 转换为 IFooDevice )。
我必须在这里选择两个邪恶中较小的一个吗?如果是这样的话会是哪一个?或者我会错过什么?
修改
关于“管理”部分。这个想法是让库提供各种不同的通信设备(客户端)应用程序可以使用和共享。管理仅仅归结为实例的存储,注册新设备和查找某个设备的方法。为任务选择“正确”设备的责任取决于客户端,因为它最了解它对通信的要求。为了重用并共享可用设备(并且我指的是真实实例而不仅仅是类),我需要一个到所有可用设备的中央访问点。我不太喜欢经理本身,但在这种情况下,这是我唯一可以做到的。
答案 0 :(得分:1)
我认为访客模式是更好的选择。
答案 1 :(得分:1)
我认为汤姆建议的内容可能会有所改变,以满足您的需求:
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
函数。
答案 2 :(得分:1)
我想我会想要一个简单的解决方案,为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<NeededDeviceType*>(device)
客户端可以检查设备是否与他们需要的设备兼容。
当然,在Device
级别也可以导出在每种设备类型中实现的任何方法(例如,描述字符串,确保一个客户端独占使用的锁定方法等)。
答案 3 :(得分: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
这更有意义。
至于你有关通用设备接口的建议。我也从那开始,但后来不确定它的实用性 - 我一直想知道我发送命令的确切设备,我既不需要也不想通过抽象接口工作。
答案 4 :(得分:0)
乍一看,如果需要管理所有设备,并且使用未知设备无法完成其他任何操作,第一种方法对我来说似乎没问题。一般设备的元数据(例如名称,......)通常是管理设备所需的数据。
但是,如果您需要将管理和设备功能之间的界面分开,则可以使用virtual inheritance。 IManagedDevice 和 IFooDevice 都是同一具体设备的接口,因此它们都有一个共同的虚拟基础 IDevice 。
具体而言(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;
}