Working with class functions that need to be broken down into functions for clarity?

As is usually the case with this situation. 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 should do, but actually it can lead to functions with more than 10,000 lines. That way you break them. The problem is that many of these helper functions should not be called by anything other than the function they serve. This makes the code confusing. For example, chew (food); eat () will be called, but should not be called by the user of this class, and probably should not be called anywhere.

How these cases are treated as a whole. I looked 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) 

They all look like pretty high-level functions, which means that their source code should be quite long. What's good 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 can be quite large.

What should a programmer do in these cases; which is good practice for such situations.

+4
source share
4 answers

For example, chew (food); eat () will be called, but should not be called by the user of this class, and probably should not be called anywhere.

Then either create a member function chew a private , or protected , or a standalone namespace inside the anonymous namespace inside the eat 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 advantages of this approach over private member functions are that the implementation of eat can be changed without changing the header (requiring recompilation of client code). The disadvantage is that a function cannot be called by any function outside its module, therefore it cannot be used by several member functions if they do not share the implementation file and that it cannot access the private parts of the class directly.

The downside to protected member functions is that derived classes cannot directly call chew .

+11
source

The implementation of a single member function may be shared in any way.

A popular option is to use private member functions:

 struct Human { void eat(); private: void chew(...); void eat_spinach(); ... }; 

or use the Pimpl idioms:

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

However, as soon as the complexity of eat increases, you certainly do not want to collect a collection of private methods (either inside the Pimpl class or inside a private section).

So you want to break 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 basic principles:

  • Keep it simple.
  • The answer is one class.
  • Never duplicate code

Edit: A slightly better illustration showing a possible division 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(); } 
+1
source

One possibility is to (possibly private ly) make Human smaller objects, each of which does a smaller part of the work. So you can have a Stomach object. Human::eat(Food food) should delegate this->stomach.digest(food) , returning a DigestedFood object that is processed by the Human::eat(Food food) function.

0
source

The decomposition function is what is studied experimentally, and usually this implies decomposition of the type at the same time. If your functions get too large, different things can be done that are best suited to a particular case, depending on the problem.

  • separate functions to private functions

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

  • decompose the class into different subclasses that have different responsibilities.

In some cases, part of the work naturally falls on its own small subtask, then higher-level functions can be implemented in terms of calls to internal subobjects (usually these are type members).

Since the domain you are trying to model can be interpreted in completely different ways, I'm afraid that you will try to provide a reasonable breakdown, but you can imagine that you had a subobject of mouth in Human that you could use ingest food or drink. In the mouth subobject you can have functions open , chew , swallow ...

0
source

Source: https://habr.com/ru/post/1379340/


All Articles