2012-11-01 8 views
7

Ho uno sfondo C e sono un newb su C++. Ho una domanda di design di base. Ho una classe (lo chiamerò "chef" b/c il problema che ho sembra molto analogo a questo, sia in termini di complessità e problemi) che, in fondo funziona cosìAiuto C++ sul refactoring di una classe monster

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

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

in pseudo-codice, questo viene implementato sulla falsariga di:

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 di chef qui sembra essere una classe mostro, e ha il potenziale di diventare uno. cuoco sembra anche a violare il principio di un'unica responsabilità, così invece dovremmo avere qualcosa di simile:

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

e

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

ecc ...

che sto certamente ancora lottando con il modo di implementarlo in fase di esecuzione in modo tale che, se per esempio il plater (o "chef nella sua veste di plater") decide di andare a casa a metà del servizio di cena, lo chef deve lavorare un nuovo turno.

Questo sembra essere correlato a una domanda più ampia che ho che se la stessa persona invariabilmente fa la preparazione, la cottura e la placcatura in questo esempio, qual è il reale vantaggio pratico di avere questa gerarchia di classi per modellare ciò che un singolo chef lo fa? Immagino che si imbatta nella "paura di aggiungere classi", ma allo stesso tempo, in questo momento o nel prossimo futuro, non penso che mantenere la classe dello chef nella sua interezza sia terribilmente ingombrante. Penso anche che sia in un senso molto reale più facile per un ingenuo lettore del codice vedere i tre diversi metodi nell'oggetto chef e andare avanti.

Capisco che potrebbe minacciare di diventare poco maneggevole quando/se aggiungiamo metodi come "cut_onions()", "cut_carrots()", ecc ..., ognuno con i propri dati, ma sembra che possano essere trattati con avendo facendo la funzione prep(), per esempio, più modulare. Inoltre, sembra che l'SRP portato alla sua logica conclusione creerebbe una classe "onion_cutters" "carrot_cutters" ecc ... e ho ancora difficoltà a vederne il valore, dato che in qualche modo il programma deve assicurarsi che il lo stesso dipendente taglia le cipolle e le carote che aiuta a mantenere la stessa variabile di stato attraverso i metodi (ad esempio, se il dipendente taglia le cipolle tagliate con le dita non è più idoneo a tagliare le carote), mentre nella classe mostro oggetto chef sembra che tutto ciò che si prende cura di

Naturalmente, capisco che questo diventa meno importante per avere un "design orientato agli oggetti" significativo, ma mi sembra che se dobbiamo avere oggetti separati per ciascuno dei compiti dello chef (che sembra innaturale, dato che la stessa persona sta facendo tutte e tre le funzioni), quindi sembra dare priorità alla progettazione del software rispetto al modello concettuale. Sento che un design orientato agli oggetti è utile qui se vogliamo avere, ad esempio, "meat_chef" "sous_chef" "three_star_chef" che sono probabilmente persone diverse. Inoltre, in relazione al problema del runtime è che c'è un sovraccarico di complessità che sembra, sotto la stretta applicazione del principio di responsabilità singola, che deve assicurarsi che i dati sottostanti che costituiscono il dipendente della classe base vengano modificati e che questo cambiamento sia riflessa in fasi temporali successive.

Sono quindi piuttosto tentato di lasciarlo più o meno così com'è. Se qualcuno potesse chiarire perché questa sarebbe una cattiva idea (e se hai suggerimenti su come meglio procedere) sarei molto grato.

+0

A volte la mappatura veri ruoli mondo/responsabilità/attività agli oggetti nel codice semplicemente non funziona. Forse hai bisogno di qualche funzione generica che richiede una persona e un'azione. Quella funzione fa sì che la persona applichi l'azione. Il modulo –

+0

su ogni interfaccia di classe potrebbe darti più indizi? come può fare un placcatore? cosa può fare cook_food? hanno bisogno di ereditare o è solo un'abilità (chiamata di funzione)? – billz

+0

Guarda il metodo di composizione. O forse hai bisogno di uno schema di stato qui? –

risposta

3

Per evitare di abusare delle gerarchie di classe ora e in futuro, dovresti usarlo solo quando è presente una relazione .Come te stesso, "è cook_food un kitchen_worker". Ovviamente non ha senso nella vita reale, e nemmeno nel codice. "cook_food" è un'azione, quindi potrebbe avere senso creare una classe di azioni e sottoclasse quella invece.

Avere una nuova classe solo per aggiungere nuovi metodi come cook() e prep() non è davvero un miglioramento rispetto al problema originale in ogni caso - dal momento che tutto quello che hai fatto è avvolto il metodo all'interno di una classe. Quello che volevi veramente era creare un'astrazione per fare una qualsiasi di queste azioni, quindi torna alla classe d'azione.

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

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

A un cuoco può quindi essere fornito un elenco di azioni da eseguire nell'ordine specificato. Dì ad esempio, una coda.

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

Questo è più comunemente noto come Strategy Pattern. Promuove il principio aperto/chiuso, consentendo di aggiungere nuove azioni senza modificare le classi esistenti.


Un approccio alternativo è possibile utilizzare è un Template Method, in cui si specifica una sequenza di passi astratti, e utilizzare sottoclassi di implementare il comportamento specifico per ciascuno di essi.

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 altro modello strettamente correlati che potrebbe essere adatto per questo è il Builder Pattern

Questi modelli possono anche ridurre del Sequential Coupling anti-modello, come è fin troppo facile dimenticare di chiamare alcuni metodi, o chiamare il numero nel giusto ordine, in particolare se lo fai più volte. Potresti anche considerare di inserire kitchen.opens() e closes() in un metodo di template simile, che non devi preoccuparti del fatto che closes() venga chiamato.


Sulla creazione di singole classi per onion_cutter e carrot_cutter, questo non è davvero la logica conclusione del SRP, ma in realtà una violazione di esso - perché si sta facendo le classi che sono responsabili per il taglio, e tenendo alcune informazioni su ciò che stanno tagliando. Sia il taglio di cipolle e carote può essere astratto in una singola azione di taglio - ed è possibile specificare quale oggetto da tagliare, e aggiungere un reindirizzamento per ogni singola classe se avete bisogno di codice specifico per ogni oggetto.

Un passo sarebbe creare un'astrazione per dire che qualcosa è tagliabile. Il rapporto è per la sottoclasse è candidato, poiché una carota è tagliabile.

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

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

L'azione di taglio può prendere un oggetto tagliabile ed eseguire qualsiasi azione di taglio comune che è applicabile a tutte cuttables, e può anche applicare il comportamento taglio specifico di ciascun oggetto.

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

Grazie per quello. Intendevo seguirlo con una domanda. Quindi, se seguiamo l'approccio del modello di strategia, diciamo che ogni chef ha molti membri di dati (ad esempio, tasting_spoon, salt_shaker, ecc.) Usati sia nella classe cook_food() che plate_dish(). Sembra quindi che lo chef di classe abbia bisogno di cook_food() e cook_food() abbia bisogno di chef di classe. Come evitare la dipendenza circolare tra say cook_food() e chef di classe senza dover dare un sacco di argomenti alla classe cook_food()? Dal punto di vista del design, se le classi sono altamente accoppiate, non c'è un demerito nel separarle? – user1790399

+0

Un modo per evitare la dipendenza circolare consiste nel creare un'interfaccia chef, che può essere consumata dalla classe cook_food, e che l'effettiva classe chef può implementare. Finché l'interfaccia specifica solo i dettagli necessari per il cook_food per conoscere lo chef, non ci sono problemi. (o potresti farlo al contrario, e creare un'interfaccia di cottura che lo chef possa consumare.) Non esiste un solo modo corretto, ma c'è sicuramente il merito di separare ogni attività nella sua stessa classe. –

+0

Mi spiace, cosa significa per una classe essere in grado di consumare un'interfaccia? Significa fare qualcosa del tipo, se andiamo con l'interfaccia sulla classe chef, ad esempio, cook_food (chef-> interface_for_cooking()), con interface_for_cooking() composto da chiamate come get_salt_shaker(), get_tasting_spoon() ecc. .? E per seguire con questo esempio, sarebbe una cattiva idea avere due interfacce separate per le classi cook_food() e plate_dish()? – user1790399

Problemi correlati