¿Por qué la creación de instancias es como es?

17

Aprendí C # en el transcurso de los últimos seis meses más o menos y ahora estoy profundizando en Java. Mi pregunta es sobre la creación de instancias (en cualquier idioma, realmente) y es más de: Me pregunto por qué lo hicieron de esa manera. Toma este ejemplo

Person Bob = new Person();

¿Hay alguna razón por la que el objeto se especifica dos veces? ¿Habría alguna vez unsomething_else Bob = new Person() ?

Parecería que si siguiera la convención sería más como:

int XIsAnInt;
Person BobIsAPerson;

O tal vez uno de estos:

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

Supongo que tengo curiosidad si hay una respuesta mejor que "así es como se hace".

Jason Wohlgemuth
fuente
26
¿Qué pasa si la persona es un subtipo de LivingThing? Se puede escribir LivingThing lt = new Person(). Busque herencia e interfaces.
xlecoustillier
2
Person Bobdeclara una variable de tipo "referencia a Person" llamada Bob. new Person()crea un Personobjeto ¡Las referencias, variables y objetos son tres cosas diferentes!
user253751
55
¿Te molesta la redundancia? Entonces, ¿por qué no escribir var bob = new Person();?
200_success
44
Person Bob();es posible en C ++ y significa casi lo mismo quePerson Bob = Person();
user60561
3
@ user60561 no, declara una función que no toma argumentos y devuelve Person.
Nikolai

Respuestas:

52

¿Habría alguna vez algo_else Bob = new Person ()?

Sí, por herencia. Si:

public class StackExchangeMember : Person {}

Luego:

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

Bob también es una persona y, por Dios, no quiere ser tratado de manera diferente a los demás.

Además, podríamos dotar a Bob de superpoderes:

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

IModerator bob = new StackOverFlowModerator();

Y así, por Dios, no tolerará ser tratado de manera diferente a cualquier otro moderador. Y a él le gusta escabullirse por el foro para mantener a todos en línea mientras está de incógnito:

StackExchangeMember bob = new StackOverFlowModerator();

Luego, cuando encuentra un pobre primer póster, se quita su capa de invisibilidad y salta.

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

Y luego puede actuar inocente y todo eso después:

((Person) bob).ImNotMeanIWasJustInstantiatedThatWay();
radarbob
fuente
20
Esto sería mucho más claro si escribes en minúscula los nombres de tus objetos.
Lightness compite con Monica
38
Buen ejemplo de la especificación; mal ejemplo de herencia. Para cualquier otra persona que lea esto, por favor, no intente resolver los roles de usuario utilizando la herencia.
Aaronaught
8
@Aaronaught es correcto. No cree clases separadas para diferentes tipos de personas. Usa un campo de bits enum.
Cole Johnson
1
@Aaronaught Está muy bien decir qué no hacer, pero no es muy útil sin decir lo que la gente debería hacer en su lugar.
Pharap
55
@Pharap: He hecho exactamente eso, en varias otras preguntas . La respuesta simple es que los usuarios (autenticación / identidad) y la política de seguridad (autorización / permisos) deben tratarse como preocupaciones separadas, y los modelos estándar para la política de seguridad están basados ​​en roles o en reclamos. La herencia es más útil para describir el objeto que realmente realiza la autenticación, por ejemplo, una implementación LDAP y una implementación SQL.
Aaronaught
34

Tomemos su primera línea de código y examínelo.

Person Bob = new Person();

El primero Persones una especificación de tipo. En C #, podemos prescindir de esto simplemente diciendo

var Bob = new Person();

y el compilador inferirá el tipo de la variable Bob a partir de la llamada del constructorPerson() .

Pero es posible que desee escribir algo como esto:

IPerson Bob = new Person();

Donde no está cumpliendo todo el contrato API de Persona, sino solo el contrato especificado por la interfaz IPerson.

Robert Harvey
fuente
2
+1: Haré el IPersonejemplo en mi código para asegurarme de no usar accidentalmente ningún método privado cuando escribo código que debería ser copiable / pegado a otra IPersonimplementación.
Cort Ammon - Restablece a Monica
@CortAmmon Creo que tienes un error tipográfico allí, claramente querías decir "trabajar con polimorfismo" en lugar de copiar / pegar en ese código: D
Benjamin Gruenbaum
@BenjaminGruenbaum seguro, para alguna definición de polimorfismo ;-)
Cort Ammon - Restablece a Monica
@CortAmmon, ¿cómo llamarías accidentalmente a un método privado ? ¿Seguramente te referías internal?
Cole Johnson
@ColeJohnson de cualquier manera. Escribí que pensar en un caso específico con el que me encuentro privatees significativo: los métodos de fábrica que forman parte de la clase que se instancia. En mi situación, el acceso a valores privados debería ser la excepción, no la norma. Trabajo en código que me va a sobrevivir mucho tiempo. Si lo hago de esta manera, no solo es improbable que use métodos privados yo mismo, sino que cuando el próximo desarrollador copie / pegue esto unas pocas docenas de lugares y el desarrollador luego los copie, disminuye las probabilidades de que alguien vea la oportunidad de utilizar métodos privados como comportamiento "normal".
Cort Ammon - Restablece a Monica
21
  1. Esta sintaxis es más o menos un legado de C ++, que, por cierto, tiene ambas:

    Person Bob;

    y

    Person *bob = new Bob();

    El primero para crear un objeto dentro del alcance actual, el segundo para crear un puntero a un objeto dinámico.

  2. Definitivamente puedes tener something_else Bob = new Person()

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

    Aquí está haciendo dos cosas diferentes, indicando el tipo de la variable local numsy dice que desea crear un nuevo objeto del tipo 'Lista' y ponerlo allí.

  3. C # está de acuerdo con usted, porque la mayoría de las veces el tipo de la variable es idéntico a lo que ingresa, por lo tanto:

    var nums = new List<int>();
  4. En algunos idiomas, hace todo lo posible para evitar indicar los tipos de variables como en F # :

    let list123 = [ 1; 2; 3 ]
ALASKA_
fuente
55
Probablemente sea más exacto decir que su segundo ejemplo crea un puntero a un nuevo objeto Bob. La forma en que se almacenan las cosas es técnicamente un detalle de implementación.
Robert Harvey
44
"El primero en crear un objeto local en la pila, el segundo en crear un objeto en el montón". Oh hombre, no esta información errónea de nuevo.
Lightness compite con Monica
@LightnessRacesinOrbit mejor?
AK_
1
@AK_ Si bien "dinámico" significa "montón" (cuando el montón es el modelo de memoria utilizado por la plataforma), "automático" es muy distinto de "pila". Si lo hace new std::pair<int, char>(), entonces los miembros firsty secondel par tienen una duración de almacenamiento automática, pero probablemente se asignan en el montón (como miembros del pairobjeto de duración de almacenamiento dinámico ).
Vuelva a instalar a Monica
1
@AK_: Sí, esos nombres implican exactamente lo que significan, mientras que este "apilamiento" frente a "tonterías" no tiene sentido . Ese es todo el punto. El hecho de que los encuentre confusos solo refuerza la necesidad de que les enseñemos, de modo que no confíe en una terminología inexacta / incorrecta solo porque es familiar.
ligereza corre con Mónica
3

Hay una gran diferencia entre int xy Person bob. Un intes un intes un inty siempre debe ser un inty nunca puede ser otra cosa que un int. Incluso si no inicializa intcuando lo declara ( int x;), sigue siendo un intconjunto con el valor predeterminado.

Person bobSin embargo, cuando declaras , hay una gran flexibilidad en cuanto a lo que el nombre bobpodría referirse en un momento dado. Podría referirse a a Person, o podría referirse a alguna otra clase, por ejemplo Programmer, derivada de Person; incluso podría ser null, refiriéndose a ningún objeto en absoluto.

Por ejemplo:

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

Los diseñadores de lenguaje ciertamente podrían haber hecho una sintaxis alternativa que hubiera logrado lo mismo que Person carol = new Person()en menos símbolos, pero aún así tendrían que permitir Person carol = new Person() (o hacer alguna regla extraña que haga que ese particular en particular de los cuatro ejemplos anteriores sea ilegal). Estaban más preocupados por mantener el lenguaje "simple" que por escribir código extremadamente conciso. Eso puede haber influido en su decisión de no proporcionar la sintaxis alternativa más corta, pero en cualquier caso, no fue necesario y no lo proporcionaron.

David K
fuente
1

Las dos declaraciones pueden ser diferentes, pero a menudo son las mismas. Un patrón común recomendado en Java se ve así:

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

Estas variables listy mapse declaran utilizando las interfaces Listy Mapmientras el código crea instancias específicas. De esta manera, el resto del código solo depende de las interfaces y es fácil elegir una clase de implementación diferente para instanciar TreeMap, ya que el resto del código no puede depender de ninguna parte de la HashMapAPI que esté fuera de la Mapinterfaz.

Otro ejemplo en el que los dos tipos difieren es en un método de fábrica que selecciona una subclase específica para instanciar, luego la devuelve como el tipo base para que la persona que llama no necesite conocer los detalles de implementación, por ejemplo, una opción de "política".

La inferencia de tipos puede corregir la redundancia del código fuente. Ej. En Java

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

construirá el tipo correcto de Lista gracias a la inferencia de tipos y la declaración

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

En algunos idiomas, la inferencia de tipos va más allá, por ejemplo, en C ++

auto p = new Person();
Jerry101
fuente
Por cierto, el lenguaje Java establece una convención fuerte para usar nombres de identificadores en minúsculas, como bob, no Bob. Esto evita mucha ambigüedad, por ejemplo, package.Class vs. Class.variable.
Jerry101
... y es por eso que tienes clazz.
un CVn
No, clazzse usa porque classes una palabra clave, por lo que no se puede usar como identificador.
Jerry101
... lo que no sería un problema si las convenciones de nombres no fueran como son. ClassEs un identificador perfectamente válido.
cHao
... como están cLaSs, cLASSy cLASs.
el.pescado
1

En palabras simples:

  • Separar la declaración de la creación de instancias ayuda a desacoplar quién usa los objetos de quién los crea
  • Cuando haces eso, el polyporphism está habilitado ya que, siempre que el tipo instanciado sea un subtipo del tipo de declaración, todo el código que use la variable funcionará
  • En lenguajes fuertemente tipados, debe declarar una variable que indique su tipo, al hacerlo simplemente var = new Process()no declara la variable primero.
Tulains Córdova
fuente
0

También se trata del nivel de control sobre lo que está sucediendo. Si la declaración de un objeto / variable llama automáticamente a un constructor, por ejemplo, si

Person somePerson;

era automáticamente lo mismo que

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

entonces nunca podría usar (por ejemplo) métodos estáticos de fábrica para crear instancias de objetos en lugar de constructores predeterminados, es decir, hay momentos en los que no desea llamar a un constructor para una nueva instancia de objeto.

En este ejemplo se explica en Joshua Bloch 's Effective Java (artículo 1 irónicamente!)

David Scholefield
fuente
Con respecto a los libros, tanto mi libro C # como mi libro Java fueron de Joyce Farrell. Eso es exactamente lo que especificó el curso. También he estado complementando ambos con varios videos de youtube en C # y Java.
Jason Wohlgemuth
No estaba criticando, solo daba una referencia de dónde vino esto :)
David Scholefield