2012-11-01 3 views
7

Ich habe einen C-Hintergrund und bin ein Newb auf C++. Ich habe eine grundlegende Designfrage. Ich habe eine Klasse (ich nenne es werde „Chef“ b/c das Problem, das ich scheint sehr ähnlich zu dieser, und zwar sowohl im Hinblick auf die Komplexität und Ausgaben), die im Grunde wie dieseC++ Hilfe zum Refactoring einer Monsterklasse

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

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

in Pseudo-Code arbeitet, Dies wird in folgende Richtung umgesetzt:

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

Die Klasse der Küchenchefs scheint hier eine Monsterklasse zu sein und hat das Potenzial, eins zu werden. Koch scheint auch die einzige Verantwortung Prinzip zu verletzen, so stattdessen sollten wir so etwas wie:

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

und

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

etc ...

Ich bin immer noch zugegebenermaßen kämpfen mit, wie man Implementieren Sie es zur Laufzeit, so dass, wenn zum Beispiel Plater (oder "Koch in seiner Eigenschaft als Plater") beschließt, mitten beim Abendessen Service nach Hause gehen, dann muss der Koch eine neue Schicht arbeiten.

Dies scheint mit einer breiteren Frage verbunden zu sein Ich habe, dass, wenn die gleiche Person unweigerlich das Vorbereiten, Kochen und Galvanisieren in diesem Beispiel macht, was ist der wirkliche praktische Vorteil dieser Hierarchie von Klassen zu modellieren, was ein einzelner Koch tut das? Ich schätze, das läuft auf die "Angst vor dem Hinzufügen von Klassen" hinaus, aber zur gleichen Zeit, im Moment oder auf absehbare Zeit, glaube ich nicht, dass die Aufrechterhaltung der Kochklasse in ihrer Gesamtheit furchtbar mühsam ist. Ich denke auch, dass es für einen naiven Leser des Codes einfacher ist, die drei verschiedenen Methoden im Kochobjekt zu sehen und weiterzumachen.

Ich verstehe, es könnte drohen unhandlich zu werden, wenn/wenn wir Methoden wie "cut_onions()", "cut_carrots()", etc ..., vielleicht jeder mit ihren eigenen Daten hinzufügen, aber es scheint, dass diese behandelt werden können mit der Vorbereitung der prep() -Funktion, sagen wir, modularer. Außerdem scheint es, dass die SRP, die zu ihrer logischen Schlussfolgerung geführt wird, eine Klasse "onion_cutters" "carrot_cutters" usw. kreieren würde ... und es fällt mir immer noch schwer, den Wert zu sehen, da das Programm irgendwie dafür sorgen muss Derselbe Angestellte schneidet die Zwiebeln und die Karotten, was hilft, den Zustand variabel über die Methoden zu halten (zB wenn der Angestellte seinen Finger schneidet und Zwiebeln schneidet, ist er nicht länger zum Schneiden von Karotten berechtigt), während dies in der Klasse der Monsterobjekte der Fall ist all das wird erledigt.

Natürlich verstehe ich, dass es dann weniger darum geht, ein sinnvolles "objektorientiertes Design" zu haben, aber es scheint mir, dass wir für jede Aufgabe des Küchenchefs getrennte Objekte haben müssen (was unnatürlich erscheint) die gleiche Person macht alle drei Funktionen), dann scheint das Software-Design Vorrang vor dem konzeptionellen Modell zu haben. Ich finde, dass ein objektorientiertes Design hier hilfreich ist, wenn wir sagen wollen "meat_chef" "sous_chef" "three_star_chef", die wahrscheinlich unterschiedliche Leute sind. Im Zusammenhang mit dem Laufzeitproblem besteht außerdem ein Overhead in der Komplexität, der unter der strikten Anwendung des Prinzips der einheitlichen Verantwortlichkeit zu gewährleisten scheint, dass die zugrunde liegenden Daten, die den Mitarbeiter der Basisklasse ausmachen, geändert werden und dass diese Änderung erfolgt in nachfolgenden Zeitschritten wiedergegeben.

Ich bin daher eher versucht, es mehr oder weniger zu lassen, wie es ist. Wenn jemand erklären könnte, warum das eine schlechte Idee wäre (und wenn Sie Vorschläge haben, wie man am besten vorgeht), wäre ich Ihnen am meisten verpflichtet.

+0

Manchmal reale Welt Rollen/Verantwortlichkeit Kartierungs-/Aufgaben in Code zu Objekten funktioniert einfach nicht. Vielleicht brauchen Sie eine generische Funktion, die eine Person und eine Aktion braucht. Diese Funktion ruft die Person zum Anwenden der Aktion auf. –

+0

Modul auf jeder Klassenschnittstelle kann Ihnen mehr Hinweise geben? Wie kann ein Plater machen? was kann cook_food machen? Müssen sie vererben oder ist es nur eine Fähigkeit (Funktionsaufruf)? – billz

+0

Schauen Sie sich die Kompositionsmethode an. Oder brauchen Sie hier ein Zustandsmuster? –

Antwort

3

Um zu vermeiden, Klassenheerarchien jetzt und in Zukunft zu missbrauchen, sollten Sie es wirklich nur verwenden, wenn eine Beziehung vorhanden ist.Wie du selbst "ist cook_food ein Küchenarbeiter". Es macht offensichtlich keinen Sinn im wirklichen Leben, und auch nicht im Code. "cook_food" ist eine Aktion, daher kann es sinnvoll sein, eine Aktionsklasse zu erstellen und diese stattdessen abzuleiten.

Eine neue Klasse hinzuzufügen, nur um neue Methoden wie cook() und prep() hinzuzufügen, ist sowieso nicht wirklich eine Verbesserung des ursprünglichen Problems - da alles, was Sie getan haben, die Methode innerhalb einer Klasse ist. Was Sie wirklich wollten, war eine Abstraktion zu machen, um eine dieser Aktionen auszuführen - also zurück zur Aktionsklasse.

class action { 
    public: 
     virtual void perform_action()=0; 
} 

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

Ein Koch kann dann eine Liste von Aktionen erhalten, die in der von Ihnen angegebenen Reihenfolge ausgeführt werden. Sagen wir zum Beispiel eine Warteschlange.

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

Dies ist häufiger bekannt als Strategy Pattern. Es fördert das Prinzip "offen/geschlossen", indem es Ihnen erlaubt, neue Aktionen hinzuzufügen, ohne Ihre bestehenden Klassen zu ändern.


Ein alternativer Ansatz, den Sie verwenden können, ist ein Template Method, wo Sie eine Folge von abstrakten Schritte angeben, und Subklassen verwenden das spezifische Verhalten für jeden zu implementieren.

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

Eine weitere eng verwandte Muster, das für diese geeignet sein könnte, ist die Builder Pattern

Diese Muster auch der Sequential Coupling anti-Muster reduzieren, wie es nur allzu leicht zu vergessen, einige Methoden zu nennen, oder Anruf sie in der richtigen Reihenfolge, besonders wenn Sie es mehrmals tun. Sie könnten auch in Betracht ziehen, Ihre kitchen.opens() und close() in eine ähnliche Template-Methode einzufügen, als dass Sie sich nicht darum kümmern müssen, dass clos() aufgerufen wird.


auf der Schaffung von einzelnen Klassen für onion_cutter und carrot_cutter, das ist nicht wirklich die logische Schlussfolgerung des SRP, aber in Wirklichkeit eine Verletzung davon - weil du Klassen machst, die zum Schneiden verantwortlich sind, und hält einige Informationen darüber, was sie schneiden. Sowohl das Schneiden von Zwiebeln als auch von Möhren können zu einem einzigen Schneidevorgang zusammengefasst werden - und Sie können angeben, welches Objekt geschnitten werden soll, und eine Umleitung zu jeder einzelnen Klasse hinzufügen, wenn Sie für jedes Objekt einen spezifischen Code benötigen.

Ein Schritt wäre, eine Abstraktion zu erstellen, um zu sagen, dass etwas schneidbar ist. Die ist Beziehung für Unterklassen ist Kandidat, da eine Karotte ist cutable.

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

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

Die Schneidwirkung kann eine schneidbare Objekt nehmen und führen jede Aktion gemeinsamen Schneid, die für alle cuttables anwendbar ist, und das Verhalten jedes Objekt spezifische Schnitt auch anwenden können.

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

Danke dafür. Ich wollte ihm eine Frage stellen. Wenn wir also dem Strategie-Muster-Ansatz folgen, können wir sagen, dass jeder Koch viele Datenmitglieder hat (z. B. probierlöffel, Salz-Shaker, usw.), die sowohl in der Klasse cook_food() als auch in der plate_dish() -Klasse verwendet werden. Es scheint dann Klasse Chef braucht cook_food() und cook_food() braucht Klasse Koch. Wie vermeidet man eine zirkuläre Abhängigkeit zwischen say cook_food() und dem Klassenkoch, ohne der Klasse cook_food() viele Argumente zu geben? Wenn die Klassen hochgradig gekoppelt sind, gibt es dann nicht einen Mangel an Trennung? – user1790399

+0

Eine Möglichkeit, die zirkuläre Abhängigkeit zu vermeiden, wäre die Erstellung einer Chefschnittstelle, die die Klasse cook_food konsumieren kann und die die eigentliche Chefklasse implementieren kann. Solange die Schnittstelle nur die Details spezifiziert, die das Kochwissen vom Koch benötigt, ist das kein Problem. (Oder Sie könnten es andersherum machen, und eine Kochschnittstelle für den Koch zubereiten.) Es gibt keinen einzigen richtigen Weg, aber es ist sicherlich ein Verdienst, jede Aufgabe in ihre eigene Klasse aufzuteilen. –

+0

Entschuldigung, was bedeutet es für eine Klasse, eine Schnittstelle konsumieren zu können? Heißt das etwa, wenn wir mit der Schnittstelle der Chefklasse gehen, zum Beispiel cook_food (chef-> interface_for_cooking()), mit interface_for_cooking(), bestehend aus Aufrufen wie get_salt_shaker(), get_tasting_spoon() usw. .? Und mit diesem Beispiel zu folgen, wäre es eine schlechte Idee, zwei separate Schnittstellen für die Klassen cook_food() und plate_dish() zu haben? – user1790399