Czy możemy żyć bez konstruktorów?

25

Powiedzmy, że z jakiegoś powodu wszystkie obiekty są tworzone w ten sposób $ obj = CLASS :: getInstance (). Następnie wstrzykujemy zależności za pomocą ustawiaczy i przeprowadzamy inicjalizację początkową za pomocą $ obj-> initInstance (); Czy są jakieś realne problemy lub sytuacje, których nie można rozwiązać, jeśli w ogóle nie będziemy używać konstruktorów?

Ps powodem dla utworzenia obiektu w ten sposób jest to, że możemy zastąpić klasę wewnątrz getInstance () zgodnie z pewnymi regułami.

Pracuję w PHP, jeśli to ważne

Axel Foley
źródło
jaki język programowania?
komara
2
PHP (dlaczego to ma znaczenie?)
Axel Foley
8
Wygląda na to, że to, co chcesz zrobić, można osiągnąć za pomocą wzoru Factory.
superM
1
Dla przypomnienia: wspomniany patern fabryczny @ superM to jeden ze sposobów implementacji Inversion of Control (Dependency Injection).
Joachim Sauer
Czy nie jest to mniej więcej to, co javascript robi z obiektami?
Thijser

Odpowiedzi:

44

Powiedziałbym, że to poważnie utrudnia twoją przestrzeń projektową.

Konstruktory są doskonałym miejscem do inicjowania i sprawdzania przekazywanych parametrów. Jeśli nie możesz ich już do tego używać, inicjalizacja, obsługa stanu (lub oczywiste odrzucanie konstruktora „uszkodzonych” obiektów) staje się o wiele trudniejsze i częściowo niemożliwe.

Na przykład, jeśli każdy Fooobiekt potrzebuje, Frobnicatorto może sprawdzić w swoim konstruktorze, czy wartość Frobnicatorjest różna od zera. Jeśli usuniesz konstruktora, trudniej będzie to sprawdzić. Czy sprawdzasz w każdym punkcie, w którym byłby używany? W init()metodzie (skutecznie eksternalizującej metodę konstruktora)? Nigdy tego nie sprawdzasz i masz nadzieję na najlepsze?

Podczas mógłby prawdopodobnie nadal realizować wszystko (mimo wszystko, wciąż jesteś turing kompletne), niektóre rzeczy będzie dużo trudniej zrobić.

Osobiście sugerowałbym przyjrzenie się wstrzykiwaniu zależności / inwersji kontroli . Techniki te umożliwiają także przełączanie konkretnych klas implementacyjnych, ale nie uniemożliwiają pisania / używania konstruktorów.

Joachim Sauer
źródło
Co miałeś na myśli, mówiąc „wprost”, odmawiając konstruktorowi „zepsutych” obiektów?
Geek
2
@Geek: konstruktor może sprawdzić swój argument i zdecydować, czy spowoduje to powstanie obiektu roboczego (na przykład, jeśli Twój obiekt tego potrzebuje HttpClient, sprawdza, czy parametr ten nie ma wartości null). A jeśli te ograniczenia nie zostaną spełnione, może to spowodować wyjątek. Nie jest to tak naprawdę możliwe w przypadku podejścia konstruowania i ustawiania wartości.
Joachim Sauer
1
Myślę, że OP opisywał eksternalizację konstruktora w środku init(), co jest całkowicie możliwe - chociaż to tylko nakłada większe obciążenie związane z utrzymaniem.
Peter
26

2 zalety dla konstruktorów:

Konstruktory umożliwiają atomowe wykonanie kroków konstrukcyjnych obiektu.

Mógłbym uniknąć konstruktora i do wszystkiego użyć seterów, ale co z obowiązkowymi właściwościami, jak sugerował Joachim Sauer? W przypadku konstruktora obiekt posiada własną logikę konstrukcyjną, aby upewnić się, że nie ma niepoprawnych instancji takiej klasy .

Jeśli utworzenie instancji Foowymaga ustawienia 3 właściwości, konstruktor może odwołać się do wszystkich 3, zweryfikować je i zgłosić wyjątek, jeśli są one nieprawidłowe.

Kapsułkowanie

Opierając się wyłącznie na ustawiaczach, ciężar spoczywający na użytkowniku obiektu spoczywa na jego prawidłowym zbudowaniu. Mogą istnieć różne kombinacje właściwości, które są prawidłowe.

Na przykład każde Foowystąpienie wymaga wystąpienia Barjako właściwości barlub wystąpienia BarFinderjako właściwości barFinder. Może używać jednego z nich. Możesz utworzyć konstruktor dla każdego poprawnego zestawu parametrów i w ten sposób egzekwować konwencje.

Logika i semantyka obiektów żyją w samym obiekcie. To dobre kapsułkowanie.

Brandon
źródło
15

Tak, możesz żyć bez konstruktorów.

Pewnie, możesz skończyć z dużą ilością zduplikowanego kodu płyty kotła. A jeśli twoja aplikacja ma jakąkolwiek skalę, prawdopodobnie spędzisz dużo czasu próbując zlokalizować źródło problemu, gdy ten kod bojlera nie jest konsekwentnie używany w całej aplikacji.

Ale nie, nie potrzebujesz „własnych” konstruktorów. Oczywiście nie potrzebujesz też ściśle klas i obiektów.

Teraz, jeśli Twoim celem jest użycie jakiegoś fabrycznego wzorca do tworzenia obiektów, nie wyklucza to wzajemnie korzystania z konstruktorów podczas inicjowania obiektów.

Grandmaster B.
źródło
Absolutnie. Na początku można nawet żyć bez klas i przedmiotów.
JensG
5
Wszyscy pozdrawiają potężnego asemblera!
Davor Ždralo
9

Zaletą używania konstruktorów jest to, że ułatwiają one zapewnienie, że nigdy nie będziesz mieć nieprawidłowego obiektu.

Konstruktor daje możliwość ustawienia wszystkich zmiennych składowych obiektu na prawidłowy stan. Gdy upewnisz się, że żadna metoda mutatora nie może zmienić obiektu w nieprawidłowy stan, nigdy nie będziesz mieć nieprawidłowego obiektu, co uratuje cię od wielu błędów.

Ale gdy nowy obiekt jest tworzony w niepoprawnym stanie i musisz wywołać niektóre obiekty ustawiające, aby wprowadzić go w prawidłowy stan, w którym można go użyć, ryzykujesz, że konsument klasy zapomni o wywołaniu tych ustawień lub wywoła je niepoprawnie i kończy się to nieprawidłowym obiektem.

Obejściem może być tworzenie obiektów tylko metodą fabryczną, która sprawdza poprawność każdego tworzonego obiektu przed zwróceniem go do obiektu wywołującego.

Philipp
źródło
3

$ obj = CLASS :: getInstance (). Następnie wstrzykujemy zależności za pomocą ustawiaczy i przeprowadzamy inicjalizację początkową za pomocą $ obj-> initInstance ();

Myślę, że utrudniasz to, niż musi być. Możemy dobrze wstrzykiwać zależności za pomocą konstruktora - a jeśli masz ich dużo, po prostu użyj struktury przypominającej słownik, aby określić, których chcesz użyć:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

Wewnątrz konstruktora możesz zapewnić spójność w następujący sposób:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args może być następnie użyty do ustawienia prywatnych członków w razie potrzeby.

Gdy zostanie to wykonane całkowicie w obrębie konstruktora, nigdy nie wystąpi stan pośredni, który $objistnieje, ale nie zostanie zainicjowany, tak jak w systemie opisanym w pytaniu. Lepiej unikać takich stanów pośrednich, ponieważ nie można zagwarantować, że obiekt będzie zawsze używany poprawnie.

Izkata
źródło
2

Tak naprawdę myślałem o podobnych rzeczach.

Pytanie, które zadałem, brzmiało: „Co robi konstruktor i czy można to zrobić inaczej?” Doszedłem do tych wniosków:

  • Zapewnia to zainicjowanie niektórych właściwości. Akceptując je jako parametry i ustawiając je. Ale kompilator może to łatwo wymusić. Po prostu dodając adnotacje do pól lub właściwości jako „wymagane”, kompilator sprawdzi podczas tworzenia instancji, czy wszystko jest ustawione poprawnie. Wezwanie do utworzenia instancji prawdopodobnie byłoby takie samo, po prostu nie byłoby żadnej metody konstruktora.

  • Zapewnia, że ​​właściwości są prawidłowe. Można to łatwo osiągnąć przez stwierdzenie warunku. Znowu po prostu adnotacje właściwości z prawidłowymi warunkami. Niektóre języki już to robią.

  • Bardziej złożona logika budowy. Nowoczesne wzorce nie zalecają tego w konstruktorze, ale proponują użycie specjalistycznych metod lub klas fabrycznych. Zatem użycie konstruktorów w tym przypadku jest minimalne.

Aby odpowiedzieć na twoje pytanie: Tak, wierzę, że jest to możliwe. Wymagałoby to jednak dużych zmian w projekcie języka.

I właśnie zauważyłem, że moja odpowiedź jest całkiem OT.

Euforyk
źródło
2

Tak, możesz zrobić prawie wszystko bez użycia konstruktorów, ale wyraźnie marnuje to zalety języków programowania obiektowego.

We współczesnych językach (porozmawiam tutaj o C #, w którym programuję) możesz ograniczyć części kodu, które można uruchomić tylko w konstruktorze. Dzięki temu możesz uniknąć niezdarnych błędów. Jedną z takich rzeczy jest tylko modyfikator:

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Zgodnie z zaleceniami Joachima Sauera, zamiast używać Factorywzoru, przeczytaj o Dependency Injection. Polecam przeczytanie Dependency Injection w .NET autorstwa Mark Seemann .

SOReader
źródło
1

Utwórz instancję obiektu z typem w zależności od wymagań, jest to absolutnie możliwe. Może to być sam obiekt, wykorzystujący zmienne globalne systemu, aby zwrócić określony typ.

Jednak mają w kodzie klasę, która może być „Wszystko” to koncepcja typu dynamicznego . Osobiście uważam, że takie podejście powoduje niespójność w kodzie, sprawia, że ​​kompleksy testowe * i „przyszłość staje się niepewna” w odniesieniu do przebiegu proponowanej pracy.

* Mam na myśli fakt, że testy muszą uwzględniać najpierw typ, a następnie wynik, który chcesz osiągnąć. Następnie tworzysz duży test zagnieżdżony.

marcocs
źródło
1

Aby zrównoważyć niektóre inne odpowiedzi, twierdząc:

Konstruktor daje możliwość ustawienia wszystkich zmiennych składowych obiektu na prawidłowy stan ... nigdy nie będziesz mieć niepoprawnego obiektu, co uratuje cię od wielu błędów.

i

W przypadku konstruktora obiekt posiada własną logikę konstrukcyjną, aby upewnić się, że nie ma niepoprawnych instancji takiej klasy.

Takie stwierdzenia czasami sugerują założenie, że:

Jeśli klasa ma konstruktor, który do czasu wyjścia zamienia obiekt w prawidłowy stan, a żadna z metod klasy nie mutuje tego stanu, aby unieważnić go, wówczas kod poza klasą nie może wykryć obiektu tej klasy w niepoprawnym stanie.

Ale to nie do końca prawda. Większość języków nie ma reguły przeciw konstruktorowi przekazującemu this( selflub jakiemukolwiek jemu język to nazwie) kod zewnętrzny. Taki konstruktor całkowicie przestrzega powyższej reguły, ale ryzykuje narażeniem częściowo skonstruowanych obiektów na kod zewnętrzny. To drobiazg, ale łatwo go przeoczyć.

Daniel Earwicker
źródło
0

Jest to nieco anegdotyczne, ale zwykle rezerwuję konstruktory na stan niezbędny do tego, aby obiekt był kompletny i użyteczny. Z wyjątkiem iniekcji setera, po uruchomieniu konstruktora mój obiekt powinien być w stanie wykonać wymagane od niego zadania.

Wszystko, co można odroczyć, pomijam w konstruktorze (przygotowuję wartości wyjściowe itp.). Przy takim podejściu nie używam konstruktorów do niczego, ale sensowne jest wstrzykiwanie zależności.

Ma to również dodatkową zaletę polegającą na okablowaniu procesu projektowania mentalnego, aby nie robić nic przedwcześnie. Nie zainicjujesz ani nie wykonasz logiki, która może nigdy nie zostać użyta, ponieważ w najlepszym razie wszystko, co robisz, to podstawowa konfiguracja dla przyszłych prac.

Omega
źródło