Na przykład klasa zwykle ma członków klasy i metody, np .:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Ale po przeczytaniu na temat zasady pojedynczej odpowiedzialności i zasady otwartej zamkniętej wolę rozdzielić klasę na klasę DTO i klasę pomocniczą tylko metodami statycznymi, np .:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Myślę, że to pasuje do zasady pojedynczej odpowiedzialności, ponieważ teraz CatData jest odpowiedzialna tylko za przechowywanie danych, nie przejmuje się metodami (także CatMethods). Jest to również zgodne z zasadą otwartego zamkniętego, ponieważ dodawanie nowych metod nie musi zmieniać klasy CatData.
Moje pytanie brzmi, czy jest to dobry czy anty-wzór?
object-oriented
static-methods
dto
ocomfd
źródło
źródło
Odpowiedzi:
Pokazałeś dwie skrajności („wszystko prywatne i wszystkie (być może niepowiązane) metody w jednym obiekcie” vs. „wszystko publiczne i żadna metoda wewnątrz obiektu”). IMHO dobre modelowanie OO nie jest żadnym z nich , najsłodsze miejsce jest gdzieś pośrodku.
Jednym lakmusowym testem tego, które metody lub logika należą do klasy, a co na zewnątrz, jest sprawdzenie zależności, które wprowadzą metody. Metody, które nie wprowadzają dodatkowych zależności są w porządku, o ile dobrze pasują do abstrakcji danego obiektu . Metody wymagające dodatkowych zewnętrznych zależności (takie jak biblioteka rysunków lub biblioteka we / wy) rzadko są dobrym rozwiązaniem. Nawet jeśli spowodujesz, że zależności znikną za pomocą wstrzykiwania zależności, nadal zastanowię się dwa razy, czy umieszczenie takich metod w klasie domeny jest naprawdę konieczne.
Więc nie powinieneś upubliczniać każdego członka ani nie musisz implementować metod dla każdej operacji na obiekcie w klasie. Oto alternatywna sugestia:
Teraz
Cat
obiekt zapewnia wystarczającą logikę, aby otaczający kod mógł łatwo zaimplementowaćprintInfo
idraw
bez ujawniania wszystkich atrybutów publicznie. Właściwe miejsce dla tych dwóch metod najprawdopodobniej nie jest klasą bogówCatMethods
(ponieważprintInfo
idraw
najprawdopodobniej są to różne obawy, więc myślę, że jest bardzo mało prawdopodobne, aby należały do tej samej klasy).Mogę sobie wyobrazić,
CatDrawingController
który implementujedraw
(i może używa wstrzykiwania zależności do uzyskania obiektu Canvas). Mogę również wyobrazić sobie inną klasę, która implementuje niektóre dane wyjściowe i zastosowania konsoligetInfo
(printInfo
w tym kontekście mogą stać się przestarzałe). Ale aby podejmować rozsądne decyzje w tej sprawie, trzeba znać kontekst i sposób, w jakiCat
klasa będzie faktycznie używana.Tak właśnie interpretowałem krytyków Anemic Domain Model Fowlera - dla logiki generalnie wielokrotnego użytku (bez zewnętrznych zależności) same klasy domen są dobrym miejscem, więc powinny być do tego wykorzystywane. Ale to nie oznacza implementacji żadnej logiki, wręcz przeciwnie.
Zwróć też uwagę, że powyższy przykład pozostawia miejsce na podjęcie decyzji o (im) zmienności. Jeśli
Cat
klasa nie ujawni żadnych ustawień iImage
jest niezmienna, ta konstrukcja pozwoli naCat
niezmienne (czego nie zrobi podejście DTO). Ale jeśli uważasz, że niezmienność nie jest wymagana lub nie jest pomocna w twoim przypadku, możesz również pójść w tym kierunku.źródło
getInfo()
że ktoś wprowadza w błąd, zadając to pytanie. Subtelnie łączy obowiązki związane z prezentacjąprintInfo()
, pokonując celDrawingController
klasyDrawingController
. Zgadzam się z tymgetInfo()
iprintInfo()
są okropnymi nazwiskami. Zastanów siętoString()
iprintTo(Stream stream)
Późna odpowiedź, ale nie mogę się oprzeć.
W większości przypadków większość zasad, zastosowanych bez zastanowienia, będzie w większości okropnie nie tak (w tym ta).
Pozwólcie, że opowiem wam historię o narodzinach obiektu w chaosie jakiegoś właściwego, szybkiego i brudnego, proceduralnego kodu, który wydarzył się nie z założenia, ale z desperacji.
Mój stażysta i ja programujemy w parach, aby szybko utworzyć wyrzucony kod do zeskrobania strony internetowej. Nie mamy absolutnie żadnego powodu, aby oczekiwać, że ten kod będzie trwał długo, więc po prostu staramy się stworzyć coś, co działa. Chwytamy całą stronę jako sznurek i kroimy potrzebne rzeczy w najbardziej niesamowity kruchy sposób, jaki można sobie wyobrazić. Nie osądzaj. To działa.
Teraz, robiąc to, stworzyłem statyczne metody rąbania. Mój stażysta stworzył klasę DTO, która była bardzo podobna do twojej
CatData
.Kiedy po raz pierwszy spojrzałem na DTO, to mnie wkurzyło. Lata szkód wyrządzonych przez Javę w moim mózgu spowodowały, że odrzuciłem się na publiczne tereny. Ale pracowaliśmy w C #. C # nie potrzebuje przedwczesnych programów pobierających i ustawiających, aby zachować prawo do uczynienia danych niezmiennymi lub enkapsulowanymi później. Bez zmiany interfejsu możesz je dodać w dowolnym momencie. Może po prostu możesz ustawić punkt przerwania. Wszystko bez informowania klientów o tym. Yea C #. Boo Java.
Więc trzymałem się za język. Patrzyłem, jak użył moich metod statycznych do zainicjowania tego przed użyciem. Mieliśmy około 14 z nich. To było brzydkie, ale nie mieliśmy powodu się przejmować.
Potem potrzebowaliśmy go w innych miejscach. Odkryliśmy, że chcemy skopiować i wkleić kod. Przerzucanych jest 14 linii inicjalizacji. Zaczynało boleć. Wahał się i poprosił mnie o pomysły.
Niechętnie zapytałem: „czy wziąłbyś pod uwagę przedmiot?”
Spojrzał ponownie na swój DTO i zmieszał twarz w dezorientacji. „To obiekt”.
„Mam na myśli prawdziwy przedmiot”
„Hę?”
„Pozwól, że coś ci pokażę. Ty decydujesz, czy to jest przydatne”
Wybrałem nową nazwę i szybko wymyśliłem coś, co wyglądało trochę tak:
Nie zrobiło to nic, czego nie zrobiły już metody statyczne. Ale teraz wciągnąłem 14 metod statycznych do klasy, w której mogliby być sami z danymi, nad którymi pracowali.
Nie zmusiłem stażysty do korzystania z niego. Właśnie to zaoferowałem i pozwoliłem mu zdecydować, czy chce trzymać się metod statycznych. Poszedłem do domu, myśląc, że pewnie trzymałby się tego, co już pracował. Następnego dnia odkryłem, że używa go w wielu miejscach. Odszyfrował resztę kodu, który nadal był brzydki i proceduralny, ale ta odrobina złożoności była teraz ukryta przed nami za obiektem. Było trochę lepiej.
Teraz na pewno za każdym razem, gdy uzyskujesz do niego dostęp, robi sporo pracy. DTO jest ładną, szybko buforowaną wartością. Martwiłem się o to, ale zdałem sobie sprawę, że mogę dodać buforowanie, jeśli zajdzie taka potrzeba, bez dotykania żadnego z używanych kodów. Więc nie zamierzam zawracać sobie głowy, dopóki nas to nie obchodzi.
Czy mówię, że zawsze powinieneś trzymać się obiektów OO nad DTO? Nie. DTO błyszczy, gdy trzeba przekroczyć granicę, która powstrzymuje cię przed przemieszczaniem się metod. DTO mają swoje miejsce.
Ale podobnie jak obiekty OO. Dowiedz się, jak korzystać z obu narzędzi. Dowiedz się, ile kosztuje każda z nich. Naucz się decydować o problemie, sytuacji i stażu. Dogma nie jest tu twoim przyjacielem.
Ponieważ moja odpowiedź jest już absurdalnie długa, pozwólcie, że zwrócę uwagę na niektóre nieporozumienia związane z przeglądem waszego kodu.
Gdzie jest twój konstruktor? To nie pokazuje mi wystarczająco dużo, aby wiedzieć, czy jest to przydatne.
Możesz zrobić wiele głupich rzeczy w imię zasady pojedynczej odpowiedzialności. Mógłbym argumentować, że Cat Strings i Cat ints powinny być oddzielone. Te metody rysowania i obrazy muszą mieć własną klasę. Że twój program działa jako jedna odpowiedzialność, więc powinieneś mieć tylko jedną klasę. : P
Dla mnie najlepszym sposobem na przestrzeganie zasady pojedynczej odpowiedzialności jest znalezienie dobrej abstrakcji, która pozwoli Ci zawrzeć w pudełku złożoność, abyś mógł ją ukryć. Jeśli potrafisz nadać mu dobre imię, dzięki któremu ludzie nie będą zaskoczeni tym, co znajdą, gdy zaglądają do środka, to dość dobrze go przestrzegasz. Oczekiwanie, że podyktuje to więcej decyzji, to będzie kłopot. Szczerze mówiąc, oba twoje kody robią to, więc nie rozumiem, dlaczego SRP ma tutaj znaczenie.
Więc nie. Zasada otwartego zamknięcia nie polega na dodawaniu nowych metod. Chodzi o to, aby móc zmienić implementację starych metod i niczego nie edytować. Nic, co cię wykorzystuje, a nie twoje stare metody. Zamiast tego piszesz nowy kod gdzieś indziej. Jakaś forma polimorfizmu dobrze to zrobi. Nie widzę tego tutaj.
Do diabła, skąd mam wiedzieć? Spójrz, robienie tego w obie strony ma zalety i koszty. Po oddzieleniu kodu od danych możesz je zmienić bez konieczności ponownej kompilacji drugiego. Może jest to dla ciebie niezwykle ważne. Może to po prostu komplikuje twój kod.
Jeśli dzięki temu poczujesz się lepiej, nie jesteś tak daleko od czegoś, co Martin Fowler nazywa obiektem parametru . Nie musisz brać tylko prymitywów do swojego obiektu.
To, co chciałbym, abyś zrobił, to rozwinięcie poczucia, jak dokonać separacji, czy nie, w jednym ze stylów kodowania. Ponieważ wierz w to lub nie, nie musisz wybierać stylu. Musisz po prostu żyć z wyborem.
źródło
Natknąłeś się na temat debaty, która od ponad dekady tworzy argumenty wśród programistów. W 2003 roku Martin Fowler ukuł frazę „Anemic Domain Model” (ADM), aby opisać to oddzielenie danych i funkcji. On - i inni, którzy się z nim zgadzają - twierdzą, że „Bogate modele domenowe” (łączenie danych i funkcjonalności) to „właściwe OO”, a podejście ADM to anty-wzorzec inny niż OO.
Zawsze byli tacy, którzy odrzucają ten argument, a ta strona argumentu stała się głośniejsza i odważniejsza w ostatnich latach wraz z przyjęciem przez większą liczbę twórców technik rozwoju funkcjonalnego. Takie podejście aktywnie zachęca do rozdzielenia danych i obaw dotyczących funkcji. Dane powinny być w możliwie największym stopniu niezmienne, tak więc enkapsulacja stanu zmiennego staje się nieistotna. W takich sytuacjach dołączanie funkcji bezpośrednio do tych danych nie przynosi korzyści. To, czy jest to wtedy „nie OO”, czy nie, absolutnie nie interesuje takich ludzi.
Bez względu na to, po której stronie ogrodzenia siedzicie (siedzę bardzo mocno na stronie „Martin Fowler mówi mnóstwo starych rzeczy” BTW), wasze użycie metod statycznych
printInfo
idraw
jest niemal powszechnie odrzucone. Metody statyczne trudno kpić z pisania testów jednostkowych. Więc jeśli mają skutki uboczne (takie jak drukowanie lub rysowanie na jakimś ekranie lub innym urządzeniu), nie powinny być statyczne lub powinny mieć przekazaną lokalizację wyjściową jako parametr.Możesz mieć interfejs:
I implementacja, która jest wstrzykiwana do reszty systemu w czasie wykonywania (z innymi implementacjami używanymi do testowania):
Lub dodaj dodatkowe parametry, aby usunąć skutki uboczne z tych metod:
źródło
DTO - Obiekty transportu danych
są do tego przydatne. Jeśli zamierzasz przetaczać dane między programami lub systemami, pożądane są DTO, ponieważ zapewniają one zarządzalny podzbiór obiektu, który dotyczy tylko struktury danych i formatowania. Zaletą jest to, że nie trzeba synchronizować aktualizacji złożonych metod w kilku systemach (o ile dane podstawowe się nie zmieniają).
Cały sens OO polega na ścisłym powiązaniu danych i kodu działającego na te dane. Rozdzielanie obiektu logicznego na osobne klasy jest zwykle złym pomysłem.
źródło
Wiele wzorców projektowych i zasad jest sprzecznych, a ostatecznie tylko Twoja ocena dobrego programisty pomoże ci zdecydować, które wzorce powinny dotyczyć konkretnego problemu.
Moim zdaniem zasada Zrób najprostszą rzecz, która może być możliwa, powinna wygrać domyślnie, chyba że masz silny powód, aby skomplikować projekt. W twoim konkretnym przykładzie wybrałbym pierwszy projekt, który jest bardziej tradycyjnym podejściem obiektowym z danymi i funkcjami razem w tej samej klasie.
Oto kilka pytań, które możesz sobie zadać na temat aplikacji:
Odpowiedź na którekolwiek z tych pytań może doprowadzić do bardziej złożonego projektu z osobnymi klasami DTO i funkcyjnymi.
źródło
Czy to dobry wzór?
Prawdopodobnie dostaniesz tutaj różne odpowiedzi, ponieważ ludzie podążają za różnymi szkołami myślenia. Zanim zacznę: odpowiedź ta jest zgodna z programowaniem obiektowym, tak jak to opisuje Robert C. Martin w książce „Clean Code”.
„True” OOP
O ile mi wiadomo, istnieją 2 interpretacje OOP. Dla większości ludzi sprowadza się to do „obiekt jest klasą”.
Jednak druga szkoła myślenia okazała się bardziej pomocna: „Obiekt jest czymś, co coś robi”. Jeśli pracujesz z klasami, każda klasa byłaby jedną z dwóch rzeczy: „ posiadaczem danych ” lub obiektem . Posiadacze danych przechowują dane (duh), obiekty robią różne rzeczy. To nie znaczy, że posiadacz danych nie może mieć metod! Pomyśl o
list
: Alist
tak naprawdę nic nie robi , ale ma metody, które wymuszają sposób przechowywania danych .Posiadacze danych
Jesteś
Cat
najlepszym przykładem posiadacza danych. Jasne, poza twoim programem kot jest czymś, co coś robi , ale w twoim programieCat
jest ciągname
, intweight
i obrazimage
.To, że posiadacze danych nic nie robią , nie oznacza również, że nie zawierają logiki biznesowej! Jeśli twoja
Cat
klasa wymaga konstruktoraname
,weight
aimage
ty pomyślnie sformułowałeś regułę biznesową, że każdy kot ma je. Jeśli możesz zmienićweight
po utworzeniuCat
klasy, jest to kolejna enkapsulacja reguły biznesowej. To są bardzo podstawowe rzeczy, ale to tylko oznacza, że bardzo ważne jest, aby nie popełnić błędu - w ten sposób zapewnisz, że nie da się tego źle pomylić.Obiekty
Obiekty coś robią. Jeśli chcesz oczyścić obiekty *, możemy zmienić to na „Obiekty robią jedną rzecz”.
Drukowanie informacji o kocie jest zadaniem obiektu. Na przykład możesz nazwać ten obiekt „
CatInfoLogger
” za pomocą metody publicznejLog(Cat)
. To wszystko, co musimy wiedzieć z zewnątrz: ten obiekt rejestruje informacje kota i aby to zrobić, potrzebujeCat
.Wewnątrz ten obiekt będzie zawierał odniesienia do wszystkiego, czego potrzebuje, aby wypełnić swoją jedyną odpowiedzialność za rejestrowanie kotów. W tym przypadku, to tylko odniesienie do
System
zaSystem.out.println
, ale w większości przypadków będzie to prywatne odniesienia do innych obiektów. Jeśli logika formatowania wyjścia przed wydrukowaniem staje się zbyt skomplikowana,CatInfoLogger
można po prostu uzyskać odniesienie do nowego obiektuCatInfoFormatter
. Jeśli później każdy dziennik musi również zostać zapisany w pliku, możesz dodaćCatToFileLogger
obiekt, który to robi.Posiadacze danych a obiekty - podsumowanie
Dlaczego miałbyś to wszystko robić? Ponieważ teraz zmiana klasy zmienia tylko jedną rzecz ( sposób, w jaki kot jest zalogowany w przykładzie). Jeśli zmieniasz obiekt, zmieniasz tylko sposób wykonania określonej akcji; jeśli zmienisz właściciela danych, zmienisz tylko, jakie dane są przechowywane i / lub w jaki sposób są przechowywane.
Zmiana danych może również oznaczać sposób, w jaki coś zostało zrobione, musi się również zmienić, ale zmiana ta nie nastąpi, dopóki sam nie zmienisz odpowiedzialnego obiektu.
Przesada?
To rozwiązanie może wydawać się nieco przesadne. Pozwólcie, że powiem szczerze: tak jest . Jeśli cały twój program jest tak duży jak w przykładzie, nie rób żadnego z powyższych - po prostu wybierz jeden z proponowanych sposobów za pomocą rzutu monetą. Nie ma niebezpieczeństwa mylące inny kod, jeśli nie ma żaden inny kod.
Jednak każde „poważne” (= więcej niż skrypt lub 2) oprogramowanie jest prawdopodobnie na tyle duże, że skorzystałoby z takiej struktury.
Odpowiedź
Przekształcenie „większości klas” (bez dalszych kwalifikacji) w DTO / posiadaczy danych, nie jest anty-wzorcem, ponieważ w rzeczywistości nie jest to żaden wzorzec - jest mniej więcej losowy.
Oddzielanie danych i obiektów jest jednak postrzegane jako dobry sposób na utrzymanie kodu w czystości *. Czasami potrzebujesz tylko płaskiego, „anemicznego” DTO i to wszystko. Innym razem potrzebujesz metod egzekwowania sposobu przechowywania danych. Po prostu nie umieszczaj funkcjonalności programu z danymi.
* To jest „czyste” z wielką literą C, jak tytuł książki, ponieważ - pamiętaj pierwszy akapit - chodzi o jedną szkołę myślenia, podczas gdy są też inne.
źródło