Multiple subclass hierarchy

328 views Asked by At

I have the class hierarchy:

class AbstractProcess
{
    virtual void Do() = 0;
};

class BuildProcess : public AbstractProcess
{
    virtual void Do();
};

class UpdateProcess : public AbstractProcess
{
    virtual void Do();
};

However, I now want to introduce 2 (4?) new subclasses as a process can also be manual or automated. The following looks to be too long winded:

class ManualBuildProcess : public BuildProcess
{
    virtual void Do();
};

class ManualUpdateProcess : public UpdateProcess
{
    virtual void Do();
};

class AutomatedBuildProcess : public BuildProcess
{
    virtual void Do();
};

class AutomatedUpdateProcess : public UpdateProcess
{
    virtual void Do();
};

This will only get worse if I want to introduce additional subclasses of either Build/Update or Manual/Automated processes.

Is there a better design?

4

There are 4 answers

0
Petr On

I can suggest two approaches.

One is to make your Manual...Process templated:

template<class Process> class ManualProcess: public Process {
    virtual void Do() {
        ...
        // you can access base class data:
        Process::Do();
    }
};

// use like
ManualProcess<BuildProcess> process; // instead of ManualBuildProcess

Secondly, have two hierarchies and prefer composition over inheritance, something like

class Operator;
class ManualOperator: public Operator...
class AutomaticOperator: public Operator...

class Process {
    Operator* operator;
    ...
};
class BuildProcess: public Process...
class UpdateProcess: public Process...
...

or vice-versa (Operator holding a pointer to a Process), or even in a symmetrical way with Operators and Processes not knowing about each other, but with a third class holding a pointer to Operator and a pointer to Process.

Of course, this all applies only if there is some code common for all Manual cases and some code common for all Build cases, etc. If each of these version have its own code, then you obviously have no choice.

0
cvesters On

C++ allows multiple inheritance. This may make the class hierarchy better, but with such little context it's hard to say what's best.

But you may think of having an AutomatedProcess and ManualProcess directly as subclasses from the AbstractProccess, just as the Build Process and UpdateProcess.

However if all combinations do exist, then you would have to make a class for each one of them, still resulting in the amount of classes you have now.

You may want to think what an automated/manual process does. It may not need to be a separate class, but it could be a member of a class.

0
cdonat On

Apart from vaious object oriented approaches like e.g. strategy pattern, you could alo use Templates in C++:

template <typename Action> class Process {
    Action a;
    public:
        Process(const Action& action): a{action} {};
        void do() { a.do(); };
};

template <typename Interface> class BuildAction {
    Interface i;
    public:
        BuildAction(const Interface& interface): i{intreface} {};
        BuildAction(const BuildAction& other): i{other.i} {};
        void do() { i.build(); };
};

template <typename Interface> class UpdateAction {
    Interface i;
    public:
        UpdateAction(const Interface& interface): i{intreface} {};
        UpdateAction(const UpdateAction& other): i{other.i} {};
        void do() { i.update(); };
};

class ManualInterface {
    public:
        void build();
        void update();
};

class AutomaticInterface {
    public:
        void build();
        void update();
};

typedef Process<BuildAction<ManualInterface>>     ManualBuildProcess;
typedef Process<BuildAction<AutomaticInterface>>  AutomaticBuildProcess;
typedef Process<UpdateAction<ManualInterface>>    ManualUpdateProcess;
typedef Process<UpdateAction<AutomaticInterface>> AutomaticUpdateProcess;

I might have some suboptimal Assumptions for your actual Usecase. You might find better structures like e.g.:

typedef Process<BuildAction, ManualInterface> ManualBuildProcess;

Of course you can combine templates with OO-patterns as well:

Process<ManualInterface> myManualBuildProcess{BuildStrategy{}};

BTW.: I have used C++11 syntax. In case you only have C++98, you'll have to adapt the code accordingly.

0
Andriy Tylychko On

I would minimise inheritance here and use composition. Manual/Automated is one plane, Build/Update is another. Seems like your Process needs strategies defined in both planes. This can be done as:

struct Executor;
struct Operator;

template <typename ExecutorT, typename OperatorT>
struct Process
{
    std::unique_ptr<Executor> executor_;
    std::unique_ptr<Operator> operator_;

    Process():
        executor_(new ExecutorT()),
        operator_(new OperatorT())
    {}

    void Do()
    {
        executor_->Do();
        operator_->Do();
        // do something else
    }
};

The rest is pretty simple and now you can concentrate on your business logic:

struct Executor
{
    virtual void Do() = 0;
};

struct Manual : Executor
{
    virtual void Do() override {}
};

struct Automated : Executor
{
    virtual void Do() override {}
};

struct Operator
{
    virtual void Do() = 0;
};

struct Build : Operator
{
    virtual void Do() override {}
};

struct Update : Operator
{
    virtual void Do() override {}
};