HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Classes for sentence-like readability

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
readabilitylikesentenceforclasses

Problem

If we have a set of classes or structs such as:

struct Point
{
    float x;
    float y;
};

struct Square
{
    Point topLeft;
    float width;
};

struct Circle
{
    float radius;
    Point middle;
};


If we need to add some functionality to to these, does something like below make sense?

inline Point middleOf(const Square &square)
{
    float halfWidth(square.width/2.0f);
    return Point{
        square.topLeft.x + halfWidth,
        square.topLeft.y + halfWidth};
}

inline Point middleOf(const Circle &circle)
{
    return circle.middle;
}

class MoveSquare
{
public:
    MoveSquare(Square &squareToMove)
    :
    squareToMove(squareToMove)
    {}

    void to(const Point &target)
    {
        squareToMove.topLeft = offsetHalfWidth(target);
    }

private:    
    Point offsetHalfWidth(Point p)
    {
        const float halfWidth(squareToMove.width / 2.0f);
        return Point{
            p.x - halfWidth,
            p.y - halfWidth};
    }

private:
    Square &squareToMove;
};

class MoveCircle
{
public:
    MoveCircle(Circle &circle)
    :
    circleToMove(circle)
    {}

    void to(Point position)
    {
        circleToMove.middle = position;
    }

private:
    Circle &circleToMove;
};

inline MoveSquare move(Square &square)
{
    return MoveSquare{square};
}

inline MoveCircle move(Circle &circle)
{
    return MoveCircle{circle};
}


The main idea is being able to have code that reads like a sentence. Such as:

int main() {

    Square aSquare;
    aSquare.topLeft = Point{1,1};
    aSquare.width = 3;

    Circle aCircle;
    aCircle.middle = Point{1,1};
    aCircle.radius = 2;

    move(aSquare).to(Point{1,1});
    move(aSquare).to(middleOf(aCircle));

    move(aCircle).to(middleOf(aSquare));

    return 0;
}

Solution

Firstly, a negative remark. The power of C++ means that it is not uncommon for new C++ developers to try and make C++ look like a language they feel more familiar with - sometimes English, sometimes a programming language they already know. This is almost always a bad thing - you need to speak like a native.

However, whilst C++ used for mainstream development does have generally understood idioms, one of its capabilities and original design aims is to try to support different styles of programming.

As well as trying to create an application syntax which is more English like, you are externalizing functionality into proxy objects rather than using the OOP paradigm of member functions. IMHO, this is perfectly legitimate as an experiment.

What you'll likely find is that as you pursue this you gave to make various compromises that finally result in you deciding that maybe this is not he way to go. Or maybe not. See what you find.

If I were to go down this path, I'd probably play with using templates to try and get some generic behaviour, given that the OOP approach us not being used. How exactly this goes will depend on whatever other functionality is needed.

For example:

template
class Move
{
public:
    Move(T& thing)
        : thing_(thing)
    {}
    virtual void to(const Point& position) = 0;
    T& thing() { return thing_; }
private:
    T& thing_;
};

class MoveSquare : public Move
{
public:
    MoveSquare(Square &squareToMove)
        : Move(squareToMove)
    {}

    virtual void to(const Point &target)
    {
        thing().topLeft = offsetHalfWidth(target);
    }

private:
    Point offsetHalfWidth(const Point& p) 
    {
        const float halfWidth(thing().width / 2.0f);
        return Point{ p.x - halfWidth, p.y - halfWidth };
    }
};

class MoveCircle : public  Move
{
public:
    MoveCircle(Circle &circle)
        : Move(circle)
    {}

    virtual void to(const Point& position)
    {
        thing().middle = position;
    }

};

Code Snippets

template<class T>
class Move
{
public:
    Move(T& thing)
        : thing_(thing)
    {}
    virtual void to(const Point& position) = 0;
    T& thing() { return thing_; }
private:
    T& thing_;
};

class MoveSquare : public Move<Square>
{
public:
    MoveSquare(Square &squareToMove)
        : Move<Square>(squareToMove)
    {}

    virtual void to(const Point &target)
    {
        thing().topLeft = offsetHalfWidth(target);
    }

private:
    Point offsetHalfWidth(const Point& p) 
    {
        const float halfWidth(thing().width / 2.0f);
        return Point{ p.x - halfWidth, p.y - halfWidth };
    }
};

class MoveCircle : public  Move<Circle>
{
public:
    MoveCircle(Circle &circle)
        : Move<Circle>(circle)
    {}

    virtual void to(const Point& position)
    {
        thing().middle = position;
    }

};

Context

StackExchange Code Review Q#56606, answer score: 7

Revisions (0)

No revisions yet.