用于在硬件接口之间切换的最佳设计模式

Best design pattern for switching between hardware interfaces

本文关键字:最佳 设计模式 之间 硬件 接口 用于      更新时间:2023-10-16

我正在寻求关于我目前的方法是否合理的建议。如果没有,我想要一个关于某种类型的设计模式的建议,可以用来取代我目前的直觉。

我的前提是我有一台相机,它需要一个带有CameraLink或CoaXPress电缆接口的帧捕获卡来连接到PC。相机和计算机之间的所有通信和数据传输都必须使用帧捕获卡进行控制,因此这两个物理硬件对象之间的耦合非常紧密。

我的问题是我想创建一个"Camera"对象(用于GUI),它有一个"FrameGrabber"卡对象,用于获取数据和发送/接收命令和数据。然而,我有许多不同类型的不同帧抓取卡。让我们称它们为CoaxGrabberA、CoaxGrabblerB、LinkGrabberA和LinkGrabber B。CoaxGrabber需要一组与LinkGrabber不同的初始化、setter和getter参数。

因此,我认为我需要使用两个级别的继承,但从我读到的所有内容来看,继承应该很少使用,而组合应该更受欢迎。因此,我非常怀疑自己的设计决定,并寻求某种更好的设计。下面是一些半生不熟的代码示例。这有点长,但重要的是CoaxGrabberA、CoaxGrabblerB、LinkGrabberA和LinkGrabber B是FrameGrabber的孙子,必须可以访问Camera。其他一切都是为了填补你可能需要的细节。

我的目标是在运行时选择要用于相机对象的任何帧抓取器(任何品牌/型号/接口)。此外,我想轻松访问孙帧抓取器类型所特有的所有成员函数,以在运行时修改硬件的行为。

我的问题是"是否有一种特定的设计模式来匹配我不知道的问题,这会让我的生活比使用我天真、直观的方法更轻松">

//-----------------------------------------
// Parent Class
//=========================================
class FrameGrabber {
public:
virtual void sendCommandString(std::string cmd) = 0;
virtual void startAcquisition() = 0;
virtual void stopAcquisition() = 0;
};

//-----------------------------------------
// Children Classes
//=========================================
class CoaxGrabber : FrameGrabber {
public:
//functions unique to coax grabbers
virtual void setCommAddress(int commAddress) = 0;   
virtual void setStatusPort(int statusPort) = 0;    
//functions universal to all grabbers
virtual void sendCommandString(std::string cmd) = 0; 
virtual void startAcquisition() = 0;                 
virtual void stopAcquisition() = 0;  
protected:
int _commAddress;
int _statusPort;        
};

class LinkGrabber : FrameGrabber {
public:
//functions unique to link grabbers
virtual void setBaudRate(int baudRate) = 0;
virtual void setNumChannels(int numChannels) = 0;
//functions universal to all grabbers
virtual void sendCommandString(std::string cmd) = 0;    
virtual void startAcquisition() = 0;
virtual void stopAcquisition() = 0;
protected:
int _baudRate;
int _numChannels;
};

//-----------------------------------------
// Grandchildren Classes
//=========================================
class CoaxGrabberA : public CoaxGrabber {
//identical public members as CoaxGrabber
//different implementation using
//different low-level API, ex: BitFlow
}

class CoaxGrabberB : public CoaxGrabber {
//identical public members as CoaxGrabber
//different implementation using
//different low-level API, ex: Kaya
}

class LinkGrabberA : public LinkGrabber {
//identical public members as LinkGrabber
//different implementation using
//different low-level API, ex: NationalInstruments
}

class LinkGrabberB : public LinkGrabber {
//identical public members as LinkGrabber
//different implementation using
//different low-level API, ex: Imperx
}

//-----------------------------------------------------
// Finally, my Camera object, nothing too interesting here
//=====================================================
class Camera {
public:
Camera() {
_frameGrabber = NULL;
}
~Camera() { 
delete _frameGrabber;
}
void setGrabber(FrameGrabber* newGrabber)
{
delete _frameGrabber;
_frameGrabber = newGrabber;
}
void startAcquisition() {
_frameGrabber.startAcquisiton();
}
void stopAcquisition() {
_frameGrabber.stopAcquisition();
}
int setSensitivity(int sens) {
_frameGrabber.sendCommandString("sens=" + std::to_string(sens)); 
}
private:
FrameGrabber* _frameGrabber;
};

//-----------------------------------------
// This is why I don't like my Camera object
// the actual end-user interface smells
//=========================================
class CameraGui : QMainWindow
{
public:
void setGrabberType(int type);
void setCoaxGrabberCommAddress(int address);
void setLinkGrabberBaudRate(int rate);
CameraSystem _myCamera;
CoaxGrabber* _myCoaxGrabber;
LinkGrabber* _myLinkGrabber;
};

//---------------------------------------------------------------
//This function smells to me, but I cannot think of any other way
//of course, int type will be enum in actual program.
//===============================================================
void CameraGui::setGrabberType(int type) {
switch (type) {
case 0: 
delete _myCoaxGrabber;
_myCoaxGrabber = new CoaxGrabberA();
_myCamera.setGrabber(&_myCoaxGrabber); 
break;
case 1: 
delete _myCoaxGrabber;
_myCoaxGrabber = new CoaxGrabberB();
myCamera.setGrabber(&_myCoaxGrabber)); 
break;
case 2: 
delete _myLinkGrabber;
_myLinkGrabber = new LinkGrabberA();
_myCamera.setGrabber(&_myLinkGrabber); 
break;
case 3: 
delete _myLinkGrabber;
_myLinkGrabber = new LinkGrabberB();
_myCamera.setGrabber(&_myLinkGrabber); 
break;
}
}
//---------------------------------------------------------------
// this method of setting parameters also smells to me,
// since this data is linked to the Camera object, which
// will have no way of knowing whether the state of its
// framegrabber changed... furthermore, if I change framegrabbers,
// none of the parameter settings (state) will be remembered.
// the user will need to set them all over again.
// the only way I know to circumvent this is to allocate memory for
// every type of framegrabber, and broadcast all state changes to
// all applicable parent grabbers, which will reside in permanent
// memory until the application closes.
//===============================================================
void CameraGui::setCoaxGrabberCommAddress(int address) {
if(myCoaxGrabber != NULL) {
myCoaxGrabber->setCommAddress(address);
}
}
//likewise smell
void CameraGui::setLinkGrabberBaudRate(int rate) {
if(myLinkGrabber != NULL) {
myLinkGrabber->setBaudRate(rate);
}
}

如有任何建议,我们将不胜感激。长话短说,我对OO设计模式知之甚少,但这感觉像是一个解决了的问题,我觉得我在重新发明轮子。有没有更好、更成熟的方法来实现我想要做的事情?

您的设计模式被称为"工厂",继承没有错(https://en.wikipedia.org/wiki/Factory_method_pattern)

在继承和聚合之间进行选择时,我们应该使用的经验法则:

  • 如果某个东西反映了"是"关系(例如CoaxGrabber是FrameGrabber),则使用继承
  • 如果某个东西反映了"有"关系(例如CameraGui有FrameGrabber),请使用聚合

我建议使用智能指针(例如std::shared_ptr)而不是新指针,并删除当前使用的指针,这将使代码更易于管理,不易出错。

在这种情况下:

class Camera {
public:
CameraSystem() {} // don't need explicit initialization
~CameraSystem() {} // resource in shared_ptr will be deleted automatically
void setGrabber(const std::shared_ptr<FrameGrabber>& newGrabber)
{
_frameGrabber = newGrabber;
}
void startAcquisition() {
_frameGrabber->startAcquisiton(); // note -> instead of .
}
// ....
private:
std::shared_ptr<FrameGrabber> _frameGrabber;
};

如果使用工厂:

void CameraGui::setGrabberType(int type) {
_myCamera.setGrabber(GrabberFactory::createGrabber(type));
}
class GrabberFactory {
public:
std::shared_ptr<FrameGrabber> createGrabber(int type) {
switch (type) {
case GrabberTypeCoaxA: return {new CoaxGrabberA()};
case GrabberTypeCoaxB: return {new CoaxGrabberB()}; 
default: throw std::invalid_argument("Invalid grabber type");
}
}
};

因此,我认为我需要使用两个级别的继承,但从我读过的所有内容,继承应该很少使用,而且构图应该受到青睐。

我不知道在不了解更多细节的情况下是否可以做到这一点,但如果可以的话,如果你能明确定义一个Port接口并在FrameGrabber内聚合端口,而不是有多个FrameGrabber实现,这似乎会有助于使设计更清晰。这将有利于组合而非继承,并成为战略模式的实现。

之后,如果您希望每个端口都有自己的特定API,那么端口设置UI显然必须更加复杂,因为它需要知道如何处理不同的具体端口。有帮助的是用每种端口的相应控制器或视图模型来实现各种PortSettingsView。例如由BitflowCoaxPortSettingsViewModel驱动的BitflowCoaxPortSettingsView等。如果你不熟悉类似MVC的架构,我建议你了解它们。

UI只需要根据端口类型实例化适当的具体PortSettingsView和端口设置视图模型。通过这样做,视图和视图模型将始终知道他们正在配置哪种端口,从而可以轻松处理特定于端口的行为。

可能还有其他选择。也许应该使用更抽象的方法来配置端口,以便所有端口都可以通过相同的API进行配置。例如,您可以使用键值对数据结构来保存配置。然后,所有端口都可以实现类似public void reconfigure(PortSettings settings)方法的东西。我不知道它是否适合你的问题。

最后,请记住,使用工厂来抽象复杂的创建过程总是一个好主意。例如,您可以将该任务委托给工厂,而不是在UI中直接在端口类型上使用switch语句来实例化正确的视图和视图模型。