1

I'm currently working on the control software for a system that includes a lot of user controllable hardware and also has many configurations. Configurations in this context refers to a specific combination of hardware. Each configuration can be thought of as it's own "product" for arguments state.

In previous iterations of the software, hardware configurations were varied at compile time (working in C++) which means we had to generate a lot of installers for each release. Our goals with the new interface are the following:

  • Determine hardware connections at run time
  • Distribute one installer

In the current design, we're making heavy use of inheritance and dynamic polymorphism. The gist is we have a GUI layer that communicates via view models (MVVM architecture) to the back end (model layer). In the view models, we're using abstract interfaces for each concrete type of hardware in real life (i.e. Camera, Robot and so on). There is also an interface for a Hardware object which means we're also using multiple inheritance.

I've recently been running into talks and posts that show how intrusive inheritance can be but I'm struggling to see what other design we could use to solve our design problem and still address our goals with this software.

As an example, consider the following sample pseudo code (C++). Say we have a configuration that has a camera and a single axis robot.

We'll define the hardware interface as:

class HardwareInterface {
  public:
    virtual bool connect() = 0;
    virtual bool disconnect() = 0;
    virtual bool reconnect() = 0;
    virtual bool isConnected() = 0;
    virtual std::string name() = 0;
    virtual std::string serialNumber() = 0;
    // ... more
};

class CameraInterfaceCallback {
  public:
    virtual void newFrameReady(void* data, int width, int height, int depth, int type);
};

class CameraInterface : public CameraInterfaceCallback 
{
  public:
    virtual void setGain(double gain) = 0;
    virtual void setFps(double fps) = 0;
};

class RobotInterfaceCallback {
  public:
    virtual void moveStarted(RobotInterface* robot, int axis);
    virtual void moveStopped(RobotInterface* robot, int axis);
};

class RobotInterface : public RobotInterfaceCallback {
  public:
    virtual void moveTo(double position, int axis) =0;
    virtual void moveBy(double move_by, int axis) = 0;
};

class MyCamera : public QObject, public HardwareInterface, public CameraInterface {
  // implementation
  Q_OBJECT
  signals:
     void newFrameReady(void* data, int width, int height, int depth, int type);
};

class MyRobot : public QObject, public HardwareInterface, public RobotInterface {
  // implementation
  Q_OBJECT
  signals:
    void moveStarted(RobotInterface* robot, int axis);
    void moveStopped(RobotInterface* robot, int axis);
};

In the GUI of the project, once the concrete object is created then the object is bound to via it's abstract interface. One other complication is that we use Qt5 so the "Callbacks" are actually implemented as signals and emitted in the concrete hardware class. This way we can have a view that is meant only for the RobotInterface and just bind the view to the interface and be done with it. This means that if down the line we add MyOtherRobot, the front end doesn't really care.

My questions are:

  • Is there anything intrinsically wrong with our approach (which feels like abuse of inheritance)?
  • Is there any advantage to using actual callbacks versus using signals and slots as part of the Qt5 framework?

Notes:

  • The application is multi-threaded, all of the hardware objects exist on the main thread. So communication between threads, in general, is not needed.
  • The design that is currently implemented is only used internally for the specific software we are developing. It is not a "public" API that is exposed to our users. From a user's perspective, the control software is a black box.
  • Your first question is unanswerable. What do you mean by "better?" You're clearly unsatisfied with your current design. What is the source of your dissatisfaction? – Robert Harvey May 1 at 18:33
  • @RobertHarvey Good point, I've re-phrased the question. – Developer Paul May 1 at 19:50
  • "Actual callbacks" have more problems (disadvantages) than the alternatives to callbacks. "Actual callbacks" are okay if you restrict what code can be executed within the callback. A better way to design is to create sequence diagrams that the hardware needs, then identify the parts that needs to be sync/async/both, then use the appropriate implementation for each interaction. Hardware needs may override software design considerations. – rwong May 1 at 20:37
  • Several more questions. (1) Is the robotic system sensitive to actual (wall-clock) timing? I.e. does the software need to perform actions/decisions within a time limit in order to function correctly / not to cause danger? (2) I'm not familiar with Qt, but is it relatively easy/difficult to change between sync/async by changing connection type? In addition to changing connection type, what other code change effort are needed to change between sync/async? My limited experience was that those are significant efforts. – rwong May 1 at 21:08
  • 1
    @rwong Good point about callbacks and the need for a sequence diagram. Those may be invaluable in this case actually. As for your other questions: 1) Not the robotics itself, all moves are relatively slow and safe, but we do need accurate timing when coordinating movements of robotics with other hardware in the system. 2) It's relatively easy. Signal/slots can be asynchronous and you can actually move an entire object to a new thread pretty painlessly. – Developer Paul May 2 at 3:10
2

At first glance, the fact that XxxInterface inherits from XxxInterfaceCallback appears strange, until you see that it is done to support the Qt signal/slot mechanism.

Other than that, you appear to be using inheritance exactly for what it is intended to be used for: abstracting away which concrete implementation the rest of the application is using.

The only drawback to using the Qt signal/slot mechanism is that you are now tied to the Qt framework. You will have to decide for yourself how big of a drawback that is.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.