Rozmawiam z moim współpracownikiem o tym, ile pracy może wykonać konstruktor. Mam klasę B, która wewnętrznie wymaga innego obiektu A. Obiekt A jest jednym z niewielu elementów, których klasa B potrzebuje do wykonania swojej pracy. Wszystkie jego publiczne metody zależą od wewnętrznego obiektu A. Informacje o obiekcie A są przechowywane w bazie danych, więc próbuję sprawdzić i uzyskać je, sprawdzając je w bazie danych w konstruktorze. Mój współpracownik zwrócił uwagę, że konstruktor nie powinien wykonywać dużo pracy poza przechwytywaniem parametrów konstruktora. Ponieważ wszystkie publiczne metody i tak zawiodłyby, gdyby obiekt A nie został znaleziony przy użyciu danych wejściowych do konstruktora, argumentowałem, że zamiast zezwalać na utworzenie instancji i późniejsze niepowodzenie, w rzeczywistości lepiej jest rzucić ją wcześniej w konstruktorze.
Co myślą inni? Używam C #, jeśli to robi jakąkolwiek różnicę.
Czytanie Czy jest jakiś powód, aby wykonywać całą pracę obiektu w konstruktorze? zastanawiam się, czy pobranie obiektu A przez przejście do bazy danych jest częścią „każdej innej inicjalizacji niezbędnej do przygotowania obiektu do użycia”, ponieważ jeśli użytkownik przekaże konstruktorowi nieprawidłowe wartości, nie byłbym w stanie użyć żadnej z jego publicznych metod.
Konstruktory powinny utworzyć instancję pól obiektu i wykonać dowolną inną inicjalizację niezbędną do przygotowania obiektu do użycia. Ogólnie oznacza to, że konstruktory są małe, ale istnieją scenariusze, w których byłoby to znaczną ilość pracy.
źródło
Odpowiedzi:
Twoje pytanie składa się z dwóch całkowicie oddzielnych części:
Czy powinienem zgłosić wyjątek od konstruktora, czy powinienem mieć metodę niepowodzenia?
Jest to wyraźne zastosowanie zasady szybkiego działania. Awarie konstruktora są znacznie łatwiejsze do debugowania w porównaniu z koniecznością znalezienia przyczyny niepowodzenia metody. Na przykład instancja może zostać już utworzona z innej części kodu i podczas wywoływania metod występują błędy. Czy to oczywiste, że obiekt został utworzony źle? Nie.
Jeśli chodzi o problem „zawiń połączenie w try / catch”. Wyjątki mają być wyjątkowe . Jeśli wiesz, że jakiś kod zgłosi wyjątek, nie pakujesz go w try / catch, ale sprawdzasz parametry tego kodu przed wykonaniem kodu, który może wygenerować wyjątek. Wyjątki mają na celu jedynie zapewnienie, że system nie przejdzie w stan nieprawidłowy. Jeśli wiesz, że niektóre parametry wejściowe mogą prowadzić do nieprawidłowego stanu, upewnij się, że parametry te nigdy się nie zdarzają. W ten sposób musisz spróbować try / catch tylko w miejscach, w których możesz logicznie obsłużyć wyjątek, który zwykle znajduje się na granicy systemu.
Czy mogę uzyskać dostęp do „innych części systemu, takich jak DB” z konstruktora.
Myślę, że jest to sprzeczne z zasadą najmniejszego zdziwienia . Niewiele osób oczekiwałoby od konstruktora dostępu do bazy danych. Więc nie, nie powinieneś tego robić.
źródło
Ugh.
Konstruktor powinien robić jak najmniej - problem polega na tym, że zachowanie wyjątków w konstruktorach jest niezręczne. Bardzo niewielu programistów wie, jakie jest właściwe zachowanie (w tym scenariusze dziedziczenia), a zmuszanie użytkowników do próbowania / wychwytywania każdej pojedynczej instancji jest ... w najlepszym razie bolesne.
Są to dwie części: w konstruktorze i przy użyciu konstruktora. W konstruktorze jest to niewygodne, ponieważ nie można wiele zdziałać w przypadku wystąpienia wyjątku. Nie możesz zwrócić innego typu. Nie możesz zwrócić wartości null. Zasadniczo możesz powtórzyć wyjątek, zwrócić uszkodzony obiekt (zły konstruktor) lub (najlepiej) zastąpić część wewnętrzną rozsądnym ustawieniem domyślnym. Używanie konstruktora jest niewygodne, ponieważ wtedy twoje instancje (i instancje wszystkich typów pochodnych!) Mogą rzucać. Następnie nie można ich używać w inicjalizatorach członków. W końcu używasz wyjątków dla logiki, aby zobaczyć „czy moje dzieło się powiodło?”, Wykraczające poza czytelność (i kruchość) spowodowaną wszędzie przez try / catch.
Jeśli twój konstruktor robi rzeczy nietrywialne lub rzeczy, których można się spodziewać, że zawiedzie, rozważ
Create
metodę statyczną (z konstruktorem niepublicznym). Jest o wiele bardziej użyteczny, łatwiejszy do debugowania, a mimo to zapewnia w pełni skonstruowaną instancję, gdy odniesie sukces.źródło
Connection
obiekt możeCreateCommand
dla ciebie, abyś wiedział, że polecenie jest odpowiednio połączone.Konstruktor - równowaga złożoności metody jest rzeczywiście kwestią dyskusji na temat dobrego stylu. Dość często
Class() {}
używany jest pusty konstruktor , z metodąInit(..) {..}
bardziej skomplikowanych rzeczy. Wśród argumentów, które należy wziąć pod uwagę:Class x = new Class(); x.Init(..);
, częściowo w testach jednostkowych. Jednak nie tak dobre do czytania. Czasami po prostu umieszczam je w jednym wierszu kodu.źródło
init
metody jest to, że obsługa awarii jest o wiele łatwiejsza. W szczególności zwracana wartośćinit
metody może wskazywać, czy inicjalizacja się powiodła, a metoda może zapewnić, że obiekt jest zawsze w dobrym stanie.