2

I am writing a utility in C++ with Qt which communicates with an embedded device. The program was originally going to be used to just plot data from the device, but a new requirement has been added (not by me; no control over it). So now it has two modes, and thus two classes:

  1. DevicePlotWindow
  2. DeviceDebugWindow

Both modes do exactly the same thing except for the output -- i.e. plot data and write a log message to the debug console, respectively.

Here's where it gets a bit messy. The second mode was already an existing standalone tool, which is going to be deprecated, and merged into this program. The request from the users is to keep the same look and feel as the older tool.

This means that in my utility, I have two very similarly functional modes, but look completely different.

I have already implemented this by copy-pasting the UI elements and their functionality in terms of communication with the device. But I hate breaking DRY.

What is the best, cleanest way to "share" the UI elements and their corresponding member function handlers with both modes (classes)? I am aware of inheritance, but I'd like to hear what others have in mind and the best way to go about this.

Example:

class DevicePlotWindow : public QWidget {
    private:
        ...
        Plot plot_;
        QSpinBox address_;
        QSpinBox data_;

    public:
        ...
        void plot();
        void on_address_value_changed(int val);
        void on_data_value_changed(int val);
};

class DeviceDebugWindow : public QWidget {
    private:
        ...
        QPlainTextEdit console_;
        QSpinBox address_;
        QSpinBox data_;

    public:
        ...
        void write_console(QString const &msg);
        void on_address_value_changed(int val);
        void on_data_value_changed(int val);
};
1

The most straightforward solution I'd see and would generally want to use, if you have two classes which are nearly identical but only differ in how they output data, is make one class which has what is common and depend on an abstraction for the output.

enter image description here

Don't really see a more straightforward way than that. Of course IOutput could be one or more function objects in C++ as opposed to an interface as one example, but same kind of basic idea. And you can use composition to inject an instance of a window with the appropriate outputter on construction which it can store as a member as opposed to subtyping windows (which would lead to more coupling). Another C++ specific alternative is to make Window a class template and generate the two versions of the window (one plotting window, one debug window), still relying on the same basic interface/abstraction (also same basic idea).

Main trick is to design that appropriate IOutput interface. For example, if you need to store Qt widgets for the "debug" version and add them to the window layout, you might need to pass the window's layout along to add any appropriate widgets to it, and then call an abstract output kind of function to update the outputter's widget appropriately. Or you might optionally return a QWidget in IOutput which the window adds to its layout. Something of this sort. You need some way to grab the widget for an outputter for the window to add to its layout through this abstraction.

And likewise these two functions:

    void plot();
    void write_console(QString const &msg);

Need to be unified into one output idea/signature. They become the analogical abstract output function. So you want to pass something along which provides enough information for both outputters to do their thing: for the plotter to plot the data and for the debug version to convert it to a string and show it inside a text field.

I'd need to see a bit more code to suggest the most straightforward way to come up with the design of the analogical IOutput. But hopefully you get the idea and can see how to do it rather quickly, and that's how I'd suggest to tackle this problem. You have the concrete window and it depends on an abstract outputter interface, and different concrete outputters can substitute the appropriate functionality required for a specific kind of output.

What is the best, cleanest way to "share" the UI elements and their corresponding member function handlers with both modes (classes)?

Just for this general type of problem, see if you can invert the thinking a bit. Instead of thinking how you share the same redundant code in two classes that want to share most of the same code, implement the code once and think about how to create an abstraction which allows the unique parts, not shared, to diverge. Similar strategy if you are refactoring one class in hindsight. Instead of copying and pasting its code somewhere else only to change a little bit, see how you can refactor the class to avoid directly depending on concrete details for the parts that need to diverge while coming up with an abstraction to depend on instead.

It's the key to kind of arriving at these types of abstractions upfront rather than in hindsight. For a basic CS-type example, instead of implementing a quicksort for two different random-access data structures and then trying to figure out how to "share the code", implement it one time away from any specific data structure and as you are implementing, come up with the appropriate abstraction (random-access iterator concept, e.g.) that allows it to work for any conforming data structure.

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.