mercredi 25 mai 2016

Best design pattern for switching between hardware interfaces

I am seeking advice about whether or not my current approach makes sense. If not, I'd like a recommendation about some type of design pattern than can be used to replace my current intuition.

My premise is that I have a camera that requries a frame grabber card with either a CameraLink or CoaXPress cable interface to connect to a PC. All communication and data transfer between the camera and computer must be controlled using the frame grabber card, so the coupling between these two physical hardware objects is very tight.

My problem is that I want to create a "Camera" object (for a GUI) which has-a "FrameGrabber" card object that it uses to acquire data and send/receive commands and data. However, I have many different frame grabber cards of many different types. Lets call them CoaxGrabberA, CoaxGrabberB, LinkGrabberA, and LinkGrabberB. The CoaxGrabbers require a different set of parameters for initialization, setters, and getters than the LinkGrabbers.

As a result, I think I need to use two levels of inheritance, but from everything I've read, inheritance should be used very rarely, and composition should be favored. As such, I am extremely doubting my design decisions, and seek some type of better design. Here's an example of some half-baked code. It's a bit lengthy, but the important part is the concept that CoaxGrabberA, CoaxGrabberB, LinkGrabberA, and LinkGrabberB are grandchildren of FrameGrabber, which must be accessible to Camera. Everything else is to fill in the meat for details you may need.

My goal is to, at runtime, select whichever framegrabber (of any make/model/interface) that I want to use for my Camera object. Furthermore, I want to easily access all of the member functions that are unique to that grandchild framegrabber type to modify the behavior of the hardware at runtime.

My question is "is there a particular design pattern to match my problem that I don't know about, which would make my life easier than using my naive, intuitive approach"

//-----------------------------------------
// 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:
    CameraSystem() {
        _frameGrabber = NULL;
    }

    ~CameraSystem() { 
        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);
    }
}

Any and all advice will be greatly appreciated. Long story short, I know little about OO design patterns, but this feels like a solved problem and I feel like I'm reinventing the wheel. Is there a better, more established way to implement what I am trying to do?

Aucun commentaire:

Enregistrer un commentaire