Projekt: metoda obiektowa a metoda osobnej klasy, która przyjmuje parametr jako parametr?

14

Na przykład, czy lepiej zrobić:

Pdf pdf = new Pdf();
pdf.Print();

lub:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Inny przykład:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

lub:

Country m = new Country("Mexico");
Country us = new Country("US");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(us);
double mRatio = ds.GetDebtToGDPRatio(m);    

Obawiam się w ostatnim przykładzie, że istnieją potencjalnie nieskończone statystyki (ale powiedzmy nawet 10), że możesz chcieć wiedzieć o kraju; czy wszystkie należą do obiektu wiejskiego?

na przykład

Country m = new Country("Mexico");
double ratio = m.GetGDPToMedianIncomeRatio();

Są to proste stosunki, ale załóżmy, że statystyki są wystarczająco skomplikowane, aby uzasadnić metodę.

Gdzie jest linia między operacjami nieodłącznie związanymi z obiektem a operacjami, które można wykonać na obiekcie, ale nie są jego częścią?

Użytkownik
źródło

Odpowiedzi:

16

Przyjmując przykłady PDF jako punkt wyjścia, spójrzmy na to.

http://en.wikipedia.org/wiki/Single_responsibility_principle

Zasada jednolitej odpowiedzialności sugeruje, że obiekt powinien mieć jeden i tylko jeden cel. Pamiętaj o tym.

http://en.wikipedia.org/wiki/Separation_of_concerns

Zasada Separation of Concerns mówi nam, że klasy nie powinny mieć nakładających się funkcji.

Kiedy spojrzysz na te dwa, sugerują, że logika powinna iść w klasie tylko wtedy, gdy ma to sens, tylko jeśli ta klasa jest za to odpowiedzialna.

Teraz w twoim przykładzie PDF pytanie brzmi: kto jest odpowiedzialny za drukowanie? Co ma sens?

Pierwszy fragment kodu:

Pdf pdf = new Pdf();
pdf.Print();

To nie jest dobre. Dokument PDF nie drukuje się sam. Drukuje go ... ta da! ... drukarka. Drugi fragment kodu jest więc znacznie lepszy:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

To ma sens. Drukarka PDF drukuje dokument pdf. Co więcej, drukarka nie powinna być drukarką PDF ani drukarką fotograficzną. Powinna to być po prostu drukarka zdolna do drukowania przesyłanych do niej materiałów z najlepszymi z możliwych możliwości.

Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);

To takie proste. Umieść metody tam, gdzie mają sens. Oczywiście nie zawsze jest to takie proste. Weźmy na przykład statystyki swojego kraju:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

Martwisz się, że może nie być n statystyk i że nie powinny one należeć do klasy Country. To prawda. Jeśli jednak Twój model wymaga tylko tych konkretnych statystyk, ten przykład modelowania może być w porządku.

W takim przypadku można powiedzieć całkiem logicznie, że kraj powinien być w stanie obliczyć własne statystyki, specyficzne dla twojego modelu i obowiązujących wymagań.

I na tym polega rzecz: jakie są twoje wymagania? Twoje wymagania będą wpływać na sposób modelowania świata, kontekst, w którym te wymagania będą spełnione.

Jeśli rzeczywiście masz wiele / zmienną liczbę statystyk, wtedy twój drugi przykład ma większy sens:

Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);

Jeszcze lepiej, dysponuj abstrakcyjną nadklasą lub interfejsem o nazwie Statystyka, które przyjmują kraj jako parametr:

interface StatisticsCalculator // or a pure abstract class if doing C++
{
   double getStatistics(Country country); // or a pure virtual function if in C++
}

klasa DebtToGDPRatioStatisticsCalculator implementuje StatisticsCalculator ....

klasa InfantMortalityStatisticsCalculator implementuje StatisticsCalculator ...

I tak dalej i tak dalej. Co prowadzi do: uogólnienia, delegacji, abstrakcji. Gromadzenie danych statystycznych jest delegowane do określonych instancji, które uogólniają określoną abstrakcję (interfejs API do zbierania statystyk).

Nie wiem, czy to odpowiada 100% na twoje pytanie. W końcu nie mamy nieomylnych modeli opartych na nienaruszalnych prawach (tak jak robią to ludzie EE). Wszystko, co możesz zrobić, to położyć rzeczy, które mają sens. I to jest decyzja inżynierska, którą musisz podjąć. Najlepiej jest naprawdę zapoznać się z zasadami OO (i ogólnie dobrymi zasadami modelowania oprogramowania).

luis.espinal
źródło
1
+1 za interfejs StatisticsCalculator (i późniejsze użycie wzorca strategii). I dokładnie przemyślana odpowiedź
edwardsmatt
3
w tej chwili nie ma wystarczająco dużo czasu, aby to dokładnie zdekonstruować, ale należy zauważyć, że klasa Printer z czasem stanie się klasą Boga, ściśle powiązaną z wszelkiego rodzaju klasami dokumentów. Pdf.Print byłby preferowany - ale wszystko zależy od tego, jak zdefiniujesz „pojedynczą odpowiedzialność” ;-)
Steven A. Lowe
@ Steve - to, co proponujesz, to okropny pomysł (posiadanie narzędzia Pdf do drukowania ()). Nie odzwierciedla sposobu, w jaki druk jest wdrażany w prawdziwym życiu. Każdy system operacyjny i API drukowania, o których wiem, zapewnia abstrakcję Printer. Spójrz na listę drukarek w komputerze XP / Vista (lub w katalogu / var / spool lub równoważnym w * nix). Każda aplikacja serializuje obiekt dokumentu do jednej ze swoich drukarek. Nie ma drukarki Word, drukarki tekstowej ani PDF. Istnieją tylko drukarki specyficzne dla urządzenia drukującego, a nie specyficzne dla typu dokumentu.
luis.espinal
2
+1 Podoba mi się Zastanawiam się, co powiedziałeś ... @Steve i luis: Myślę, że brakujący fragment debaty na temat obiektów Boga jest ogólnym obiektem drukarki, powinien zaakceptować kilka standardowych formatów, takich jak ASCII lub bitmapa (chociaż pdf jest prawdopodobnie również uzasadnione) i na trzeciej klasie powinna spoczywać odpowiedzialność za konwersję określonego typu dokumentu (np. dokumentu tekstowego ms) na jeden z tych standardowych formatów.
Użytkownik
2
Wydaje mi się, że być może PDF powinien być w stanie renderować się albo na interfejsie Canvas, albo na obiekcie Image, który mógłby być następnie przetworzony przez obiekt Printer.
Winston Ewert
4

Myślę, że żadna z nich nie jest zdecydowanie lepsza od drugiej. Korzystanie z pdf.Print () jest ściślejsze, ale posiadanie klasy PdfPrinter może być lepsze, jeśli:

  • Musisz zarządzać instancjami drukarek
  • Istnieje szeroki zakres opcji i działań, które zniszczyłyby złożoność pdf.Print (...) (np. Anulowanie wydruku, dodatkowe formatowanie itp.)

W przeciwnym razie nie dałbym się rozłączyć.

Kevin Hsu
źródło
dobra, praktyczna odpowiedź; czas pokaże, jak to się musi ewoluować
Steven A. Lowe
1
Krótka sugestia dotyczy spojrzenia zarówno na logikę, jak i dane podczas stosowania SRP, aby zdecydować, czy będziemy żałować, że nie odłączyliśmy ich wcześniej. Problem z przechowywaniem ustawień poszczególnych drukarek w Pdfklasie polega na tym, że nie powinny być one przechowywane razem - Pdfsą przechowywane w pliku, ale ustawienia poszczególnych drukarek powinny być przechowywane z profilem użytkownika / maszyny.
rwong