Często napotykam ten problem, szczególnie w Javie, nawet jeśli uważam, że jest to ogólny problem z OOP. To znaczy: zgłoszenie wyjątku ujawnia problem projektowy.
Załóżmy, że mam klasę, która ma String name
pole i String surname
pole.
Następnie używa tych pól do utworzenia pełnego imienia i nazwiska osoby w celu wyświetlenia go na jakimś dokumencie, np. Na fakturze.
public void String name;
public void String surname;
public String getCompleteName() {return name + " " + surname;}
public void displayCompleteNameOnInvoice() {
String completeName = getCompleteName();
//do something with it....
}
Teraz chcę wzmocnić zachowanie mojej klasy, zgłaszając błąd, jeśli displayCompleteNameOnInvoice
zostanie wywołany przed przypisaniem nazwy. To dobry pomysł, prawda?
Mogę dodać kod wywołujący wyjątki do getCompleteName
metody. Ale w ten sposób naruszam „niejawną” umowę z użytkownikiem klasy; ogólnie, pobierający nie powinni zgłaszać wyjątków, jeśli ich wartości nie są ustawione. Ok, to nie jest standardowy getter, ponieważ nie zwraca pojedynczego pola, ale z punktu widzenia użytkownika rozróżnienie może być zbyt subtelne, aby o tym myśleć.
Albo mogę rzucić wyjątek z wnętrza displayCompleteNameOnInvoice
. Ale żeby to zrobić, powinienem przetestować bezpośrednio name
lub surname
pola, a tym samym naruszyłbym abstrakcję reprezentowaną przez getCompleteName
. Ta metoda odpowiada za sprawdzenie i utworzenie pełnej nazwy. Może nawet zdecydować, opierając decyzję na innych danych, że w niektórych przypadkach wystarczy surname
.
Tak więc jedyną możliwością wydaje się zmienić semantyczną metodę getCompleteName
na composeCompleteName
, co sugeruje bardziej „aktywne” zachowanie, a wraz z nim zdolność do zgłaszania wyjątku.
Czy to lepsze rozwiązanie projektowe? Zawsze szukam najlepszej równowagi między prostotą a poprawnością. Czy istnieje odniesienie do projektu dla tego problemu?
źródło
displayCompleteNameOnInvoice
może po prostu rzucić wyjątek, jeśligetCompleteName
zwracanull
, prawda?Odpowiedzi:
Nie pozwól, aby twoja klasa została zbudowana bez przypisania nazwy.
źródło
Odpowiedź dostarczona przez DeadMG prawie ją uwydatnia, ale pozwólcie, że sformułuję to nieco inaczej: Unikaj posiadania obiektów z nieprawidłowym stanem. Obiekt powinien być „cały” w kontekście wykonywanego zadania. Lub nie powinno istnieć.
Więc jeśli chcesz płynności, użyj czegoś takiego jak Wzorzec Konstruktora . Lub cokolwiek innego, co stanowi osobną reifikację obiektu w budowie w przeciwieństwie do „rzeczy rzeczywistej”, w której ten drugi ma zagwarantowany prawidłowy stan i jest tym, który faktycznie ujawnia operacje zdefiniowane dla tego stanu (np
getCompleteName
.).źródło
So if you want fluidity, use something like the builder pattern
- płynność lub niezmienność (chyba że w jakiś sposób to masz na myśli). Jeśli ktoś chce stworzyć niezmienne obiekty, ale musi odroczyć ustawianie niektórych wartości, wówczas wzorcem do naśladowania jest ustawianie ich jeden po drugim na obiekcie konstruktora i tworzenie niezmiennego dopiero po zebraniu wszystkich wartości wymaganych do poprawnego stan ...Nie masz obiektu ze złym stanem.
Konstruktor lub konstruktor ma na celu ustawienie stanu obiektu na coś spójnego i użytecznego. Bez tej gwarancji pojawiają się problemy z nieprawidłowo zainicjowanym stanem w obiektach. Problem ten się komplikuje, jeśli masz do czynienia z współbieżnością i coś może uzyskać dostęp do obiektu, zanim całkowicie go skonfigurujesz.
Pytanie w tej części brzmi zatem: „dlaczego pozwalasz, aby imię lub nazwisko było zerowe?” Jeśli nie jest to coś, co jest prawidłowe w klasie, aby działało poprawnie, nie pozwól na to. Poproś konstruktora lub konstruktora, który poprawnie zweryfikuje utworzenie obiektu, a jeśli nie jest poprawny, podnieś problem w tym momencie. Możesz także skorzystać z jednej z istniejących
@NotNull
adnotacji , aby pomóc w komunikowaniu, że „to nie może być zerowe” i wymusić to w kodowaniu i analizie.Dzięki tej gwarancji znacznie łatwiej jest zrozumieć kod i to, co robi, i nie trzeba rzucać wyjątków w dziwnych miejscach ani nadmiernie sprawdzać funkcji gettera.
Gettery, które robią więcej.
Jest na ten temat całkiem sporo. Masz ile logiki w getterach i co powinno być dozwolone w getterach i seterach? stąd i pobierający i ustawiający wykonujący dodatkową logikę na przepełnieniu stosu. Jest to problem, który pojawia się wielokrotnie w projektowaniu klas.
Rdzeń tego pochodzi z:
I masz rację. Nie powinni. Powinni wyglądać „głupio” na resztę świata i robić to, czego się spodziewają. Wprowadzenie zbyt dużej logiki prowadzi do problemów, w których naruszane jest prawo najmniejszego zdziwienia. I spójrzmy prawdzie w oczy, naprawdę nie chcesz owijać użytkowników,
try catch
ponieważ wiemy, jak bardzo lubimy to robić w ogóle.Są też sytuacje, w których musisz użyć
getFoo
metody, takiej jak framework JavaBeans, i gdy masz coś z wywołania EL oczekującego na uzyskanie komponentu bean (tak, że<% bar.foo %>
faktycznie wywołujegetFoo()
klasę - odkładając na bok „jeśli komponent powinien tworzyć kompozycję, czy powinien które należy pozostawić w polu widzenia? ”, ponieważ łatwo można wymyślić sytuacje, w których jedna lub druga może wyraźnie być właściwą odpowiedzią)Uświadom sobie również, że możliwe jest określenie danego gettera w interfejsie lub bycie częścią wcześniej ujawnionego publicznego interfejsu API dla klasy, która jest refaktoryzowana (poprzednia wersja miała właśnie „totalName”, która została zwrócona i przerwała się refaktoryzacja) na dwa pola).
Pod koniec dnia...
Rób to, co jest najłatwiejsze do uzasadnienia. Spędzisz więcej czasu na utrzymywaniu tego kodu niż na jego projektowaniu (chociaż im mniej czasu poświęcisz na projektowanie, tym więcej czasu poświęcisz na utrzymanie). Idealnym wyborem jest zaprojektowanie go w taki sposób, aby jego utrzymanie nie zajęło tyle czasu, ale nie siedź też i nie zastanawiaj się nad tym przez kilka dni - chyba że naprawdę jest to wybór projektowy, który będzie miał implikacje dni później .
Próba trzymania się semantycznej czystości istoty pobudzającej
private T foo; T getFoo() { return foo; }
spowoduje w pewnym momencie kłopoty. Czasami kod po prostu nie pasuje do tego modelu, a zniekształcenia, przez które przechodzimy, aby próbować go utrzymać w ten sposób, po prostu nie mają sensu ... i ostatecznie utrudniają zaprojektowanie.Zaakceptuj czasami, że ideały projektu nie mogą być zrealizowane tak, jak chcesz w kodzie. Wytyczne są wytycznymi, a nie kajdanami.
źródło
surname
lubname
wartość null jest poprawnym stanem dla obiektu, odpowiednio go obsłuż. Ale jeśli nie jest to poprawny stan powodujący, że metody w klasie generują wyjątki, należy zapobiegać temu, aby znajdował się w tym stanie od momentu, w którym został zainicjowany. Możesz ominąć niekompletne obiekty, ale problematyczne jest przekazywanie tych, które są nieprawidłowe. Jeśli nazwisko zerowe jest wyjątkowe, postaraj się temu zapobiec wcześniej niż później.Nie jestem facetem od Javy, ale wydaje się, że jest to zgodne z obiema ograniczeniami, które przedstawiłeś.
getCompleteName
nie zgłasza wyjątku, jeśli nazwy są niezainicjowane, idisplayCompleteNameOnInvoice
tak jest.źródło
getCompleteName()
nie musi dbać o to, czy właściwość jest rzeczywiście przechowywana, czy obliczana w locie.getCompleteName
, co jest ohydne.getCompleteName()
w pozostałej części klasy, a nawet w innych częściach programu. W niektórych przypadkach zwrotnull
może być dokładnie wymagany. Ale chociaż istnieje JEDNA metoda, której specyfikacją jest „wyrzucenie wyjątku w tym przypadku”, to DOKŁADNIE to, co powinniśmy zakodować.Wygląda na to, że nikt nie odpowiada na twoje pytanie.
W przeciwieństwie do tego, w co ludzie wierzą, „unikanie nieprawidłowych obiektów” nie jest często praktycznym rozwiązaniem.
Odpowiedź to:
Tak, powinno.
Prosty przykład:
FileStream.Length
w C # / .NET , który rzuca,NotSupportedException
jeśli plik nie jest widoczny.źródło
Masz całą sprawdzanie nazwy do góry nogami.
getCompleteName()
nie musi wiedzieć, które funkcje będą z niego korzystać. Zatem brak połączenianame
podczas rozmowy telefonicznejdisplayCompleteNameOnInvoice()
niegetCompleteName()
jest obowiązkiem.Ale
name
jest to wyraźny warunek wstępnydisplayCompleteNameOnInvoice()
. Dlatego powinien on przejąć odpowiedzialność za sprawdzenie stanu (lub powinien przekazać odpowiedzialność za sprawdzenie innej metody, jeśli wolisz).źródło