【问题标题】:Best design pattern for switching between hardware interfaces在硬件接口之间切换的最佳设计模式
【发布时间】:2016-09-23 23:26:33
【问题描述】:

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

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

我的问题是我想创建一个“相机”对象(用于 GUI),它有一个“FrameGrabber”卡对象,用于获取数据和发送/接收命令和数据.但是,我有许多不同类型的图像采集卡。我们称它们为 CoaxGrabberA、CoaxGrabberB、LinkGrabberA 和 LinkGrabberB。 CoaxGrabbers 需要一组与 LinkGrabbers 不同的初始化、setter 和 getter 参数。

因此,我认为我需要使用两个级别的继承,但从我所阅读的所有内容来看,应该很少使用继承,并且应该优先使用组合。因此,我非常怀疑我的设计决策,并寻求某种更好的设计。这是一些半生不熟的代码的示例。这有点冗长,但重要的部分是 CoaxGrabberA、CoaxGrabberB、LinkGrabberA 和 LinkGrabberB 是 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 设计模式知之甚少,但这感觉像是一个已解决的问题,我觉得我在重新发明轮子。有没有更好、更成熟的方法来实现我正在尝试做的事情?

【问题讨论】:

  • 为什么是“类”而不是“类”?是否有一些特殊的“#define class Class”?
  • 不,我只是在将所有内容输入窗口时搞砸了。可能是通过复制/粘贴传播的一个错误。我应该仔细检查我的代码。这些都不会编译,它只是为了让我大致了解我想要实现的目标。长话短说吧,应该是小写的
  • 好问题 - 为什么会被否决?
  • 我很想将所有配置数据存储在 property map (std::map<std::string, std::string>) 中,并让每个 Graber 类型将其配置信息转换为相关类型 (或添加一些通用转换函数)。然后每个Grabber 类型可以报告它需要为用户virtual std::vector<std::string> getPropertyList() const = 0; 设置哪些属性名称,因此GUI 知道要请求什么以及要设置什么属性。 Example Property Map.

标签: c++ user-interface inheritance design-patterns


【解决方案1】:

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

如果不知道更多细节,我无法判断是否可以完成,但如果可以的话,如果您明确定义Port 接口并在FrameGrabber 中聚合端口,似乎有助于使设计更简洁而不是有多个FrameGrabber 实现。这将有利于组合而不是继承,并成为Strategy pattern 的实现。

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

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

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

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

【讨论】:

    【解决方案2】:

    你的设计模式叫做“工厂”,继承没有错 (https://en.wikipedia.org/wiki/Factory_method_pattern)

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

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

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

    在这种情况下:

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

    【讨论】:

    • 谢谢,我了解shared_ptrs,但他的工厂方法对我来说仍然有点困惑。我了解发生了什么,但我不一定了解您为什么需要它。看起来它只是一个接受选择并返回一个孩子(假装是父母)的函数。我会做更多的研究。感谢您的帮助!
    • 您需要它,因为它将对象创建逻辑封装在同一个地方。真正的工厂更复杂,因为您必须指定很多参数才能创建正确的对象。工厂还允许在多个组件之间重用创建代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 2012-03-28
    相关资源
    最近更新 更多