Perché la creazione dell'istanza è così com'è?


17

Ho imparato C # nel corso degli ultimi sei mesi circa e ora sto approfondendo Java. La mia domanda riguarda la creazione di istanze (in entrambe le lingue, in realtà) ed è più di: mi chiedo perché l'hanno fatto in quel modo. Prendi questo esempio

Person Bob = new Person();

C'è una ragione per cui l'oggetto viene specificato due volte? Ci sarebbe mai stato un something_else Bob = new Person()?

Sembrerebbe che se stavo seguendo dalla convention sarebbe più simile a:

int XIsAnInt;
Person BobIsAPerson;

O forse uno di questi:

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

Suppongo di essere curioso di sapere se esiste una risposta migliore di "è così che si fa".


26
Cosa succede se Person è un sottotipo di LivingThing? Potresti scrivere LivingThing lt = new Person(). Cerca eredità e interfacce.
xlecoustillier,

2
Person Bobdichiara una variabile di tipo "riferimento a Person" chiamata Bob. new Person()crea un Personoggetto. Riferimenti, variabili e oggetti sono tre cose diverse!
user253751

5
Sei infastidito dalla ridondanza? Allora perché non scrivere var bob = new Person();?
200_successo

4
Person Bob();è possibile in C ++ e significa quasi la stessa cosa diPerson Bob = Person();
user60561

3
@ user60561 no, dichiara una funzione che non accetta argomenti e che restituisce Person.
Nikolai,

Risposte:


52

Ci sarebbe mai stato qualcosa di Bob = new Person ()?

Sì, a causa dell'eredità. Se:

public class StackExchangeMember : Person {}

Poi:

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

Anche Bob è una persona e, per astuzia, non vuole essere trattato in modo diverso da chiunque altro.

Inoltre, potremmo dotare Bob di superpoteri:

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

IModerator bob = new StackOverFlowModerator();

E così, per astuzia, non sopporta di essere trattato in modo diverso rispetto a qualsiasi altro moderatore. E gli piace intrufolarsi nel forum per mantenere tutti in linea mentre si è in incognito:

StackExchangeMember bob = new StackOverFlowModerator();

Poi, quando trova qualche povero 1 ° poster, si toglie il mantello dell'invisibilità e si lancia.

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

E poi può agire in modo innocente e cose successive:

((Person) bob).ImNotMeanIWasJustInstantiatedThatWay();

20
Ciò sarebbe molto più chiaro se hai inserito i nomi dei tuoi oggetti in lettere minuscole.
Lightness Races con Monica

38
Buon esempio delle specifiche; cattivo esempio di eredità. Per chiunque altro legga questo, per favore, non provare a risolvere i ruoli degli utenti usando l'ereditarietà.
Aaronaught l'

8
@Aaronaught è corretto. Non creare classi separate per diversi tipi di persone. Usa un bitfield enum.
Cole Johnson,

1
@Aaronaught Va tutto molto bene a dire cosa non fare, ma non è molto utile senza dire cosa dovrebbe fare invece la gente.
Pharap

5
@Pharap: ho fatto esattamente questo, in molte altre domande . La semplice risposta è che gli utenti (autenticazione / identità) e la politica di sicurezza (autorizzazione / autorizzazioni) devono essere trattati come preoccupazioni separate e che i modelli standard per la politica di sicurezza sono basati sui ruoli o basati sulle attestazioni. L'ereditarietà è più utile per descrivere l'oggetto che esegue effettivamente l'autenticazione, ad esempio un'implementazione LDAP e un'implementazione SQL.
Aaronaught il

34

Prendiamo la tua prima riga di codice ed esaminiamola.

Person Bob = new Person();

La prima Personè una specifica del tipo. In C #, possiamo rinunciare a questo semplicemente dicendo

var Bob = new Person();

e il compilatore inferirà il tipo della variabile Bob dalla chiamata del costruttore Person().

Ma potresti voler scrivere qualcosa del genere:

IPerson Bob = new Person();

Dove non stai rispettando l'intero contratto API di Person, ma solo il contratto specificato dall'interfaccia IPerson.


2
+1: Farò l' IPersonesempio nel mio codice per assicurarmi di non usare accidentalmente alcun metodo privato quando scrivo codice che dovrebbe essere copiato / incollabile in un'altra IPersonimplementazione.
Cort Ammon - Ripristina Monica

@CortAmmon Penso che tu abbia un errore di battitura lì, chiaramente intendevi "lavorare con il polimorfismo" piuttosto che copiare / incollare su quel codice: D
Benjamin Gruenbaum

@BenjaminGruenbaum certo, per qualche definizione di polimorfismo ;-)
Cort Ammon - Reinstate Monica

@CortAmmon come chiameresti accidentalmente un metodo privato ? Sicuramente intendevi internal?
Cole Johnson,

@ColeJohnson in entrambi i casi. Ho scritto che pensando a un caso specifico in cui mi imbatto dove privateè significativo: vengono istanziati metodi di fabbrica che fanno parte della classe. Nella mia situazione, l'accesso a valori privati ​​dovrebbe essere l'eccezione, non la norma. Lavoro sul codice che mi sopravviverà a lungo. Se lo faccio in questo modo, non solo è improbabile che io utilizzi personalmente alcuni metodi privati, ma quando il prossimo sviluppatore copia / incolla questo alcune dozzine di posti e lo sviluppatore dopo li copia, diminuisce le probabilità che qualcuno veda l'opportunità usare metodi privati ​​come comportamento "normale".
Cort Ammon - Ripristina Monica l'

21
  1. Questa sintassi è praticamente un'eredità del C ++, che, tra l'altro, ha entrambi:

    Person Bob;

    e

    Person *bob = new Bob();

    Il primo a creare un oggetto nell'ambito corrente, il secondo a creare un puntatore a un oggetto dinamico.

  2. Sicuramente puoi averlo something_else Bob = new Person()

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

    Stai facendo due cose diverse qui, affermando il tipo della variabile locale numse dici che vuoi creare un nuovo oggetto del tipo 'Elenco' e metterlo lì.

  3. C # è in qualche modo d'accordo con te, perché il più delle volte il tipo di variabile è identico a quello che hai inserito quindi:

    var nums = new List<int>();
  4. In alcune lingue fai del tuo meglio per evitare di indicare i tipi di variabili come in F # :

    let list123 = [ 1; 2; 3 ]

5
Probabilmente è più preciso affermare che il tuo secondo esempio crea un puntatore a un nuovo oggetto Bob. Il modo in cui le cose vengono archiviate è tecnicamente un dettaglio di implementazione.
Robert Harvey,

4
"Il primo a creare un oggetto locale nello stack, il secondo a creare un oggetto nell'heap." Oh amico, non di nuovo questa disinformazione.
Corse di leggerezza con Monica

@LightnessRacesinOrbit meglio?
AK_

1
@AK_ Mentre "dinamico" significa praticamente "heap" (quando heap è il modello di memoria utilizzato dalla piattaforma), "automatico" è molto distinto da "stack". In tal caso new std::pair<int, char>(), i membri firste secondla coppia hanno una durata di memorizzazione automatica, ma sono probabilmente allocati sull'heap (come membri pairdell'oggetto durata-memoria dinamica ).
Ripristina Monica il

1
@AK_: Sì, quei nomi implicano esattamente cosa significano, mentre questa assurdità "stack" vs "heap" no . Questo è il punto. Il fatto che li trovi confusi non fa che rafforzare la necessità per noi di insegnarli, in modo da non fare affidamento su una terminologia imprecisa / errata solo perché è familiare!
Lightness Races con Monica il

3

C'è un'enorme differenza tra int xe Person bob. Un intè un intè un inte deve sempre essere un inte non può mai essere altro che un int. Anche se non lo inizializzi intquando lo dichiari ( int x;), è comunque intimpostato sul valore predefinito.

Quando dichiari Person bob, tuttavia, c'è una grande flessibilità su ciò a cui il nome bobpotrebbe effettivamente riferirsi in un dato momento. Potrebbe riferirsi a un Person, o potrebbe riferirsi ad un'altra classe, ad esempio Programmer, derivata da Person; potrebbe anche essere null, riferendosi a nessun oggetto.

Per esempio:

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

I progettisti del linguaggio avrebbero sicuramente potuto creare una sintassi alternativa che avrebbe realizzato la stessa cosa Person carol = new Person()di un numero minore di simboli, ma avrebbero comunque dovuto consentire Person carol = new Person() (o fare qualche strana regola rendendo illegale quel particolare dei quattro esempi sopra). Erano più interessati a mantenere il linguaggio "semplice" che a scrivere un codice estremamente conciso. Ciò potrebbe aver influenzato la loro decisione di non fornire la sintassi alternativa più breve, ma in ogni caso, non era necessario e non lo hanno fornito.


1

Le due dichiarazioni possono essere diverse ma spesso sono uguali. Un modello comune consigliato in Java è simile a:

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

Queste variabili liste mapvengono dichiarate utilizzando le interfacce Liste Mapmentre il codice crea istanze di implementazioni specifiche. In questo modo, il resto del codice dipende solo dalle interfacce ed è facile scegliere diverse classi di implementazione da istanziare, ad esempio TreeMap, poiché il resto del codice non può dipendere da nessuna parte HashMapdell'API esterna Mapall'interfaccia.

Un altro esempio in cui i due tipi differiscono è in un metodo factory che seleziona una sottoclasse specifica per creare un'istanza, quindi la restituisce come tipo base in modo che il chiamante non debba essere a conoscenza dei dettagli dell'implementazione, ad esempio una scelta "politica".

L'inferenza del tipo può correggere la ridondanza del codice sorgente. Ad esempio in Java

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

costruirà il giusto tipo di Elenco grazie all'inferenza del tipo e alla dichiarazione

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

In alcune lingue, l'inferenza del tipo va oltre, ad esempio in C ++

auto p = new Person();

A proposito, il linguaggio Java stabilisce una forte convenzione per l'uso di nomi identificativi minuscoli, come bob, no Bob. Ciò evita molte ambiguità, ad esempio package.Class vs. Class.variable.
Jerry101,

... ed è per questo che hai clazz.
un CVn

No, clazzviene utilizzato perché classè una parola chiave, pertanto non può essere utilizzata come identificatore.
Jerry101,

... che non sarebbe un problema se le convenzioni di denominazione non fossero come sono. Classè un identificatore perfettamente valido.
cao

... come sono cLaSs, cLASSe cLASs.
el.pescado,

1

Nelle parole di laici:

  • Separare la dichiarazione dall'istanza aiuta a separare chi usa gli oggetti da chi li crea
  • Quando lo fai, il polisforfismo è abilitato poiché, finché il tipo istanziato è un sottotipo del tipo di dichiarazione, tutto il codice che utilizza la variabile funzionerà
  • In linguaggi fortemente tipizzati devi dichiarare una variabile indicandone il tipo, semplicemente facendo non var = new Process()stai dichiarando prima la variabile.

0

Riguarda anche il livello di controllo su ciò che sta accadendo. Se la dichiarazione di un oggetto / variabile chiama automaticamente un costruttore, ad esempio if

Person somePerson;

era automaticamente lo stesso di

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

allora non saresti mai in grado di usare (per esempio) metodi statici di fabbrica per creare un'istanza di oggetti piuttosto che costruttori predefiniti, cioè ci sono momenti in cui non vuoi chiamare un costruttore per una nuova istanza di oggetto.

Questo esempio è spiegato in Joshua Bloch s' Effective Java (punto 1 ironicamente!)


Per quanto riguarda i libri, sia il mio libro C # che il libro Java erano di Joyce Farrell. Questo è esattamente ciò che il corso ha specificato. Ho anche integrato entrambi con vari video di YouTube su C # e Java.
Jason Wohlgemuth,

Non stavo criticando, sto solo facendo un riferimento a da dove proviene :)
David Scholefield,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.