Sanchises
Sanchises

Reputation: 863

How to create a loose coupling between parts of a project?

Introduction: I come from a mechanical engineering background, but took a class in embedded software programming (on a lovely little robot) with the intention of improving some skills I had in programming already. However, the class was largely unsatisfactory in what I hoped to achieve (basically, it taught the basics of c++ with some very superficial composition patterns).

Question We were told to make our code somewhat object oriented by defining classes for various parts of the code. Since all the parts were very dependent of each other, the general structure looked as follows (basically, a Drive, Sensors and WorldModel class with some dependencies, and a Director class trying to make our robot solve the task at hand)

class Drive{
    void update();
    Drive(Sensors & sensors);
private:
    Sensors & sensors
};

class Sensors{
    void update();
}

class WorldModel {
    void update();
    WorldModel(Sensors & sensors, Drive & drive);
private:
    Sensors & sensors;
    Drive & drive;
};

class Director {
    void update();
    Director(Sensors & sensors, Drive & drive, WorldModel & worldmodel);
private:
    Sensors & sensors;
    Drive & drive;
    WorldModel & worldmodel;
};

This is actually an extremely condensed version. It seems to me however that this is not really object oriented code as much as Clumsily Split-Up Code™. In particular, it seemed almost impossible to make e.g. the Sensors class get data from the Drive class without some fudging around in the Director class (i.e., first perform a function in the Drive class to get the velocity setpoint, and then provide that to the update() method in the Sensors class to do some Kalman filtering).

How does one create a project in c++ with various parts being very dependent on each other, without this becoming a problem? I read an SO answer on interfaces but I'm not sure how to apply that to this problem - is that even the way to go here? Is there a design pattern (not necessarily an object oriented one) that is suitable for projects such as this one?

Upvotes: 2

Views: 2247

Answers (1)

sehe
sehe

Reputation: 392911

No, there's not a design pattern for projects "like this".

Design patterns are not the goal.

So, let me put a few guesses straight:

  • you want light weight code (because otherwise you'd be using Java, right)
  • you want maintainable code (because otherwise, spaghetti would be fine)
  • you want idiomatic code

Here's what I'd do:

  • declare classes in separate headers
  • use forward defines to reduce header coupling
  • move implementations in the corresponding source files
  • keep unwanted implementation dependencies out of the header file. Optionally use the Pimpl Idiom here.

    e.g. if you use library X to implement Y::frobnicate don't include libX.h in your Y.h. Instead, include it in Y.cpp only.

    If you find that you need class member declaration that would require libX.h in the header, use the Pimpl Idiom.

I don't know what else you could want here :)

Maybe, if you need "interfaces" consider using template composition. Policy, strategy, state patterns. E.g. Instead of

    #include <set>

    struct ISensors {
        virtual int get(int id) const = 0;
        virtual int set(int id, int newval) const = 0;
        virtual std::set<int> sensors() const = 0;
    };

    class Drive {
        void update();
        Drive(ISensors &sensors);

      private:
        ISensors &sensors;
    };

You could consider

template <typename Sensors>
class Drive {
    void update();
    Drive(Sensors &sensors);

  private:
    Sensors &sensors;
};

Which leaves you free to implement Sensors in any which way that statically compiles. The "limitation" is that the injection of dependencies needs to be statically defined/typed. The benefit is ultimate flexibility and zero-overhead: e.g. you couldn't have virtual member function templates, but you can use this as a Sensors policy:

struct TestSensors {
    int get(int)      { return 9;  } 
    int set(int, int) { return -9; } 

    template<typename OutputIterator>
    OutputIterator sensors(OutputIterator out) const {
        int available[] = { 7, 8, 13, 21 };
        return std::copy(std::begin(available), std::end(available), out);
    }
};

using TestDrive = Drive<TestSensors>;

Upvotes: 3

Related Questions