2012-11-01 6 views
7

J'ai un arrière-plan C et je suis novice en C++. J'ai une question de conception de base. J'ai une classe (je vais l'appeler « chef » b/c le problème que j'ai semble très analogue à cela, tant en termes de complexité et des problèmes) qui fonctionne essentiellement commeAide C++ sur la refactorisation d'une classe monstre

class chef 
    { 
    public: 
      void prep(); 
      void cook(); 
      void plate(); 

    private: 
      char name; 
      char dish_responsible_for; 
      int shift_working; 
      etc... 
    } 

dans le code pseudo, ce soit mis en oeuvre le long des lignes de:

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(); 
     } 
    } 

la classe de chef ici semble être une classe de monstre, et a le potentiel de devenir un. le chef semble également violer le principe de responsabilité unique, donc nous devrions plutôt avoir quelque chose comme:

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... 
    } 

et

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

etc ...

Je suis certes encore du mal avec la façon le mettre en œuvre au moment de l'exécution de sorte que, si par exemple plater (ou "chef en sa qualité de plater") décide de rentrer à la maison à mi-chemin du service de table, alors le chef doit travailler un nouveau quart. Cela semble être lié à une question plus large: si la même personne fait invariablement la préparation, la cuisson et le placage dans cet exemple, quel est l'avantage pratique réel d'avoir cette hiérarchie de classes pour modéliser ce qu'un seul chef Est-ce que? Je suppose que cela se heurte à la «peur d'ajouter des classes», mais en même temps, en ce moment ou dans un avenir prévisible, je ne pense pas que le maintien de la classe de chef dans son ensemble est terriblement lourd. Je pense aussi qu'il est vraiment plus facile pour un lecteur naïf du code de voir les trois méthodes différentes dans l'objet chef et de passer à autre chose. Je comprends que cela pourrait menacer de devenir lourd quand/si nous ajoutons des méthodes comme "cut_onions()", "cut_carrots()", etc ..., peut-être chacune avec leurs propres données, mais il semble que celles-ci peuvent être distribuées avec en ayant la fonction prep(), disons, plus modulaire. De plus, il semble que le SRP, mené à sa conclusion logique, créerait une classe "onion_cutters" "carrot_cutters" etc ... et j'ai encore du mal à voir la valeur de cela, étant donné que le programme doit s'assurer que le même employé coupe les oignons et les carottes, ce qui aide à garder la même variable d'une méthode à l'autre (p. ex., si l'employé coupe son oignon, il n'est plus autorisé à couper les carottes), alors que tout ce qui est pris en charge. Bien sûr, je comprends que cela devient moins une question de «conception orientée objet», mais il me semble que si nous devons avoir des objets séparés pour chacune des tâches du chef (ce qui semble anormal, étant donné que la même personne fait les trois fonctions), ce qui semble donner la priorité à la conception de logiciels plutôt qu'au modèle conceptuel. Je pense qu'une conception orientée objet est utile ici si nous voulons avoir, disons, "meat_chef" "sous_chef" "three_star_chef" qui sont probablement des personnes différentes. De plus, en ce qui concerne le problème d'exécution, il semble qu'il y ait une surcharge en termes de complexité. Il semble que les données sous-jacentes qui composent l'employé de base soient modifiées et que ce changement soit reflété dans les pas de temps suivants. Je suis donc plutôt tenté de le laisser plus ou moins tel quel. Si quelqu'un pouvait clarifier pourquoi ce serait une mauvaise idée (et si vous avez des suggestions sur la meilleure façon de procéder), je serais très obligé.

+0

cartographie parfois réels rôles mondiaux/responsabilités/tâches à des objets dans le code ne fonctionne tout simplement pas. Peut-être que vous avez besoin d'une fonction générique qui prend une personne et une action. Cette fonction permet à la personne d'appliquer l'action. –

+0

module sur chaque interface de classe peut vous donner plus d'indices? comme ce qu'un plater peut faire? Qu'est-ce que cook_food peut faire? ont-ils besoin d'hériter ou c'est juste une compétence (appel de fonction)? – billz

+0

Regardez la méthode de composition. Ou peut-être que vous avez besoin d'un modèle d'état ici? –

Répondre

3

Pour éviter d'abuser des hiérarchies de classe maintenant et dans le futur, vous ne devriez vraiment l'utiliser que lorsqu'une relation est est présente.Comme vous, "cook_food est un kitchen_worker". Cela n'a évidemment aucun sens dans la vie réelle, et non dans le code non plus. "cook_food" est une action, il peut donc être judicieux de créer une classe d'action, et sous-classe à la place. Avoir une nouvelle classe juste pour ajouter de nouvelles méthodes comme cook() et prep() n'est pas vraiment une amélioration sur le problème original de toute façon - puisque tout ce que vous avez fait est enveloppé la méthode dans une classe. Ce que vous vouliez vraiment, c'était faire une abstraction pour faire l'une de ces actions - donc revenez à la classe d'action. Un chef peut ensuite recevoir une liste d'actions à effectuer dans l'ordre que vous spécifiez. Dites par exemple, une file d'attente.

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

Ceci est plus communément appelé Strategy Pattern. Il favorise le principe ouvert/fermé, en vous permettant d'ajouter de nouvelles actions sans modifier vos classes existantes.


Une autre approche que vous pouvez utiliser est un Template Method, où vous spécifiez une séquence d'étapes abstraites, et d'utiliser les sous-classes pour mettre en œuvre le comportement spécifique pour chacun.

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() { ... } 
} 

Un autre modèle étroitement lié qui pourrait convenir à c'est le Builder Pattern

Ces modèles peuvent également réduire des anti-modèle Sequential Coupling, car il est trop facile d'oublier d'appeler des méthodes, ou appelez dans le bon ordre, surtout si vous le faites plusieurs fois. Vous pourriez également envisager de placer vos kitchen.opens() et closes() dans une méthode de modèle similaire, que vous n'avez pas besoin de vous inquiéter de l'appel de clos().


sur la création de classes individuelles pour onion_cutter et carrot_cutter, ce n'est pas vraiment la conclusion logique de la SRP, mais en fait une violation de celui-ci - parce que vous faites des classes qui sont responsables de la coupe, et la tenue quelques informations sur ce qu'ils coupent. La coupe des oignons et des carottes peut être réduite en une seule action de coupe - et vous pouvez spécifier quel objet couper, et ajouter une redirection à chaque classe si vous avez besoin d'un code spécifique pour chaque objet. Une étape serait de créer une abstraction pour dire que quelque chose est découpable. La est la relation pour le sous-classement est candidat, car une carotte est cuttable.

class cuttable { 
    public: 
     virtual void cut()=0; 
} 

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

L'action de coupe peut prendre un objet découpable et effectuer toute action de coupe commun qui est applicable à tous les Découpables, et peut également appliquer le comportement de coupe spécifique de chaque objet.

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 
     } 
} 

+0

Merci pour cela. Je voulais faire un suivi avec une question. Donc, si nous suivons l'approche du modèle de stratégie, disons que chaque chef a de nombreux membres de données (par exemple, tasting_spoon, salt_shaker, etc ...) utilisés à la fois dans la classe cook_food() et plate_dish(). Il semble que le chef de classe a besoin de cook_food() et cook_food() a besoin de chef de classe. Comment éviter la dépendance circulaire entre say cook_food() et le chef de classe sans avoir à donner une tonne d'arguments à la classe cook_food()? Du point de vue de la conception, si les classes sont fortement couplées, n'y a-t-il pas un démérite à les séparer? – user1790399

+0

Une façon d'éviter la dépendance circulaire serait de créer une interface de chef, que la classe cook_food peut consommer, et la classe chef réelle peut implémenter. Tant que l'interface ne spécifie que les détails nécessaires à cook_food pour connaître le chef, il n'y a pas de problème. (ou vous pourriez le faire dans l'autre sens, et faire une interface de cuisine pour le chef à consommer.) Il n'y a pas de bonne façon, mais il y a certainement du mérite à séparer chaque tâche dans sa propre classe. –

+0

Désolé, qu'est-ce que cela signifie pour une classe de pouvoir consommer une interface? Cela signifie-t-il faire quelque chose comme, si l'on va avec l'interface sur la classe chef, par exemple, cook_food (chef-> interface_for_cooking()), avec interface_for_cooking() composé d'appels comme get_salt_shaker(), get_tasting_spoon() etc. ? Et à suivre avec cet exemple, serait-ce une mauvaise idée d'avoir deux interfaces séparées pour les classes cook_food() et plate_dish()? – user1790399