Mam trochę trudności z projektowaniem zajęć w sposób oo. Czytałem, że obiekty ujawniają swoje zachowanie, a nie dane; dlatego zamiast używać getter / setters do modyfikowania danych, metodami danej klasy powinny być „czasowniki” lub akcje działające na obiekcie. Na przykład, w „Konto” obiektu, musielibyśmy metody Withdraw()
i Deposit()
zamiast setAmount()
itd. Patrz: Dlaczego getter i setter są złe .
Na przykład, biorąc pod uwagę klasę klienta, która przechowuje wiele informacji o kliencie, np. Imię, nazwisko, numer telefonu, adres itp., W jaki sposób można uniknąć getter / setters w celu uzyskania i ustawienia wszystkich tych atrybutów? Jaką metodą typu „zachowanie” można zapisać, aby wypełnić wszystkie te dane?
java
object-oriented
IntelliData
źródło
źródło
name()
onCustomer
jest jasne, czy wyraźniejsze, niż metody zwanejgetName()
.Odpowiedzi:
Jak stwierdzono w kilku odpowiedziach i komentarzach, DTO są odpowiednie i przydatne w niektórych sytuacjach, szczególnie w przesyłaniu danych ponad granicami (np. Przesyłanie seriali do JSON w celu wysłania za pośrednictwem usługi internetowej). W pozostałej części tej odpowiedzi z grubsza to zignoruję i porozmawiam o klasach domen oraz o tym, jak można je zaprojektować, aby zminimalizować (jeśli nie wyeliminować) funkcje pobierające i ustawiające, i nadal będą przydatne w dużym projekcie. Nie będę też mówić o tym, dlaczego usuwaj osoby pobierające lub ustawiające, ani kiedy to robić, ponieważ są to pytania same w sobie.
Jako przykład wyobraź sobie, że twój projekt to gra planszowa, taka jak Szachy lub Pancernik. Możliwe są różne sposoby przedstawienia tego w warstwie prezentacji (aplikacja konsoli, usługa internetowa, GUI itp.), Ale masz także domenę podstawową. Jedną z klas, które możesz mieć, jest
Coordinate
reprezentowanie pozycji na planszy. „Złym” sposobem napisania byłoby:(Zamierzam pisać przykłady kodu w języku C # zamiast w Javie, dla zwięzłości i ponieważ jestem bardziej zaznajomiony z tym. Mam nadzieję, że to nie jest problem. Koncepcje są takie same, a tłumaczenie powinno być proste.)
Usuwanie seterów: niezmienność
Podczas gdy publiczni zdobywcy i seterzy są potencjalnie problematyczni, setery są znacznie bardziej „złymi” z tych dwóch. Zazwyczaj są one również łatwiejsze do wyeliminowania. Proces ten jest prosty - ustaw wartość w konstruktorze. Wszelkie metody, które wcześniej zmutowały obiekt, powinny zamiast tego zwrócić nowy wynik. Więc:
Zauważ, że to nie chroni przed innymi metodami w klasie mutującymi X i Y. Aby być bardziej niezmiennym, możesz użyć
readonly
(final
w Javie). Ale tak czy inaczej - niezależnie od tego, czy uczynisz swoje nieruchomości naprawdę niezmiennymi, czy po prostu zapobiegniesz bezpośredniej mutacji publicznej przez seterów - robi to sztuczkę polegającą na usunięciu twoich publicznych seterów. W zdecydowanej większości przypadków działa to dobrze.Usuwanie getterów, część 1: Projektowanie pod kątem zachowania
Wszystko to jest dobre i dobre dla seterów, ale jeśli chodzi o gettery, faktycznie strzeliliśmy sobie w stopę, zanim jeszcze zaczęliśmy. Nasz proces polegał na wymyśleniu współrzędnych - reprezentowanych przez nie danych - i stworzeniu wokół nich klasy. Zamiast tego powinniśmy zacząć od tego, jakiego zachowania potrzebujemy od współrzędnych. Nawiasem mówiąc, proces ten jest wspomagany przez TDD, w którym klasy takie wyodrębniamy tylko wtedy, gdy są potrzebne, więc zaczynamy od pożądanego zachowania i stamtąd.
Powiedzmy, że pierwsze miejsce, w którym znalazłeś potrzebę, to
Coordinate
wykrywanie kolizji: chciałeś sprawdzić, czy dwa elementy zajmują to samo miejsce na planszy. Oto „zły” sposób (konstruktorów pominięto dla zwięzłości):A oto dobry sposób:
(
IEquatable
implementacja w skrócie dla uproszczenia). Projektując zachowanie, a nie modelując dane, udało nam się usunąć nasze programy pobierające.Uwaga: dotyczy to również twojego przykładu. Być może używasz ORM lub wyświetlasz informacje o kliencie na stronie internetowej lub czymś, w którym to przypadku pewna
Customer
metoda DTO prawdopodobnie miałaby sens. Ale to, że w systemie są klienci i są reprezentowani w modelu danych, nie oznacza automatycznie, że powinieneś miećCustomer
klasę w swojej domenie. Być może podczas projektowania zachowań pojawi się jeden, ale jeśli chcesz uniknąć getterów, nie twórz go z wyprzedzeniem.Usuwanie getterów, część 2: Zachowanie zewnętrzne
Więc powyższe to dobry początek, ale prędzej czy później będzie prawdopodobnie działać w sytuacji, gdy masz problem, który jest skojarzony z klasą, która w jakiś sposób zależy od stanu klasy, ale które nie należą na klasy. Tego rodzaju zachowanie zwykle występuje w warstwie usługowej aplikacji.
Biorąc nasz
Coordinate
przykład, ostatecznie będziesz chciał przedstawić swoją grę użytkownikowi, a to może oznaczać rysowanie na ekranie. Możesz na przykład mieć projekt interfejsu użytkownika, któryVector2
reprezentuje punkt na ekranie. Ale byłoby niewłaściwe, gdybyCoordinate
klasa przejmowała konwersję ze współrzędnych na punkt na ekranie - to wprowadzałoby wszelkie obawy związane z prezentacją do twojej podstawowej domeny. Niestety tego rodzaju sytuacja jest nieodłącznie związana z projektowaniem OO.Pierwszą opcją , która jest bardzo często wybierana, jest po prostu odsłonić tych cholernych łapaczy i powiedzieć z nią do diabła. Ma to tę zaletę prostoty. Ale ponieważ mówimy o unikaniu getterów, powiedzmy, że ze względu na argumenty odrzucamy ten i sprawdzamy, jakie są inne opcje.
Drugą opcją jest dodanie
.ToDTO()
metody do swojej klasy. To lub podobne może i tak być potrzebne, na przykład, jeśli chcesz zapisać grę, musisz uchwycić prawie cały swój stan. Ale różnica między robieniem tego dla twoich usług a samym bezpośrednim dostępem do gettera jest mniej lub bardziej estetyczna. Nadal ma tyle samo „zła”.Trzecią opcją - którą widziałem zalecaną przez Zorana Horvata w kilku filmach Pluralsight - jest użycie zmodyfikowanej wersji wzorca odwiedzającego. Jest to dość nietypowe zastosowanie i odmiana wzoru i myślę, że przebieg ludzi będzie się znacznie różnił w zależności od tego, czy zwiększy złożoność bez rzeczywistego zysku, czy też jest to miły kompromis w tej sytuacji. Chodzi przede wszystkim o użycie standardowego wzorca odwiedzającego, ale
Visit
metody powinny przyjmować stan, którego potrzebują, jako parametry, zamiast klasy, którą odwiedzają. Przykłady można znaleźć tutaj .W przypadku naszego problemu rozwiązaniem wykorzystującym ten wzorzec byłoby:
Jak zapewne wiesz,
_x
i_y
tak naprawdę nie są już zamknięte. Możemy je wyodrębnić, tworząc taki,IPositionTransformer<Tuple<int,int>>
który zwraca je bezpośrednio. W zależności od gustu może to powodować, że całe ćwiczenie jest bezcelowe.Jednak w przypadku publicznych programów pobierających bardzo łatwo jest robić rzeczy w niewłaściwy sposób, po prostu wyciągając dane bezpośrednio i używając ich z naruszeniem instrukcji Tell, Don't Ask . Zważywszy, że za pomocą tego wzoru to faktycznie prościej zrobić to we właściwy sposób: gdy chcesz utworzyć zachowanie, będziesz automatycznie rozpocząć tworząc rodzaj z nim związane. Naruszenia TDA będą bardzo śmierdzące i prawdopodobnie będą wymagać obejścia prostszego, lepszego rozwiązania. W praktyce punkty te znacznie ułatwiają robienie tego we właściwy sposób, niż „zły” sposób, który zachęcają osoby pobierające.
Wreszcie , nawet jeśli początkowo nie jest to oczywiste, mogą istnieć sposoby na ujawnienie wystarczającej ilości tego, czego potrzebujesz jako zachowanie, aby uniknąć konieczności ujawnienia stanu. Na przykład, używając naszej poprzedniej wersji,
Coordinate
której jedynym członkiem publicznym jestEquals()
(w praktyce wymagałoby to pełnejIEquatable
implementacji), możesz napisać następującą klasę w warstwie prezentacji:Okazuje się, być może zaskakujące, że wszystkie zachowania, których naprawdę potrzebowaliśmy od współrzędnych do osiągnięcia naszego celu, to sprawdzanie równości! Oczywiście to rozwiązanie jest dostosowane do tego problemu i przyjmuje założenia dotyczące akceptowalnego wykorzystania / wydajności pamięci. To tylko przykład, który pasuje do tej konkretnej domeny problemowej, a nie plan ogólnego rozwiązania.
I znowu, opinie będą się różnić, czy w praktyce jest to niepotrzebna złożoność. W niektórych przypadkach takie rozwiązanie może nie istnieć lub może być nadmiernie dziwne lub złożone, w którym to przypadku możesz powrócić do powyższych trzech.
źródło
Customer
mieć klasa, która wymaga mutacji swojego numeru telefonu? Być może zmienia się numer telefonu klienta i muszę zachować tę zmianę w bazie danych, ale to nie jest odpowiedzialność za obiekt domeny zapewniający zachowanie. Jest to problem z dostępem do danych i prawdopodobnie zostałby rozwiązany za pomocą DTO i, powiedzmy, repozytorium.Customer
względnie świeżych danych obiektu domeny (w synchronizacji z bazą danych) jest kwestią zarządzania jego cyklem życia, co również nie jest jego własną odpowiedzialnością, i prawdopodobnie ponownie trafiłoby do repozytorium, fabryki lub kontenera MKOl lub cokolwiek wystąpiCustomer
s.Najprostszym sposobem uniknięcia seterów jest przekazanie wartości do metody konstruktora, gdy obiekt jest
new
podniesiony. Jest to również zwykły wzór, gdy chcesz uczynić obiekt niezmiennym. To powiedziawszy, w prawdziwym świecie rzeczy nie zawsze są tak jasne.Prawdą jest, że metody powinny dotyczyć zachowania. Jednak niektóre obiekty, takie jak Klient, istnieją przede wszystkim do przechowywania informacji. Są to obiekty, które najbardziej korzystają z pobierających i ustawiających; gdyby takie metody nie były wcale potrzebne, po prostu całkowicie je wyeliminowalibyśmy.
Dalsza lektura,
kiedy Gettery i Settery są uzasadnione
źródło
setEvil(null);
Zupełnie dobrze jest mieć obiekt, który ujawnia dane, a nie zachowanie. Nazywamy to po prostu „obiektem danych”. Wzorzec istnieje pod nazwami, takimi jak Obiekt transferu danych lub Obiekt wartości. Jeśli celem obiektu jest przechowywanie danych, pobierające i ustawiające są ważne, aby uzyskać dostęp do danych.
Dlaczego więc ktoś miałby mówić „metody pobierające i ustawiające są złe”? Zobaczysz to bardzo często - ktoś bierze wytyczną, która jest całkowicie poprawna w określonym kontekście, a następnie usuwa kontekst, aby uzyskać bardziej uderzający nagłówek. Na przykład „ faworyzowanie kompozycji zamiast dziedziczenia ” jest dobrą zasadą, ale wkrótce ktoś usunie kontekst i napisze „ Dlaczego rozszerzenie jest złe ” (hej, ten sam autor, co za zbieg okoliczności!) Lub „ dziedzictwo jest złe i musi być zniszczone ”.
Jeśli spojrzysz na treść artykułu, to ma on pewne ważne punkty, po prostu rozciąga punkt, aby utworzyć nagłówek przynęty kliknięcia. Na przykład w artykule stwierdzono, że szczegóły implementacji nie powinny być ujawniane. Takie są zasady enkapsulacji i ukrywania danych, które są fundamentalne w OO. Jednak metoda gettera z definicji nie ujawnia szczegółów implementacji. W przypadku obiektu danych Klienta właściwości Nazwa , Adres itp. Nie są szczegółami implementacji, ale raczej całym celem obiektu i powinny być częścią interfejsu publicznego.
Przeczytaj kontynuację artykułu, do którego linkujesz, aby zobaczyć, jak sugeruje on ustawianie właściwości takich jak „imię” i „pensja” na obiekcie „Pracownik” bez użycia złych seterów. Okazuje się, że używa wzorca z „eksporterem”, który jest zapełniony metodami o nazwie dodaj nazwę, dodaj wynagrodzenie, które z kolei ustawia pola o tej samej nazwie ... W końcu ostatecznie używa dokładnie wzoru ustawiającego, tylko z inna konwencja nazewnictwa.
To tak, jakbyś myślał, że unikniesz pułapek singletonów, zmieniając ich nazwy tylko na inne, zachowując tę samą implementację.
źródło
Aby przekształcić
Customer
klasę z obiektu danych, możemy zadać sobie następujące pytania dotyczące pól danych:Jak chcemy korzystać z {data field}? Gdzie jest używane {pole danych}? Czy można i należy przenieść użycie {pola danych} do klasy?
Na przykład:
Jaki jest cel
Customer.Name
?Możliwe odpowiedzi, wyświetl nazwę na stronie logowania, użyj nazwy w mailach do klienta.
Co prowadzi do metod:
Jaki jest cel
Customer.DOB
?Sprawdzanie wieku klienta. Zniżki na urodziny klienta. Mailingi
Biorąc pod uwagę komentarze, przykładowy obiekt
Customer
- zarówno jako obiekt danych, jak i „prawdziwy” obiekt z własnymi obowiązkami - jest zbyt szeroki; tzn. ma zbyt wiele właściwości / obowiązków. Co prowadzi do wielu składników w zależności odCustomer
(poprzez odczytanie jego właściwości) lub doCustomer
wielu składników. Być może istnieją różne poglądy klienta, być może każdy powinien mieć własną odrębną klasę 1 :Klient w kontekście
Account
transakcji pieniężnych i jest prawdopodobnie używany tylko do:Account
s.Ten klient nie potrzebuje pola, na przykład
DOB
,FavouriteColour
,Tel
, a może nawet nieAddress
.Klient w kontekście użytkownika logującego się na stronie bankowej.
Odpowiednie pola to:
FavouriteColour
, które mogą przybrać formę spersonalizowanych tematów;LanguagePreferences
, iGreetingName
Zamiast właściwości z modułami pobierającymi i ustawiającymi można je przechwycić za pomocą jednej metody:
Klient w kontekście marketingu i spersonalizowanej korespondencji.
Tutaj nie polegamy na właściwościach obiektu danych, lecz zaczynamy od obowiązków obiektu; na przykład:
Fakt, że ten obiekt klienta ma
FavouriteColour
właściwość i / lubAddress
właściwość, staje się nieistotny: być może implementacja korzysta z tych właściwości; ale może również korzystać z niektórych technik uczenia maszynowego i wykorzystywać wcześniejsze interakcje z klientem, aby odkryć, które produkty mogą być zainteresowane.1. Oczywiście klasy
Customer
iAccount
były przykładami, a dla prostego przykładu lub zadania domowego rozdzielenie tego klienta może być przesadą, ale na przykładzie podziału mam nadzieję wykazać, że metoda przekształcenia obiektu danych w obiekt z obowiązki będą działać.źródło
Customer.FavoriteColor
?TL; DR
Modelowanie zachowania jest dobre.
Modelowanie dobrych (!) Abstrakcji jest lepsze.
Czasami wymagane są obiekty danych.
Zachowanie i abstrakcja
Istnieje kilka powodów, dla których należy unikać getterów i seterów. Jednym z nich jest, jak zauważyłeś, unikanie modelowania danych. To jest właściwie drobny powód. Głównym powodem jest zapewnienie abstrakcji.
W twoim przykładzie z kontem bankowym, które jest jasne:
setBalance()
Metoda byłaby naprawdę zła, ponieważ ustawienie salda nie jest tym, do czego powinno się używać konta. Zachowanie konta powinno jak najbardziej odbiegać od jego bieżącego salda. Może brać pod uwagę saldo przy podejmowaniu decyzji o niepowodzeniu wypłaty, może dać dostęp do bieżącego salda, ale modyfikacja interakcji z kontem bankowym nie powinna wymagać od użytkownika obliczenia nowego salda. To samo powinno zrobić konto.Nawet parę
deposit()
iwithdraw()
metod nie jest idealna do modelowania konta bankowego. Lepszym sposobem byłoby zapewnienie tylko jednejtransfer()
metody, która bierze pod uwagę inne konto i kwotę jako argumenty. Pozwoliłoby to klasie kont na trywialne upewnienie się, że przypadkowo nie utworzysz / nie zniszczysz pieniędzy w systemie, zapewniłoby to bardzo użyteczną abstrakcję i zapewniłoby użytkownikom więcej wglądu, ponieważ wymusiłoby użycie specjalnych kont dla zarobione / zainwestowane / utracone pieniądze (patrz podwójne księgowanie ). Oczywiście nie każde użycie konta wymaga takiego poziomu abstrakcji, ale zdecydowanie warto zastanowić się, ile abstrakcji mogą zapewnić twoje klasy.Pamiętaj, że zapewnianie abstrakcji i ukrywanie wewnętrznych danych nie zawsze jest tym samym. Prawie każda aplikacja zawiera klasy, które są właściwie tylko danymi. Krotki, słowniki i tablice są częstymi przykładami. Nie chcesz ukrywać współrzędnej x punktu przed użytkownikiem. Jest bardzo mało abstrakcji, którą możesz / powinieneś robić z racją.
Klasa klienta
Klient jest z pewnością podmiotem w twoim systemie, który powinien spróbować dostarczyć użytecznych abstrakcji. Na przykład prawdopodobnie powinien być powiązany z koszykiem, a połączenie koszyka z klientem powinno pozwolić na dokonanie zakupu, co może zainicjować takie działania, jak wysłanie mu żądanych produktów, obciążenie go pieniędzmi (biorąc pod uwagę wybraną przez niego płatność metoda) itp.
Najważniejsze jest to, że wszystkie dane, o których wspomniałeś, są nie tylko powiązane z klientem, ale wszystkie te dane można modyfikować. Klient może się przenieść. Mogą zmienić wystawcę karty kredytowej. Mogą zmienić swój adres e-mail i numer telefonu. Cholera, mogą nawet zmienić nazwisko i / lub płeć! Tak więc w pełni funkcjonalna klasa klienta rzeczywiście musi zapewniać pełny dostęp modyfikujący do wszystkich tych elementów danych.
Mimo to ustawiający mogą / powinni świadczyć nietrywialne usługi: mogą zapewnić prawidłowy format adresów e-mail, weryfikację adresów pocztowych itp. Podobnie „osoby pobierające” mogą świadczyć usługi na wysokim poziomie, takie jak udostępnianie adresów e-mail w
Name <[email protected]>
formacie używając pól nazw i zdeponowanego adresu e-mail, lub podaj poprawnie sformatowany adres pocztowy itp. Oczywiście, co z tej funkcji wysokiego poziomu ma sens, zależy w dużej mierze od twojego przypadku użycia. Może to być całkowita przesada lub może wezwać inną klasę, aby zrobiła to dobrze. Wybór poziomu abstrakcji nie jest łatwy.źródło
Próbując rozwinąć odpowiedź Kaspera, najłatwiej jest zwalczać i eliminować seterów. W dość niejasnym, wymachującym ręką (i miejmy nadzieję dowcipnym) sporze:
Kiedy Customer.Name kiedykolwiek się zmieni?
Rzadko. Może wzięli ślub. Lub poszedł na ochronę świadków. Ale w takim przypadku chciałbyś również sprawdzić i ewentualnie zmienić ich miejsce zamieszkania, najbliższych krewnych i inne informacje.
Kiedy DOB kiedykolwiek się zmieni?
Tylko przy początkowym utworzeniu lub przy wkręcaniu danych. Lub jeśli są dominikańskim graczem w baseball. :-)
Te pola nie powinny być dostępne dla rutynowych, normalnych seterów. Może masz
Customer.initialEntry()
metodę lubCustomer.screwedUpHaveToChange()
metodę wymagającą specjalnych uprawnień. Ale nie mam publicznejCustomer.setDOB()
metody.Zwykle klient jest odczytywany z bazy danych, interfejsu API REST, niektórych plików XML, cokolwiek. Mieć metodę
Customer.readFromDB()
lub, jeśli jesteś bardziej rygorystyczny w kwestii SRP / separacji problemów, będziesz miał oddzielny konstruktor, np.CustomerPersister
Obiekt zread()
metodą. Wewnętrznie jakoś ustawiają pola (wolę używać dostępu do pakietu lub wewnętrznej klasy YMMV). Ale znowu unikaj publicznych seterów.(Dodatek, ponieważ pytanie nieco się zmieniło ...)
Powiedzmy, że twoja aplikacja intensywnie korzysta z relacyjnych baz danych. Byłoby głupotą mieć metody
Customer.saveToMYSQL()
lubCustomer.readFromMYSQL()
metody. To tworzy niepożądane połączenie z konkretnym, niestandardowym i prawdopodobnie zmieniającym podmiot. Na przykład po zmianie schematu lub zmianie na Postgress lub Oracle.Jednak IMO, to całkowicie dopuszczalne, aby para Klienta do abstrakcyjnego standardzie ,
ResultSet
. Oddzielny obiekt pomocnika (nazywam goCustomerDBHelper
, który prawdopodobnie jest podklasąAbstractMySQLHelper
) wie o wszystkich złożonych połączeniach z bazą danych, zna trudne szczegóły dotyczące optymalizacji, zna tabele, zapytania, sprzężenia itp. (Lub używa ORM jak Hibernacja), aby wygenerować zestaw wyników. Twój przedmiot do rozmowyResultSet
, który jest abstrakcyjny standardem , raczej się nie zmieni. Po zmianie podstawowej bazy danych lub schemacie Klient się nie zmienia , ale CustomerDBHelper tak. Jeśli masz szczęście, zmienia się tylko AbstractMySQLHelper, który automatycznie wprowadza zmiany dla klienta, sprzedawcy, wysyłki itp.W ten sposób możesz (być może) uniknąć lub zmniejszyć potrzebę pobierania i ustawiania.
I głównym punktem artykułu Holub, porównaj i porównaj powyższe z tym, jak by to było, gdybyś użył getterów i settererów do wszystkiego i zmienił bazę danych.
Podobnie, powiedzmy, że używasz dużo XML. IMO, możesz połączyć klienta z abstrakcyjnym standardem, takim jak Python xml.etree.ElementTree lub Java org.w3c.dom.Element . Klient otrzymuje i ustawia się z tego. Ponownie możesz (być może) zmniejszyć potrzebę pobierania i ustawiania.
źródło
Kwestia posiadania programów pobierających i ustawiających może wynikać z faktu, że klasa może być używana w logice biznesowej w jeden sposób, ale możesz także mieć klasy pomocnicze do serializacji / deserializacji danych z bazy danych lub pliku lub innego trwałego magazynu.
Z uwagi na fakt, że istnieje wiele sposobów przechowywania / pobierania danych i chcesz oddzielić obiekty danych od sposobu ich przechowywania, hermetyzacja może być „zagrożona” przez upublicznienie tych członków lub udostępnienie ich za pośrednictwem programów pobierających i seterów, co jest prawie tak złe, jak ich upublicznienie.
Są na to różne sposoby. Jednym ze sposobów jest udostępnienie danych „przyjacielowi”. Chociaż przyjaźń nie jest dziedziczona, można temu zaradzić przez dowolny serializator żądający informacji od przyjaciela, tj. Podstawowy serializator „przekazuje” informacje.
Twoja klasa może mieć ogólną metodę „fromMetadata” lub „toMetadata”. Z metadanych konstruuje obiekt, więc równie dobrze może być konstruktorem. Jeśli jest to język dynamicznie typowany, metadane są dość standardowe dla takiego języka i prawdopodobnie są podstawowym sposobem konstruowania takich obiektów.
Jeśli twoim językiem jest język C ++, jednym z rozwiązań jest posiadanie publicznej „struktury” danych, a następnie, aby klasa miała instancję tej „struktury” jako członka, a właściwie wszystkie dane, które zamierzasz przechowywać / odzyskać do zapisania w nim. Następnie możesz łatwo napisać „opakowania”, aby odczytać / zapisać dane w wielu formatach.
Jeśli twoim językiem jest C # lub Java, które nie mają „struktur”, możesz zrobić podobnie, ale teraz jest to klasa dodatkowa. Nie ma prawdziwej koncepcji „własności” danych ani ciągłości, więc jeśli podasz instancję klasy zawierającej twoje dane i będzie ona publicznie dostępna, wszystko, co zostanie utrzymane, może ją zmodyfikować. Możesz go „sklonować”, chociaż może to być kosztowne. Alternatywnie możesz sprawić, aby ta klasa miała prywatne dane, ale korzystały z akcesoriów. Daje to użytkownikom twojej klasy rondo sposób na uzyskanie dostępu do danych, ale nie jest to bezpośredni interfejs z twoją klasą i jest to naprawdę szczegół w przechowywaniu danych klasy, co również jest przypadkiem użycia.
źródło
OOP polega na enkapsulacji i ukrywaniu zachowań wewnątrz obiektów. Obiekty to czarne skrzynki. To jest sposób na zaprojektowanie rzeczy. Zaletą jest to, że w wielu przypadkach nie trzeba znać stanu wewnętrznego innego komponentu i lepiej jest tego nie znać. Możesz wymusić ten pomysł głównie za pomocą interfejsów lub wewnątrz obiektu z widocznością i dbając tylko o dozwolone czasowniki / działania są dostępne dla dzwoniącego.
Działa to dobrze w przypadku jakiegoś problemu. Na przykład w interfejsach użytkownika do modelowania poszczególnych składników interfejsu użytkownika. Kiedy interaktujesz z polem tekstowym, jesteś zainteresowany tylko ustawieniem tekstu, uzyskaniem go lub odsłuchaniem zdarzenia zmiany tekstu. Zazwyczaj nie interesuje Cię, gdzie znajduje się kursor, czcionka użyta do narysowania tekstu lub sposób użycia klawiatury. Kapsułkowanie zapewnia tutaj wiele.
Przeciwnie, gdy dzwonisz do usługi sieciowej, podajesz wyraźne dane wejściowe. Zwykle jest tam gramatyka (jak w JSON lub XML) i wszystkie opcje wywołania usługi nie mają powodu do ukrywania. Chodzi o to, że możesz wywołać usługę tak, jak chcesz, a format danych jest publiczny i opublikowany.
W tym przypadku lub wielu innych (takich jak dostęp do bazy danych) naprawdę pracujesz z udostępnionymi danymi. Jako taki nie ma powodu, aby go ukrywać, wręcz przeciwnie, chcesz go udostępnić. Mogą istnieć obawy o dostęp do odczytu / zapisu lub spójność sprawdzania danych, ale w tym rdzeniu jest to podstawowa koncepcja, jeśli jest ona publiczna.
Dla takiego wymogu projektowego, w którym chcesz uniknąć enkapsulacji i upublicznić rzeczy, i oczywiście unikać obiektów. Tak naprawdę potrzebujesz krotek, struktur C lub ich odpowiedników, a nie obiektów.
Ale dzieje się tak również w językach takich jak Java, jedyne, co możesz modelować, to obiekty lub tablice obiektów. Obiekty te mogą pomieścić kilka typów tubylców (int, float ...), ale to wszystko. Ale obiekty mogą również zachowywać się jak zwykła struktura z tylko polami publicznymi i tym wszystkim.
Więc jeśli modelujesz dane, możesz to zrobić za pomocą publicznych pól wewnątrz obiektów, ponieważ nie potrzebujesz więcej. Nie używasz enkapsulacji, ponieważ jej nie potrzebujesz. Odbywa się to w wielu językach. Historycznie w javie standardowa róża, w której za pomocą gettera / settera można przynajmniej mieć kontrolę odczytu / zapisu (nie dodając na przykład settera), a narzędzia i frameworki przy użyciu interfejsu API instrospection szukałyby metod getter / setter i używały go do autouzupełnianie treści lub wyświetlanie tez jako modyfikowalnych pól w automatycznie generowanym interfejsie użytkownika.
Jest też argument, że można dodać trochę logiki / sprawdzania w metodzie settera.
W rzeczywistości prawie nie ma uzasadnienia dla getter / setter, ponieważ są one najczęściej używane do modelowania czystych danych. Frameworki i programiści używający twoich obiektów oczekują, że getter / setter i tak zrobią tylko ustawianie / pobieranie pól. W rzeczywistości nie robisz nic więcej z getter / setter niż to, co można zrobić z polami publicznymi.
Ale to stare nawyki i stare nawyki są trudne do usunięcia ... Możesz nawet być zagrożony przez twoich kolegów lub nauczyciela, jeśli nie umieścisz getterów / seterów wszędzie na ślepo, jeśli nie mają tła, aby lepiej zrozumieć, czym są i czym są nie.
Prawdopodobnie będziesz musiał zmienić język, aby uzyskać dostęp do wszystkich kodów pobierających / ustawiających tezy. (Jak C # lub lisp). Dla mnie getters / setter to kolejny błąd o wartości miliarda dolarów ...
źródło
@Getter @Setter class MutablePoint3D {private int x, y, z;}
.Myślę, że to pytanie jest kłujące, ponieważ martwisz się metodami zapełniania danych, ale nie widzę żadnych oznak tego, jakie zachowanie
Customer
klasa obiektów ma zawierać.Nie należy mylić
Customer
jako klasy obiektów z „Klientem” jako użytkownikiem / aktorem, który wykonuje różne zadania za pomocą oprogramowania.Kiedy mówisz, że dana klasa klienta zachowuje wiele, jeśli informacje o kliencie, to jeśli chodzi o zachowanie, wygląda na to, że klasa klienta niewiele odróżnia ją od kamienia. A
Rock
może mieć kolor, możesz nadać mu nazwę, możesz mieć pole do przechowywania jego bieżącego adresu, ale nie oczekujemy żadnego inteligentnego zachowania od skały.Z powiązanego artykułu o getterach / seterach będących złymi:
Bez zdefiniowanego zachowania odniesienie do skały jako do
Customer
nie zmienia faktu, że jest to po prostu obiekt z pewnymi właściwościami, które chcesz śledzić, i nie ma znaczenia, jakie sztuczki chcesz zagrać, aby uciec od łapaczy i setery. Skała nie dba o to, czy ma prawidłową nazwę, a od skały nie można się dowiedzieć, czy adres jest prawidłowy, czy nie.Twój system zamówień może kojarzyć się
Rock
z zamówieniem zakupu i dopókiRock
ma on zdefiniowany adres, wówczas pewna część systemu może zapewnić dostawę przedmiotu do skały.We wszystkich tych przypadkach
Rock
jest to po prostu Obiekt danych i pozostanie nim, dopóki nie zdefiniujemy konkretnych zachowań z przydatnymi wynikami zamiast hipotetycznych.Spróbuj tego:
Kiedy unikniesz przeciążenia słowa „Klient” 2 potencjalnie różnymi znaczeniami, powinno to ułatwić konceptualizację.
Czy
Rock
obiekt składa zamówienie, czy też jest to coś, co robi człowiek, klikając elementy interfejsu użytkownika w celu uruchomienia akcji w systemie?źródło
Dodam tutaj moje 2 centy, wspominając o podejściu do obiektów mówiących w języku SQL .
Podejście to opiera się na pojęciu obiektu zamkniętego. Ma wszystkie zasoby potrzebne do wdrożenia swojego zachowania. Nie trzeba mówić, jak wykonać swoją pracę - wystarczy deklaracja. Obiekt zdecydowanie nie musi przechowywać wszystkich swoich danych jako właściwości klasy. To naprawdę nie ma - i nie powinno - mieć znaczenia, skąd pochodzą.
Mówiąc o agregacie , niezmienność również nie stanowi problemu. Powiedzmy, że istnieje sekwencja stanów, które może zawierać agregacja: za każdym razem można wdrożyć każdy stan jako samodzielny obiekt. Prawdopodobnie możesz pójść jeszcze dalej: porozmawiaj ze swoim ekspertem w dziedzinie. Są szanse, że on lub ona nie postrzega tego agregatu jako jakiegoś zjednoczonego bytu. Prawdopodobnie każdy stan ma swoje znaczenie, zasługując na swój własny przedmiot.
Na koniec chciałbym zauważyć, że proces wyszukiwania obiektów jest bardzo podobny w przypadku rozkładu systemu na podsystemy . Oba są oparte na zachowaniu, a nie na niczym innym.
źródło