Dlaczego tworzenie instancji jest takie, jakie jest?

17

Nauczyłem się C # przez ostatnie sześć miesięcy i teraz zagłębiam się w Javę. Moje pytanie dotyczy tworzenia instancji (naprawdę w każdym języku), a więcej: zastanawiam się, dlaczego to zrobili w ten sposób. Weź ten przykład

Person Bob = new Person();

Czy istnieje przyczyna dwukrotnego określenia obiektu? Czy kiedykolwiek będzie something_else Bob = new Person()?

Wydaje się, że gdybym postępował zgodnie z konwencją, byłoby to bardziej:

int XIsAnInt;
Person BobIsAPerson;

A może jeden z nich:

Person() Bob;
new Person Bob;
new Person() Bob;
Bob = new Person();

Myślę, że jestem ciekawy, czy istnieje lepsza odpowiedź niż „po prostu tak się to robi”.

Jason Wohlgemuth
źródło
26
Co jeśli osoba jest podtypem LivingThing? Mógłbyś pisać LivingThing lt = new Person(). Poszukaj dziedziczenia i interfejsów.
xlecoustillier
2
Person Bobdeklaruje wywoływaną zmienną typu „referencja do PersonBob. new Person()tworzy Personobiekt. Odnośniki, zmienne i obiekty to trzy różne rzeczy!
user253751
5
Czy denerwuje Cię zwolnienie? Dlaczego więc nie napisać var bob = new Person();?
200_success
4
Person Bob();jest możliwe w C ++ i oznacza prawie to samo, coPerson Bob = Person();
user60561
3
@ user60561 nie, deklaruje, że funkcja nie przyjmuje argumentów i zwraca Person.
Nikolai

Odpowiedzi:

52

Czy kiedykolwiek istniałby coś Bob = new Person ()?

Tak, z powodu dziedziczenia. Gdyby:

public class StackExchangeMember : Person {}

Następnie:

Person bob = new StackExchangeMember();
Person sam = new Person();

Bob też jest osobą, a na golly, nie chce być traktowany inaczej niż ktokolwiek inny.

Co więcej, możemy wyposażyć Boba w super moce:

public interface IModerator { }
public class StackOverFlowModerator : StackExchangeMember, IModerator {}

IModerator bob = new StackOverFlowModerator();

Tak więc, na golly, nie będzie on traktowany inaczej niż jakikolwiek inny moderator. I lubi przekradać się po forum, aby wszyscy byli w kontakcie podczas incognito:

StackExchangeMember bob = new StackOverFlowModerator();

Następnie, gdy znajduje biedny 1. plakat, zrzuca pelerynę niewidzialności i rzuca się.

((StackOverFlowModerator) bob).Smite(sam);

A potem może zachowywać się niewinnie i tak dalej:

((Person) bob).ImNotMeanIWasJustInstantiatedThatWay();
radarbob
źródło
20
Byłoby to o wiele wyraźniejsze, gdyby nazwy obiektów były małymi literami.
Lekkość ściga się z Moniką
38
Dobry przykład specyfikacji; zły przykład dziedziczenia. Wszystkim innym osobom, które to czytają, nie próbuj rozwiązywać ról użytkowników za pomocą dziedziczenia.
Aaronaught
8
@Aaronaught jest poprawny. Nie twórz osobnych klas dla różnych typów ludzi. Użyj pola bitowego enum.
Cole Johnson
1
@Aaronaught Wszystko dobrze mówi, czego nie robić, ale nie jest bardzo pomocne, nie mówiąc, co ludzie powinni zrobić.
Pharap
5
@Pharap: Zrobiłem dokładnie to w kilku innych pytaniach . Prosta odpowiedź jest taka, że ​​użytkownicy (uwierzytelnianie / tożsamość) i polityka bezpieczeństwa (autoryzacja / uprawnienia) powinny być traktowane jako osobne obawy, a standardowe modele polityki bezpieczeństwa są oparte na rolach lub roszczeniach. Dziedziczenie jest bardziej przydatne do opisania obiektu, który faktycznie dokonuje uwierzytelnienia, np. Implementacja LDAP i implementacja SQL.
Aaronaught
34

Weźmy pierwszy wiersz kodu i sprawdźmy go.

Person Bob = new Person();

Pierwszy Personto specyfikacja typu. W języku C # możemy zrezygnować z tego, po prostu mówiąc

var Bob = new Person();

a kompilator wyprowadzi typ zmiennej Bob z wywołania konstruktora Person().

Ale możesz napisać coś takiego:

IPerson Bob = new Person();

W przypadku, gdy nie wypełniasz całej umowy API Person, ale tylko umowę określoną przez interfejs IPerson.

Robert Harvey
źródło
2
+1: Zrobię IPersonprzykład w moim kodzie, aby upewnić się, że przypadkowo nie użyję żadnych prywatnych metod podczas pisania kodu, który powinien być kopiowany / wklejalny do innej IPersonimplementacji.
Cort Ammon - Przywróć Monikę
@CortAmmon Myślę, że masz tam literówkę, najwyraźniej miałeś na myśli „pracę z polimorfizmem”, a nie kopiowanie / pastowanie na tym kodzie: D
Benjamin Gruenbaum
@BenjaminGruenbaum na pewno dla pewnej definicji polimorfizmu ;-)
Cort Ammon - Przywróć Monikę
@CortAmmon, jak przypadkowo nazwałbyś metodę prywatną ? Na pewno miałeś na myśli internal?
Cole Johnson
@ColeJohnson w obu kierunkach. Napisałem to myślenie o konkretnym przypadku, na który natknąłem się, gdzie privatema znaczenie: metody fabryczne, które są częścią instancji klasy, są tworzone. W mojej sytuacji dostęp do wartości prywatnych powinien być wyjątkiem, a nie normą. Pracuję nad kodem, który długo mnie przeżyje. Jeśli zrobię to w ten sposób, nie tylko raczej nie będę samodzielnie stosować żadnych prywatnych metod, ale kiedy następny programista skopiuje / wklei to kilkadziesiąt miejsc, a programista po tym skopiuje je, zmniejsza szanse, że ktoś zobaczy okazję stosować metody prywatne jako „normalne” zachowanie.
Cort Ammon - Przywróć Monikę
21
  1. Ta składnia jest prawie spuścizną po C ++, która, nawiasem mówiąc, ma zarówno:

    Person Bob;

    i

    Person *bob = new Bob();

    Pierwszy tworzy obiekt w bieżącym zakresie, drugi tworzy wskaźnik do obiektu dynamicznego.

  2. Zdecydowanie możesz something_else Bob = new Person()

    IEnumerable<int> nums = new List<int>(){1,2,3,4}

    Robisz tutaj dwie różne rzeczy, określając typ zmiennej lokalnej numsi mówisz, że chcesz utworzyć nowy obiekt typu „Lista” i umieścić go tam.

  3. C # w pewnym sensie zgadza się z tobą, ponieważ przez większość czasu typ zmiennej jest identyczny z tym, co wstawiasz, stąd:

    var nums = new List<int>();
  4. W niektórych językach starasz się nie podawać typów zmiennych jak w F # :

    let list123 = [ 1; 2; 3 ]
AK_
źródło
5
Prawdopodobnie dokładniej jest powiedzieć, że twój drugi przykład tworzy wskaźnik do nowego obiektu Boba. Sposób przechowywania rzeczy jest technicznie szczegółowym aspektem implementacji.
Robert Harvey
4
„Pierwszy tworzy lokalny obiekt na stosie, drugi tworzy obiekt na stercie”. O rany, już nie ta dezinformacja.
Lekkość ściga się z Moniką
@LightnessRacesinOrbit lepiej?
AK_
1
@AK_ Podczas gdy „dynamiczny” oznacza w zasadzie „stos” (gdy stos jest modelem pamięci używanym przez platformę), „automatyczny” różni się bardzo od „stosu”. Jeśli tak new std::pair<int, char>(), członkowie firsti secondpara mają automatyczny czas przechowywania, ale prawdopodobnie są przydzielani na stercie (jako członkowie obiektu dynamicznego przechowywania pair).
Przywróć Monikę
1
@AK_: Tak, te imiona sugerują dokładnie, co oznaczają, podczas gdy ten nonsens „stos” kontra „stos” nie . To jest cały punkt. To, że uważasz, że są mylące, tylko wzmacnia potrzebę ich nauczania, abyś nie polegał na niedokładnej / niepoprawnej terminologii tylko dlatego, że jest to znane!
Wyścigi lekkości z Moniką
3

Istnieje ogromna różnica między int xi Person bob. intTo intjest inti musi być zawsze inti nigdy nie może być nic innego niż int. Nawet jeśli nie zainicjujesz tego, intkiedy go deklarujesz ( int x;), nadal jest intustawiony na wartość domyślną.

Person bobJednak kiedy deklarujesz , istnieje duża elastyczność co do tego, do czego nazwa bobmoże się odnosić w danym momencie. Może odnosić się do Personlub może odnosić się do innej klasy, np. ProgrammerPochodzącej z Person; może nawet być null, nie odnosząc się do żadnego przedmiotu.

Na przykład:

  Person bob   = null;
  Person carol = new Person();
  Person ted   = new Programmer();
  Person alice = personFactory.functionThatReturnsSomeKindOfPersonOrNull();

Projektanci języka z pewnością mogliby stworzyć alternatywną składnię, która osiągnęłaby to samo, co Person carol = new Person()w przypadku mniejszej liczby symboli, ale nadal musieliby na to pozwolić Person carol = new Person() (lub wprowadzić dziwną regułę, czyniącą ten jeden z czterech powyższych przykładów nielegalnym). Bardziej zależało im na utrzymaniu prostoty języka niż na pisaniu wyjątkowo zwięzłego kodu. Mogło to wpłynąć na ich decyzję o niepodawaniu krótszej składni alternatywnej, ale w każdym razie nie było to konieczne i nie podali jej.

David K.
źródło
1

Te dwie deklaracje mogą być różne, ale często są takie same. Typowy, zalecany wzorzec w Javie wygląda następująco:

List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

Te zmienne listi mapsą uznane za pomocą interfejsów Listi Mapgdy kod instancję konkretne implementacje. W ten sposób reszta kodu zależy tylko od interfejsów i łatwo jest wybrać inne klasy implementacji do utworzenia, na przykład TreeMap, ponieważ reszta kodu nie może zależeć od żadnej części HashMapinterfejsu API, która znajduje się poza Mapinterfejsem.

Innym przykładem, w którym oba typy się różnią, jest metoda fabryczna, która wybiera określoną podklasę do utworzenia instancji, a następnie zwraca ją jako typ podstawowy, więc osoba dzwoniąca nie musi znać szczegółów implementacji, np. Wybór „polityki”.

Wnioskowanie typu może naprawić redundancję kodu źródłowego. Np. W Javie

List<String> listOne = Collections.emptyList();

zbuduje odpowiedni rodzaj Listy dzięki wnioskowaniu o typ i deklaracji

static <T> List<T> emptyList(); 

W niektórych językach wnioskowanie typu idzie dalej, np. W C ++

auto p = new Person();
Jerry101
źródło
BTW język Java ustanawia silną konwencję dotyczącą używania małych identyfikatorów, takich jak bob, nie Bob. Pozwala to uniknąć wielu niejasności, np. Package.Class vs. Class.variable.
Jerry101
... i dlatego masz clazz.
CVn
Nie, clazzjest używany, ponieważ classjest słowem kluczowym, więc nie może być użyty jako identyfikator.
Jerry101
... co nie byłoby problemem, gdyby konwencje nazewnictwa nie były takie, jakie są. Classjest doskonale poprawnym identyfikatorem.
cHao
... jakie są cLaSs, cLASSi cLASs.
el.pescado
1

Słowami laika:

  • Oddzielenie deklaracji od instancji pomaga oddzielić, kto używa obiektów od tego, kto je tworzy
  • Po wykonaniu tej czynności włącza się poliporfizm, ponieważ dopóki utworzony instancja jest podtypem typu deklaracji, cały kod używający zmiennej będzie działał
  • W językach o silnym typie musisz zadeklarować zmienną określającą jej typ, po prostu var = new Process()nie deklarujesz zmiennej jako pierwszej.
Tulains Córdova
źródło
0

Chodzi również o poziom kontroli nad tym, co się dzieje. Jeśli deklaracja obiektu / zmiennej automatycznie wywołuje konstruktor, na przykład if

Person somePerson;

był automatycznie taki sam jak

Person somePerson = new Person(blah, blah..);

wtedy nigdy nie byłoby możliwe użycie (na przykład) statycznych metod fabrycznych do tworzenia instancji obiektów zamiast domyślnych konstruktorów, to znaczy, że czasami nie chcesz wywoływać konstruktora dla nowej instancji obiektu.

Ten przykład wyjaśniono w „ Skutecznej Javie Joshua Blocha ” (dość ironicznie, jak w punkcie 1!)

David Scholefield
źródło
Jeśli chodzi o książki, zarówno moja książka C #, jak i książka Java pochodziły od Joyce Farrell. Właśnie to określił kurs. Uzupełniam też oba filmy YouTube na C # i Javie.
Jason Wohlgemuth,
Nie krytykowałem,
podałem