jmasterx
jmasterx

Reputation: 54113

Dealing with functions in a class that should be broken down into functions for clarity?

How is this situation usually dealt with. For example, an object may need to do very specific things:

class Human
{
   public:
   void eat(Food food);
   void drink(Liquid liquid);
   String talkTo(Human human);
}

Say that this is what this class is supposed to do, but to actually do these might result in functions that are well over 10,000 lines. So you would break them down. The problem is, many of those helper functions should not be called by anything other than the function they are serving. This makes the code confusing in a way. For example, chew(Food food); would be called by eat() but should not be called by a user of the class and probably should not be called anywhere else.

How are these cases dealt with generally. I was looking at some classes from a real video game that looked like this:

class CHeli (7 variables, 19 functions)
Variables list

    CatalinaHasBeenShotDown
    CatalinaHeliOn
    NumScriptHelis
    NumRandomHelis
    TestForNewRandomHelisTimer
    ScriptHeliOn
    pHelis

Functions list

    FindPointerToCatalinasHeli (void)
    GenerateHeli (b)
    CatalinaTakeOff (void)
    ActivateHeli (b)
    MakeCatalinaHeliFlyAway (void)
    HasCatalinaBeenShotDown (void)
    InitHelis (void)
    UpdateHelis (void)
    TestRocketCollision (P7CVector)
    TestBulletCollision (P7CVectorP7CVectorP7CVector)
    SpecialHeliPreRender (void)
    SpawnFlyingComponent (i)
    StartCatalinaFlyBy (void)
    RemoveCatalinaHeli (void)
    Render (void)
    SetModelIndex (Ui)
    PreRenderAlways (void)
    ProcessControl (void)
    PreRender (void)

All of these look like fairly high level functions, which mean their source code must be pretty lengthy. What is good about this is that at a glance it is very clear what this class can do and the class looks easy to use. However, the code for these functions might be quite large.

What should a programmer do in these cases; what is proper practice for these types of situations.

Upvotes: 4

Views: 233

Answers (4)

Fred Foo
Fred Foo

Reputation: 363547

For example, chew(Food food); would be called by eat() but should not be called by a user of the class and probably should not be called anywhere else.

Then either make chew a private or protected member function, or a freestanding function in an anonymous namespace inside the eat implementation module:

// eat.cc

// details of digestion
namespace {
    void chew(Human &subject, Food &food)
    {
        while (!food.mushy())
            subject.move_jaws();
    }
}

void Human::eat(Food &food)
{
    chew(*this, food);
    swallow(*this, food);
}

The benefits of this approach compared to private member functions is that the implementation of eat can be changed without the header changing (requiring recompilation of client code). The drawback is that the function cannot be called by any function outside of its module, so it can't be shared by multiple member functions unless they share an implementation file, and that it can't access private parts of the class directly.

The drawback compared to protected member functions is that derived classes can't call chew directly.

Upvotes: 11

Alexandre C.
Alexandre C.

Reputation: 56956

The implementation of one member function is allowed to be split in whatever way you want.

A popular option is to use private member functions:

struct Human
{
    void eat();

private:
    void chew(...);
    void eat_spinach();
    ...
};

or to use the Pimpl idiom:

struct Human
{
    void eat();
private:
    struct impl;
    std::unique_ptr<impl> p_impl;
};

struct Human::impl { ... };

However, as soon as the complexity of eat goes up, you surely don't want a collection of private methods accumulating (be it inside a Pimpl class or inside a private section).

So you want to break down the behavior. You can use classes:

struct SpinachEater
{
    void eat_spinach();
private:
    // Helpers for eating spinach
};

...

void Human::eat(Aliment* e)
{
    if (e->isSpinach()) // Use your favorite dispatch method here
                        // Factories, or some sort of polymorphism
                        // are possible ideas.
    {
        SpinachEater eater;
        eater.eat_spinach();
    }

    ...
}

with the basic principles:

  • Keep it simple
  • One class one responsibility
  • Never duplicate code

Edit: A slightly better illustration, showing a possible split into classes:

struct Aliment;

struct Human
{
    void eat(Aliment* e);

private:
    void process(Aliment* e); 
    void chew();
    void swallow();
    void throw_up();
};

// Everything below is in an implementation file
// As the code grows, it can of course be split into several
// implementation files.
struct AlimentProcessor
{
    virtual ~AlimentProcessor() {}
    virtual process() {}
};

struct VegetableProcessor : AlimentProcessor
{
private:
    virtual process() { std::cout << "Eeek\n"; }
};

struct MeatProcessor
{
private:
    virtual process() { std::cout << "Hmmm\n"; }
};

// Use your favorite dispatch method here.
// There are many ways to escape the use of dynamic_cast,
// especially if the number of aliments is expected to grow.
std::unique_ptr<AlimentProcessor> Factory(Aliment* e)
{
    typedef std::unique_ptr<AlimentProcessor> Handle;

    if (dynamic_cast<Vegetable*>(e)) 
        return Handle(new VegetableProcessor);
    else if (dynamic_cast<Meat*>(e))
        return Handle(new MeatProcessor); 
    else
        return Handle(new AlimentProcessor);
};

void Human::eat(Aliment* e)
{
    this->process(e);
    this->chew();

    if (e->isGood()) this->swallow();
    else this->throw_up();
}

void Human::process(Aliment* e)
{
    Factory(e)->process();
}

Upvotes: 1

Function decomposition is something that is learnt from experience, and it usually implies type decomposition at the same time. If your functions become too large there are different things that can be done, which is best for a particular case depends on the problem at hand.

  • separate functionality into private functions

This makes more sense when the functions have to access quite a bit of state from the object, and if they can be used as building blocks for one or more of the public functions

  • decompose the class into different subclasses that have different responsibilities

In some cases a part of the work falls naturally into its own little subproblem, then the higher level functions can be implemented in terms of calls to the internal subobjects (usually members of the type).

Because the domain that you are trying to model can be interpreted in quite a number of different ways I fear trying to provide a sensible breakdown, but you could imagine that you had a mouth subobject in Human that you could use to ingest food or drink. Inside the mouth subobject you could have functions open, chew, swallow...

Upvotes: 0

Raedwald
Raedwald

Reputation: 48644

One possibility is to (perhaps privately) compose the Human of smaller objects that each do a smaller part of the work. So, you might have a Stomach object. Human::eat(Food food) would delegate to this->stomach.digest(food), returning a DigestedFood object, which the Human::eat(Food food) function processed further.

Upvotes: 0

Related Questions