2012-11-01 10 views
7

Mam tło C i jestem newb w C++. Mam podstawowe pytanie projektowe. Mam klasa (nazwijmy go „kucharz” b/c mam problem wydaje się bardzo analogiczne do tego, zarówno pod względem złożoności i zagadnień), które w zasadzie działa takC++ Pomoc w refaktoryzacji klasy potworów

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

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

w pseudo kod, to zostało zaimplementowane zgodnie z:

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

Klasa kucharska wydaje się być klasą potworów i ma potencjał, aby stać się nią. kucharz również wydaje się naruszać zasada jednej odpowiedzialności, więc zamiast tego powinniśmy mieć coś takiego:

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

i

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

etc ...

Ja wprawdzie nadal zmaga się z jak wdrożyć go w czasie pracy, tak aby, na przykład, jeśli plater (lub "kucharz w roli plateru") zdecyduje się wrócić do domu w połowie obiadu, wtedy szef kuchni musi przeprowadzić nową zmianę.

Wydaje się, że jest to związane z szerszym pytaniem, które mam, jeśli ta sama osoba niezmiennie robi przygotowanie, gotowanie i platerowanie w tym przykładzie, jaka jest prawdziwa praktyczna korzyść z posiadania tej hierarchii klas do modelowania tego, co pojedynczy kucharz robi? Wydaje mi się, że ma to związek z "obawą przed dodaniem klasy", ale jednocześnie, teraz lub w przewidywalnej przyszłości, nie wydaje mi się, żeby utrzymywanie klasy kucharza w całości było strasznie uciążliwe. Wydaje mi się również, że dla naiwnego czytelnika kodu łatwiej jest znaleźć trzy różne metody w obiekcie szefa kuchni i przejść dalej.

Rozumiem, że może zagrozić, że stanie się nieporęczny, gdy/jeśli dodamy metody takie jak "cut_onions()", "cut_carrots()", itp. ..., być może każdy z własnymi danymi, ale wygląda na to, że można je podać przy użyciu funkcji prep(), powiedzmy, bardziej modułowej. Co więcej, wydaje się, że SRP podjęte zgodnie z logicznym wnioskiem utworzyłoby klasę "onion_cutters" "carrot_cutters" itp. ... i wciąż mam problem z dostrzeżeniem jej wartości, ponieważ w jakiś sposób program musi upewnić się, że ten sam pracownik tnie cebulę i marchewkę, co pomaga w utrzymaniu tej samej wartości w różnych metodach (np. jeśli pracownik tnie cebulę do cięcia palcem, nie jest już uprawniony do krojenia marchwi), podczas gdy w klasie potworów szef kuchni wygląda na to, że wszystko to zostaje załatwione.

Oczywiście, rozumiem, że w ten sposób staje się mniejszy o sensownym "obiektowym projekcie", ale wydaje mi się, że jeśli musimy mieć oddzielne obiekty dla każdego zadania szefa kuchni (co wydaje się nienaturalne, biorąc pod uwagę, że ta sama osoba wykonuje wszystkie trzy funkcje), która wydaje się priorytetować projektowanie oprogramowania w stosunku do modelu koncepcyjnego. Uważam, że projektowanie zorientowane obiektowo jest pomocne, jeśli chcemy mieć, powiedzmy, "meat_chef" "sous_chef" "three_star_chef", które są prawdopodobnie różnymi osobami. Co więcej, związany z problemem środowiska wykonawczego jest to, że wydaje się, że narzut na złożoność, pod ścisłym zastosowaniem zasady pojedynczej odpowiedzialności, musi zapewnić, że podstawowe dane, które składają się pracownikowi klasy podstawowej, zostaną zmienione i że ta zmiana jest odzwierciedlone w kolejnych etapach czasowych.

Jestem więc raczej kuszony, aby opuścić go mniej więcej tak jak jest. Gdyby ktoś mógł wyjaśnić, dlaczego to byłby zły pomysł (i jeśli masz sugestie, jak najlepiej postępować), byłbym najbardziej zobowiązany.

+0

Czasami odwzorowanie rzeczywistych ról World/obowiązki/zadania do obiektów w kodzie po prostu nie działa. Może potrzebujesz jakiejś ogólnej funkcji, która wymaga osoby i działania. Ta funkcja umożliwia osobie wykonanie akcji. –

+0

moduł na każdym interfejsie klasy może dać więcej wskazówek? jak to może zrobić plater? co może zrobić cook_food? czy muszą odziedziczyć, czy jest to tylko umiejętność (wywołanie funkcji)? – billz

+0

Spójrz na metodę kompozycji. A może potrzebujesz tutaj wzoru stanu? –

Odpowiedz

3

Aby uniknąć nadużywania klasowych spadkobierców teraz iw przyszłości, powinieneś używać go tylko wtedy, gdy istnieje związek między a.Podobnie jak Ty, "jest cook_food a kitchen_worker". To oczywiście nie ma sensu w prawdziwym życiu, a także nie ma kodu. "cook_food" jest akcją, więc może być sens tworzenia klasy akcji i jej podklasy.

Posiadanie nowej klasy tylko w celu dodania nowych metod, takich jak cook() i prep(), tak naprawdę nie jest tak naprawdę poprawą w stosunku do pierwotnego problemu - ponieważ wszystko, co zrobiliście, jest opakowane w metodę wewnątrz klasy. Naprawdę chciałeś zrobić abstrakcję, by wykonać dowolne z tych działań - wracając do klasy działania.

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

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

Szef kuchni może wtedy otrzymać listę czynności do wykonania w określonej przez ciebie kolejności. Powiedz na przykład kolejkę. Jest to bardziej znany jako Strategy Pattern. Promuje zasadę otwartą/zamkniętą, umożliwiając dodawanie nowych działań bez modyfikowania istniejących klas.


Alternatywnym podejściem można użyć to Template Method, gdzie można określić sekwencję kroków abstrakcyjnych i używać podklasy w celu realizacji określonego zachowania dla każdego z nich.

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

Innym ściśle związane wzór, który mógłby być odpowiedni do tego jest Builder Pattern

Te wzory mogą również zmniejszyć o Sequential Coupling anty-wzór, jak to wszystko zbyt łatwo zapomnieć zadzwonić kilka metod, lub zadzwoń je w odpowiedniej kolejności, szczególnie jeśli robisz to wiele razy. Możesz również rozważyć umieszczenie metody kitchen.opens() i zamknięcie() w podobnej metodzie szablonowej, niż nie trzeba się martwić o wywołanie close().


na tworzeniu indywidualnych zajęć dla onion_cutter i carrot_cutter, to naprawdę nie jest logiczny wniosek z SRP, ale w rzeczywistości naruszenie nim - bo robisz klas, które są odpowiedzialne za cięcie i trzymając niektóre informacje o tym, co wycinają. Zarówno cebula, jak i marchew mogą zostać wyodrębnione w jedno działanie cięcia - możesz określić, który obiekt chcesz wyciąć, i dodać przekierowanie do każdej klasy, jeśli potrzebujesz konkretnego kodu dla każdego obiektu.

Jednym krokiem byłoby stworzenie abstrakcji do powiedzenia coś nadaje się do krojenia. jest zależność dla podklasy jest kandydatem, ponieważ marchew jest nadaje się do krojenia.

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

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

Akcja cięcia może podjąć obiekt nadaje się do krojenia i wykonywać żadnych wspólnych działań cięcia, które jest stosowane do wszystkich cuttables, a można też zastosować specyficzne zachowanie cięcia każdego obiektu.

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

Dzięki za to. Zamierzałem odpowiedzieć na to pytanie. Jeśli więc zastosujemy podejście oparte na wzorze strategii, powiedzmy, że każdy szef kuchni ma wielu członków danych (np. Tasting_spoon, salt_shaker, itp.) Używanych zarówno w klasie cook_food(), jak i plate_dish(). Wygląda na to, że kucharz klasy potrzebuje cook_food() i cook_food() potrzebuje klasy szefa kuchni. W jaki sposób uniknąć okrągłej zależności między powiedz cook_food() a klasowym kucharzem bez konieczności podawania mnóstwa argumentów do klasy cook_food()? Z punktu widzenia projektowania, jeśli klasy są silnie sprzężone, czy nie istnieje coś, co mogłoby je oddzielić? – user1790399

+0

Jednym ze sposobów uniknięcia cyrkularnej zależności byłoby utworzenie interfejsu szefa kuchni, który klasa cook_food może spożywać, a rzeczywisty szef kuchni może zaimplementować. Dopóki interfejs określa tylko szczegóły niezbędne dla cook_food, aby wiedzieć o szefie kuchni, nie ma problemu. (lub możesz zrobić to na odwrót, i przygotować interfejs do gotowania dla szefa kuchni.) Nie ma jednej właściwej drogi, ale z pewnością zasługuje na oddzielenie każdego zadania od własnej klasy. –

+0

Przepraszam, co to znaczy, że klasa może spożywać interfejs? Czy to oznacza robienie czegoś takiego, jeśli idziemy z interfejsem na klasie szefa kuchni, na przykład cook_food (chef-> interface_for_cooking()), z interface_for_cooking() składa się z połączeń takich jak get_salt_shaker(), get_tasting_spoon() itp .. .? I naśladując ten przykład, czy byłoby złym pomysłem mieć dwa oddzielne interfejsy dla klas cook_food() i plate_dish()? – user1790399

Powiązane problemy