Mam klasę o nazwie, Heading
która robi kilka rzeczy, ale powinna również być w stanie zwrócić przeciwieństwo bieżącej wartości nagłówka, która w końcu musi zostać użyta poprzez utworzenie nowej instancji Heading
samej klasy.
Mogę mieć prostą właściwość wywoływaną w reciprocal
celu zwrócenia przeciwnego nagłówka bieżącej wartości, a następnie ręcznie utworzyć nową instancję klasy Heading, lub mogę utworzyć metodę polegającą createReciprocalHeading()
na automatycznym utworzeniu nowej instancji klasy Heading i zwróceniu jej do właściwości użytkownik.
Jednak jeden z moich kolegów polecił mi po prostu utworzenie właściwości klasy o nazwie, reciprocal
która zwraca nową instancję samej klasy za pomocą metody getter.
Moje pytanie brzmi: czy to nie jest anty-wzorzec dla właściwości klasy, która zachowuje się w ten sposób?
Uważam to za mniej intuicyjne, ponieważ:
- Moim zdaniem własność klasy nie powinna zwracać nowej instancji klasy, i
- Nazwa właściwości, która
reciprocal
nie pomaga deweloperowi w pełni zrozumieć jej zachowanie, bez uzyskania pomocy z IDE lub sprawdzenia podpisu gettera.
Czy jestem zbyt rygorystyczny co do tego, co powinna zrobić własność klasy, czy jest to uzasadniona obawa? Zawsze starałem się zarządzać stanem klasy poprzez jej pola i właściwości oraz jej zachowanie za pomocą metod, i nie rozumiem, jak to pasuje do definicji właściwości klasy.
źródło
Heading
jako niezmiennego typu ireciprocal
powrót nowegoHeading
jest dobrą praktyką „dołu sukcesu”. (z zastrzeżeniem, że dwa wezwaniareciprocal
powinny zwrócić „to samo”, tzn. powinny przejść test równości.)Odpowiedzi:
Nie jest nieznane posiadanie takich funkcji jak Copy () lub Clone (), ale tak, myślę, że masz rację, że martwisz się tym.
Weź na przykład:
Przydałoby się ostrzeżenie, że za każdym razem właściwość była nowym obiektem.
możesz również założyć, że:
Jednak. Jeśli twoja klasa nagłówkowa była niezmiennym typem wartości, to możesz zaimplementować te relacje, a odwrotność może być dobrą nazwą dla operacji
źródło
Copy()
iClone()
są sposoby, a nie właściwości. Uważam, że OP odnosi się do osób uzyskujących nieruchomości zwracających nowe wystąpienia.String.Substring()
,Array.Reverse()
,Int32.GetHashCode()
, itd.If your heading class was an immutable value type
- Myślę, że powinieneś dodać, że ustawiacze właściwości na obiektach niezmiennych powinni zawsze zwracać nowe wystąpienia (lub przynajmniej jeśli nowa wartość nie jest taka sama jak stara wartość), ponieważ taka jest definicja obiektów niezmiennych. :)Interfejs klasy prowadzi użytkownika do przyjęcia założeń dotyczących jego działania.
Jeśli wiele z tych założeń jest poprawnych, a kilka jest błędnych, interfejs jest dobry.
Jeśli wiele z tych założeń jest błędnych, a niewiele ma rację, interfejs jest śmieci.
Powszechnym założeniem dotyczącym właściwości jest to, że wywoływanie funkcji get jest tanie. Innym powszechnym założeniem dotyczącym właściwości jest to, że wywołanie funkcji get dwa razy z rzędu zwróci to samo.
Możesz obejść ten problem, stosując spójność, aby zmienić oczekiwania. Na przykład, dla małej biblioteki 3D, gdzie trzeba
Vector
,Ray
Matrix
itd można zrobić to w taki sposób, jak w pozyskiwaniuVector.normal
iMatrix.inverse
są prawie zawsze drogie. Niestety, nawet jeśli twoje interfejsy konsekwentnie używają drogich właściwości, interfejsy będą co najmniej tak intuicyjne, jak te, które używają,Vector.create_normal()
aMatrix.create_inverse()
jednak nie znam mocnego argumentu, który można by uzasadnić, że używanie właściwości tworzy bardziej intuicyjny interfejs, nawet po zmianie oczekiwań.źródło
Właściwości powinny zwracać się bardzo szybko, zwracać tę samą wartość przy powtarzanych wywołaniach, a uzyskanie ich wartości nie powinno mieć skutków ubocznych. To, co tu opisujesz, nie powinno być implementowane jako własność.
Twoja intuicja jest zasadniczo poprawna. Metoda fabryczna nie zwraca tej samej wartości przy powtarzanych wywołaniach. Za każdym razem zwraca nową instancję. To prawie dyskwalifikuje to jako własność. Nie jest to również szybkie, jeśli późniejszy rozwój zwiększa wagę tworzenia instancji, np. Zależności sieci.
Właściwości powinny zasadniczo być bardzo prostymi operacjami, które pobierają / ustawiają część bieżącego stanu klasy. Operacje fabryczne nie spełniają tych kryteriów.
źródło
DateTime.Now
Właściwość jest powszechnie stosowane i odnosi się do nowego obiektu. Myślę, że nazwa właściwości może pomóc w usunięciu niejasności co do tego, czy ten sam obiekt jest zwracany za każdym razem. Wszystko jest w nazwie i w jaki sposób jest używane.DateTime
jest to typ wartości i nie ma pojęcia tożsamości obiektu. Jeśli dobrze rozumiem, problem polega na tym, że nie jest deterministyczny i za każdym razem zwraca nową wartość.DateTime.New
jest statyczny i dlatego nie można oczekiwać, że będzie reprezentował stan obiektu.Nie sądzę, aby istniała odpowiedź na to pytanie niezależnie od języka, ponieważ to, co stanowi „właściwość”, jest pytaniem specyficznym dla danego języka, a to, czego oczekuje osoba wywołująca „właściwość”, jest również pytaniem specyficznym dla danego języka. Myślę, że najbardziej owocnym sposobem na myślenie o tym jest myślenie o tym, jak to wygląda z punktu widzenia dzwoniącego.
W języku C # właściwości wyróżniają się tym, że są (konwencjonalnie) pisane wielkimi literami (jak metody), ale nie zawierają nawiasów (jak zmienne instancji publicznej). Jeśli widzisz następujący kod, brak dokumentacji, czego oczekujesz?
Jako względny nowicjusz w języku C #, ale czytający Wytyczne użytkowania nieruchomości firmy Microsoft , spodziewałbym
Reciprocal
się między innymi:Heading
klasyReciprocalChanged
wydarzenieSpośród tych założeń (3) i (4) są prawdopodobnie poprawne (zakładając, że
Heading
jest to niezmienny typ wartości, jak w odpowiedzi Ewana ), (1) jest dyskusyjny, (2) jest nieznany, ale także dyskusyjny, a (5) jest mało prawdopodobne mają sens semantyczny (chociaż cokolwiek ma nagłówek, może byćHeadingChanged
zdarzeniem). To sugeruje mi, że w API C # „pobierz lub oblicz wzajemność” nie powinno być implementowane jako właściwość, ale zwłaszcza jeśli obliczenia są tanie iHeading
są niezmienne, jest to przypadek graniczny.(Zauważ jednak, że żaden z tych problemów nie ma nic wspólnego z tym, czy wywołanie właściwości tworzy nową instancję , nawet (2). Tworzenie obiektów w CLR samo w sobie jest dość tanie).
W Javie właściwości są konwencją nazewnictwa metod. Jeśli widzę
moje oczekiwania są podobne do powyższych (jeśli mniej wyraźnie określone): oczekuję, że połączenie będzie tanie, idempotentne i pozbawione skutków ubocznych. Jednak poza ramą JavaBeans koncepcja „właściwości” nie jest tak bardzo znacząca w Javie, a zwłaszcza, gdy rozważa się niezmienną właściwość bez odpowiadającej jej
setReciprocal()
,getXXX()
konwencja jest obecnie nieco staromodna. Od Effective Java , drugie wydanie (już ponad osiem lat):Zatem we współczesnym, bardziej płynnym API spodziewałbym się zobaczyć
- co ponownie sugeruje, że wywołanie jest tanie, idempotentne i nie ma skutków ubocznych, ale nie powiedziałoby nic o tym, czy wykonywane jest nowe obliczenie, czy tworzony jest nowy obiekt. To jest w porządku; w dobrym API, nie powinno mnie to obchodzić.
W Ruby nie ma czegoś takiego jak własność. Istnieją „atrybuty”, ale jeśli widzę
Nie mam bezpośredniego sposobu, aby dowiedzieć się, czy uzyskuję dostęp do zmiennej instancji
@reciprocal
zaattr_reader
pomocą prostej metody akcesorium, czy też wywołuję metodę, która wykonuje kosztowne obliczenia. Jednak fakt, że nazwa metody jest rzeczownikiem prostym, zamiast powiedziećcalcReciprocal
, sugeruje ponownie, że wywołanie jest co najmniej tanie i prawdopodobnie nie ma skutków ubocznych.W Scali konwencja nazewnictwa mówi, że metody z efektami ubocznymi przyjmują nawiasy, a metody bez nich nie, ale
może być jednym z:
(Zauważ, że Scala dopuszcza różne rzeczy, które mimo wszystko są zniechęcane przez przewodnik po stylu . To jedna z moich głównych irytacji ze Scali.)
Brak nawiasów mówi mi, że połączenie nie ma skutków ubocznych; nazwa ponownie sugeruje, że połączenie powinno być stosunkowo tanie. Poza tym nie obchodzi mnie, w jaki sposób zyskuje mi wartość.
W skrócie: poznaj język, którego używasz, i dowiedz się, jakie oczekiwania przyniosą inni programiści w interfejsie API. Cała reszta to szczegół implementacji.
źródło
Jak powiedzieli inni, zwracanie instancji tej samej klasy jest dość powszechnym wzorcem.
Nazewnictwo powinno iść w parze z konwencjami nazewnictwa języka.
Na przykład w Javie prawdopodobnie spodziewałbym się, że zostanie wywołany
getReciprocal();
Biorąc to pod uwagę, rozważę obiekty zmienne vs niezmienne .
W przypadku tych niezmiennych rzeczy są dość łatwe i nie zaszkodzi, jeśli zwrócisz ten sam przedmiot, czy nie.
W przypadku zmiennych może to być bardzo przerażające.
Teraz do czego odnosi się b? Odwrotność pierwotnej wartości? Czy odwrotność zmienionego? To może być zbyt krótki przykład, ale rozumiesz.
W takich przypadkach szukaj lepszego nazewnictwa, które informuje o tym, co się dzieje, na przykład
createReciprocal()
może być lepszym wyborem.Ale tak naprawdę zależy to również od kontekstu.
źródło
getReciprocal()
, ponieważ to „brzmi” jak „normalny” getter stylu JavaBeans. Preferuj „createXXX ()” lub ewentualnie „obliczyćXXX ()”, które wskazują lub sugerują innym programistom, że dzieje się coś innegoW trosce o pojedynczą odpowiedzialność i przejrzystość chciałbym mieć ReverseHeadingFactory, który wziąłby obiekt i zwrócił jego odwrotność. Ułatwiłoby to wyjaśnienie, że zwracany jest nowy obiekt, i oznaczałoby, że kod generujący odwrotność został zamknięty w innym kodzie.
źródło
To zależy. Zwykle oczekuję, że właściwości zwrócą rzeczy, które są częścią instancji, więc zwrócenie innej instancji tej samej klasy byłoby nieco niezwykłe.
Ale na przykład klasa łańcuchowa może mieć właściwości „firstWord”, „lastWord” lub jeśli obsługuje Unicode „firstLetter”, „lastLetter”, które byłyby obiektami pełnego ciągu - zwykle mniejszymi.
źródło
Ponieważ pozostałe odpowiedzi dotyczą części Właściwość, zajmę się twoim drugim tematem
reciprocal
:Nie używaj niczego innego niż
reciprocal
. Jest to jedyny poprawny termin na to, co opisujesz (i jest to termin formalny). Nie pisz niepoprawnego oprogramowania, aby uchronić programistów przed nauczeniem się domeny, w której pracują. W nawigacji terminy mają bardzo konkretne znaczenie i używanie czegoś tak pozornie nieszkodliwego, jakreverse
iopposite
może prowadzić do zamieszania w niektórych kontekstach.źródło
Logicznie nie, nie jest to właściwość nagłówka. Gdyby tak było, można by również powiedzieć, że ujemna liczba jest własnością tej liczby, i szczerze mówiąc, że „własność” straci wszelkie znaczenie, prawie wszystko byłoby własnością.
Odwrotność jest czystą funkcją nagłówka, tak samo jak ujemna liczba jest czystą funkcją tej liczby.
Zasadniczo, jeśli ustawienie nie ma sensu, prawdopodobnie nie powinno być własnością. Ustawienie to nadal może być oczywiście zabronione, ale dodanie settera powinno być teoretycznie możliwe i sensowne.
Teraz, w niektórych językach, sensowne może być posiadanie go jako właściwości określonej w kontekście tego języka, jeśli przynosi pewne korzyści wynikające z mechaniki tego języka. Na przykład, jeśli chcesz przechowywać go w bazie danych, prawdopodobnie rozsądnym rozwiązaniem jest ustawienie go jako właściwości, jeśli pozwala on na automatyczną obsługę przez środowisko ORM. A może jest to język, w którym nie ma rozróżnienia między właściwością a parametryczną funkcją składową (nie znam takiego języka, ale jestem pewien, że istnieją). Następnie projektant interfejsu API i dokumentator muszą rozróżnić funkcje i właściwości.
Edycja: W szczególności w języku C #, przyjrzałbym się istniejącym klasom, zwłaszcza klasowym.
BigInteger
iComplex
struktury. Ale są to struktury i mają tylko funkcje statyczne, a wszystkie właściwości są innego rodzaju. Więc nie ma wiele pomocy w projektowaniuHeading
klasy.Vector
klasę , która ma bardzo mało właściwości i wydaje się być bardzo zbliżona do twojejHeading
. Jeśli przejdziesz przez to, będziesz miał funkcję wzajemności, a nie właściwość.Możesz przyjrzeć się bibliotekom faktycznie używanym przez kod lub istniejącym podobnym klasom. Staraj się być konsekwentny, zwykle jest to najlepszy sposób, jeśli jeden sposób nie jest wyraźnie właściwy, a drugi zły.
źródło
Rzeczywiście bardzo interesujące pytanie. Nie widzę naruszenia SOLID + DRY + KISS w tym, co proponujesz, ale nadal pachnie źle.
Metoda zwracająca instancję klasy nazywa się konstruktorem , prawda? więc tworzysz nową instancję z metody innej niż konstruktor. To nie koniec świata, ale jako klient nie spodziewałbym się tego. Odbywa się to zwykle w szczególnych okolicznościach: fabrycznej lub, czasem, statycznej metodzie tej samej klasy (przydatne dla singletonów i dla ... niezbyt obiektowych programistów?)
Plus: co się stanie, jeśli wywołasz
getReciprocal()
zwrócony obiekt? otrzymujesz inną instancję klasy, która może być bardzo dokładną kopią pierwszej! A jeśli się odwołujeszgetReciprocal()
TEGO? Przedmioty rozkładają się na kimś?Ponownie: co jeśli wywołujący potrzebuje wartości wzajemnej (mam na myśli skalar)?
getReciprocal()->getValue()
? naruszamy Prawo Demeter za niewielkie zyski.źródło