OOP Styl kodowania: inicjować wszystko w konstruktorze?

14

Nadal uważam się za programistę-ucznia, więc zawsze szukam „lepszego” sposobu na typowe programowanie. Dzisiaj mój współpracownik argumentował, że mój styl kodowania robi niepotrzebną pracę i chcę usłyszeć opinie innych. Zazwyczaj, gdy projektuję klasę w języku OOP (zwykle C ++ lub Python), dzieliłem inicjalizację na dwie różne części:

class MyClass1 {
public:
    Myclass1(type1 arg1, type2 arg2, type3 arg3);
    initMyClass1();
private:
    type1 param1;
    type2 param2;
    type3 param3;
    type4 anotherParam1;
};

// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
    : param1(arg1)
    , param2(arg2)
    , param3(arg3)
    {}

// Any other procedure is done in a separate initialization function 
MyClass1::initMyClass1() {
    // Validate input arguments before calculations
    if (checkInputs()) {
    // Do some calculations here to figure out the value of anotherParam1
        anotherParam1 = someCalculation();
    } else {
        printf("Something went wrong!\n");
        ASSERT(FALSE)
    }
}

(lub odpowiednik w języku Python)

class MyClass1:

    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        #optional
        self.anotherParam1 = None

    def initMyClass1():
        if checkInputs():
            anotherParam1 = someCalculation()
        else:
            raise "Something went wrong!"

Jakie jest twoje zdanie na temat tego podejścia? Czy powinienem powstrzymać się od podziału procesu inicjalizacji? Pytanie nie ogranicza się tylko do C ++ i Pythona, a odpowiedzi na inne języki są również mile widziane.

Caladbolgll
źródło
1
powiązane: stackoverflow.com/questions/3127454/…
Doc Brown,
2
patrz także: stackoverflow.com/questions/33124542/…
Doc Brown
1
A w przypadku Pythona: stackoverflow.com/questions/20661448/…
Doc Brown
Dlaczego zazwyczaj to robisz? Nawyk? Czy kiedykolwiek miałeś powód, aby to zrobić?
JeffO
@JeffO Mam ten nawyk, kiedy pracowałem nad tworzeniem GUI z biblioteką MFC. Większość klas związanych z interfejsem użytkownika, takich jak CApp, CWindow, CDlg i tak dalej, ma funkcje OnInit (), które można zastąpić, i które odpowiadają na ich komunikaty zwrotne.
Caladbolgll,

Odpowiedzi:

28

Chociaż czasami jest to problematyczne, istnieje wiele zalet inicjowania wszystkiego w konstruktorze:

  1. Jeśli wystąpi błąd, dzieje się to tak szybko, jak to możliwe i najłatwiej zdiagnozować. Na przykład, jeśli null jest niepoprawną wartością argumentu, przetestuj konstruktor i zakończ niepowodzeniem.
  2. Obiekt jest zawsze w poprawnym stanie. Współpracownik nie może popełnić błędu i zapomnieć zadzwonić, initMyClass1()ponieważ go nie ma . „Najtańsze, najszybsze i najbardziej niezawodne komponenty to te, których nie ma”.
  3. Jeśli ma to sens, obiekt można uczynić niezmiennym, co ma wiele zalet.
użytkownik949300
źródło
2

Pomyśl o abstrakcji, którą udostępniasz swoim użytkownikom.

Po co dzielić coś, co można zrobić jednym strzałem na dwa?

Dodatkowa inicjalizacja to coś dodatkowego dla programistów używających interfejsu API do zapamiętania i zapewnia więcej błędów, jeśli nie zrobią tego dobrze, ale jaką wartość mają dla nich to dodatkowe obciążenie?

Chcesz zapewnić śmiertelnie proste, łatwe w użyciu, trudne do popełnienia błędne abstrakcje. Programowanie jest wystarczająco trudne bez zbędnych rzeczy do zapamiętania / obręczy do przeskoczenia. Chcesz, aby użytkownicy interfejsu API (nawet jeśli używasz własnego interfejsu API) wpadli w pułapkę sukcesu .

Erik Eidt
źródło
1

Zainicjuj wszystko oprócz obszaru dużych zbiorów danych. Narzędzia analizy statycznej oznaczą pola niezainicjowane w konstruktorze. Jednak najbardziej produktywnym / bezpiecznym sposobem jest posiadanie wszystkich zmiennych składowych z domyślnymi konstruktorami i jawne inicjowanie tylko tych, które wymagają inicjalizacji innej niż domyślna.

zzz777
źródło
0

Są przypadki, w których obiekt ma wiele inicjalizacji, które można podzielić na dwie kategorie:

  1. Atrybuty, które są niezmienne lub nie wymagają resetowania.

  2. Atrybuty, które mogą wymagać przywrócenia oryginalnych wartości (lub wartości szablonowych) na podstawie pewnych warunków po spełnieniu swojego zadania, rodzaj miękkiego resetu. np. połączenia w puli połączeń.

Tutaj drugą część inicjalizacji utrzymywaną w osobnej funkcji, powiedzmy InitialiseObject (), można wywołać w ctor.

Tę samą funkcję można wywołać później, jeśli wymagany jest miękki reset, bez konieczności odrzucania i ponownego tworzenia obiektu.

Ramakant
źródło
0

Jak powiedzieli inni, ogólnie dobrym pomysłem jest zainicjowanie w konstruktorze.

Istnieją jednak powody, dla których może to nie mieć zastosowania w określonych przypadkach.

Obsługa błędów

W wielu językach jedynym sposobem sygnalizowania błędu w konstruktorze jest zgłoszenie wyjątku.

Jeśli Twoja inicjalizacja ma uzasadnioną szansę na zgłoszenie błędu, np. Dotyczy IO lub jego parametry mogą być wprowadzane przez użytkownika, jedynym dostępnym mechanizmem jest zgłoszenie wyjątku. W niektórych przypadkach może nie być to pożądane i bardziej sensowne może być rozdzielenie kodu podatnego na błędy na osobną funkcję inicjalizacji.

Prawdopodobnie najczęstszym tego przykładem jest C ++, jeśli standardem projektu / organizacji jest wyłączenie wyjątków.

Maszyna stanowa

Jest tak w przypadku modelowania obiektu, który ma jawne przejścia stanu. Na przykład plik lub gniazdo, które można otworzyć i zamknąć.

W takim przypadku konstrukcja (i usuwanie) obiektów często zajmuje się tylko atrybutami zorientowanymi na pamięć (nazwa pliku, port itp.). Będą wtedy funkcje do specyficznego zarządzania zmianami stanu, np. Otwieranie, zamykanie, które są skutecznie funkcjami inicjalizacji i rozrywania.

Zaletą jest obsługa błędów, jak wyżej, ale może też istnieć potrzeba oddzielenia konstrukcji od inicjalizacji (powiedzmy, że budujesz wektor plików i otwierasz je asynchronicznie).

Wadą, jak powiedzieli inni, jest to, że obciążasz teraz zarządzanie stanem użytkownikiem twoich klas. Jeśli potrafisz poradzić sobie z samą konstrukcją, możesz, powiedzmy, użyć RAII, aby zrobić to automatycznie.

Alex
źródło