C ++ Monster class refactoring help

I have background C and am new to C ++. I have a basic design question. I have a class (I will call it a “chef” b / c a problem with which I seem to be very similar to it, both in terms of complexity and problems), which basically works like this

class chef { public: void prep(); void cook(); void plate(); private: char name; char dish_responsible_for; int shift_working; etc... } 

in pseudocode, this is implemented line by line:

  int main{ chef my_chef; kitchen_class kitchen; for (day=0; day < 365; day++) { kitchen.opens(); .... my_chef.prep(); my_chef.cook(); my_chef.plate(); .... kitchen.closes(); } } 

The chef class seems to be a monster class and has the potential to become one of them. the chef also seems to violate the principle of shared responsibility, so instead we should have something like:

  class employee { protected: char name; int shift_working; } class kitchen_worker : employee { protected: dish_responsible_for; } class cook_food : kitchen_worker { public: void cook(); etc... } class prep_food : kitchen_worker { public: void prep(); etc... } 

and

  class plater : kitchen_worker { public: void plate(); } 

etc.

I admittedly still struggle with how to implement it at runtime, so that if, for example, a plater (or "a chef in his capacity as a plater") decides to return home halfway through dinner, then the chef a new shift should work.

This seems to be related to the broader question that I have: if the same person always does the cooking, cooking and coating in this example, what is the real practical advantage of having this class hierarchy simulate what does one chef do? I suppose this attacks the “fear of adding classes,” but at the same time, right now or in the foreseeable future, I don’t think supporting the chef class in its entirety is terribly cumbersome. I also believe that in the most understandable sense for a naive code reader, you can see three different methods in the chef's object and move on.

I understand that this can threaten to become cumbersome when / if we add methods such as "cut_onions ()", "cut_carrots ()", etc., possibly each with their own data, but it seems to be with them can be done by making the prep () function, say, more modular. Moreover, it seems that the SRP, taken to its logical conclusion, will create the class "onion_cutters" "carrot_cutters", etc .... and it’s still hard for me to see the value of this, given that somehow the program has to make sure that the same worker cuts onions and carrots, which helps to maintain a state variable in the same way in different methods (for example, if a worker cuts onion slices of his fingers, he no longer has the right to cut carrots), while in the monster chef class it seems that all that cares.

Of course, I understand that then it becomes less about the existence of a meaningful "object-oriented design", but it seems to me that if we need to have separate objects for each of the chef's tasks (which seems unnatural, given that the same person performs all three functions), this, apparently, prioritizes software development over the conceptual model. I feel that object oriented design is useful here if we want to have, say, "meat_chef" "sous_chef" "three_star_chef", which are probably different people. Moreover, the problem associated with the run-time problem is that there are apparently complex overhead costs, with strict application of the principle of single responsibility, which must be changed, and the basic data that make up the employee of the base class will be changed and that it change is reflected in subsequent steps of time.

Therefore, I am tempted to leave it more or less as it is. If someone can clarify why this would be a bad idea (and if you have any suggestions on how best to continue), I would be most obliged.

+7
source share
1 answer

To avoid abusing class hierarchies now and in the future, you really should only use it if you have a relationship . Like you, cook_food is a kitchen_worker. Obviously, this does not make sense in real life, nor in code. "cook_food" is an action, so it makes sense to create an action class and a subclass instead.

Having a new class to add new methods, such as cook() and prep() , is not really an improvement on the original problem, since everything you did completes the method inside the class. What you really wanted was to make an abstraction to perform any of these actions - so as to return to the class of actions.

 class action { public: virtual void perform_action()=0; } class cook_food : public action { public: virtual void perform_action() { //do cooking; } } 

Then the chef can get a list of actions to perform in the order you specify. Say, for example, a queue.

 class chef { ... perform_actions(queue<action>& actions) { for (action &a : actions) { a.perform_action(); } } ... } 

This is more commonly known as a strategy template . It promotes an open / closed principle, allowing you to add new actions without changing existing classes.


An alternative approach that you can use is the Template Method , where you specify a sequence of abstract steps and use subclasses to implement specific behavior for each of them.

 class dish_maker { protected: virtual void prep() = 0; virtual void cook() = 0; virtual void plate() = 0; public: void make_dish() { prep(); cook(); plate(); } } class onion_soup_dish_maker : public dish_maker { protected: virtual void prep() { ... } virtual void cook() { ... } virtual void plate() { ... } } 

Another close template that might be suitable for this is Figure Builder.

These patterns can also reduce the Serial Link , as it is too easy to forget to call some methods or call them in the correct order, especially if you do this several times. You might also consider putting your .opens () kitchen and closing () in a similar template method, so you don’t have to worry about calling functions closing ().


When creating separate classes for onion_cutter and carrot_cutter, this is not really the logical termination of SRP, but actually its violation - because you make classes that are responsible for cutting and contain some information about what they cut. Both cutting onions and carrots can be abstracted into a single cutting action - and you can specify which object to cut and add a redirection to each individual class if you need a specific code for each object.

One step would be to create an abstraction in order to say that something is cut off. Ratio for the subclass is a candidate because carrots are cut.

 class cuttable { public: virtual void cut()=0; } class carrot : public cuttable { public: virtual void cut() { //specific code for cutting a carrot; } } 

The cutting effect can take a cut object and perform any general cutting action applicable to all tables to be cut, and can also apply specific cut behavior for each object.

 class cutting_action : public action { private: cuttable* object; public: cutting_action(cuttable* obj) : object(obj) { } virtual void perform_action() { //common cutting code object->cut(); //specific cutting code } } 

+3
source

All Articles