2009-11-25 16 views
22

Estoy en mi primera clase de programación en la escuela secundaria. Estamos haciendo nuestro final del primer proyecto del semestre. Este proyecto solo involucra una clase, pero muchos métodos. Mi pregunta es la mejor práctica con variables de instancia y variables locales. Parece que sería mucho más fácil para mí codificar usando casi solo variables de instancia. Pero no estoy seguro de si así es como debería hacerlo o si debería usar más variables locales (podría simplemente tener que tener métodos que consideren los valores de las variables locales mucho más).Variables de instancia de Java contra variables locales

Mi razonamiento para esto también se debe a que muchas veces querré tener un método para devolver dos o tres valores, pero esto por supuesto no es posible. Por lo tanto, parece más fácil simplemente usar variables de instancia y no tener que preocuparse ya que son universales en la clase.

Gracias y lo siento si escribí de manera confusa.

+0

¿Tiene permiso para definir otras clases? –

Respuesta

4

Relato breve: si y solo si una variable necesita accederse por más de un método (o fuera de la clase), créelo como variables de instancia. Si lo necesita solo localmente, en un único método, tiene que ser una variable local. Las variables de instancia son más costosas que las variables locales.
Tenga en cuenta: las variables de instancia se inicializan a valores predeterminados mientras que las variables locales no.

8

Use variables de instancia cuando es un concepto central de su clase. Si está iterando, recurriendo o haciendo algún procesamiento, entonces use variables locales.

Cuando necesite usar dos (o más) variables en los mismos lugares, es hora de crear una nueva clase con esos atributos (y los medios apropiados para configurarlos). Esto hará que su código sea más limpio y lo ayudará a pensar sobre los problemas (cada clase es un término nuevo en su vocabulario).

Una variable se puede convertir en una clase cuando se trata de un concepto básico. Por ejemplo, identificadores del mundo real: estos podrían representarse como cadenas, pero a menudo, si los encapsula en su propio objeto, de repente comienzan a "atraer" funcionalidad (validación, asociación a otros objetos, etc.)

También (no del todo relacionado)) es la coherencia del objeto: un objeto es capaz de garantizar que su estado tenga sentido. Establecer una propiedad puede alterar a otra. También hace que sea mucho más fácil modificar su programa para que sea seguro para subprocesos más adelante (si es necesario).

3

Siempre se prefieren las variables locales internas a los métodos, ya que desea mantener el alcance de cada variable lo más pequeño posible. Pero si más de un método necesita acceder a una variable, entonces tendrá que ser una variable de instancia.

Las variables locales son más como valores intermedios utilizados para alcanzar un resultado o calcular algo sobre la marcha. Las variables de instancia son más como atributos de una clase, como su edad o nombre.

1

Trate de pensar sobre su problema en términos de objetos. Cada clase representa un tipo diferente de objeto. Las variables de instancia son los datos que una clase necesita recordar para trabajar, ya sea consigo mismo o con otros objetos. Las variables locales solo deben usarse cálculos intermedios, datos que no necesita guardar una vez que abandona el método.

2

Declare las variables de la forma más restringida posible. Declara las variables locales primero. Si esto no es suficiente, use variables de instancia. Si esto no es suficiente, use variables de clase (estáticas).

I necesita devolver más de un valor para devolver una estructura compuesta, como una matriz o un objeto.

2

La manera fácil: si la variable debe ser compartida por más de un método, use la variable de instancia; de lo contrario, use la variable local.

Sin embargo, la buena práctica es utilizar la mayor cantidad posible de variables locales. ¿Por qué? Para su proyecto simple con solo una clase, no hay diferencia. Para un proyecto que incluye muchas clases, hay una gran diferencia. La variable de instancia indica el estado de tu clase. Cuantas más variables de instancia haya en su clase, más estados podrá tener esta clase y, cuanto más compleja sea esta clase, más difícil será mantener la clase o más propenso a errores pueda ser su proyecto. Entonces, la buena práctica es usar la variable más local posible para mantener el estado de la clase lo más simple posible.

1

Trate de no devolver más de un valor de sus métodos en primer lugar. Si no puede, y en algunos casos realmente no puede, entonces recomendaría encapsular eso en una clase. Solo en el último caso recomendaría cambiar otra variable dentro de su clase (una variable de instancia). El problema con el enfoque de variables de instancia es que aumenta los efectos secundarios; por ejemplo, llama al método A en su programa y modifica algunas variables de instancia (s). Con el tiempo, eso lleva a una mayor complejidad en su código y el mantenimiento se vuelve más y más difícil.

Cuando tengo que usar variables de instancia, trato de hacer entonces finales e inicializar luego en los constructores de clase, para minimizar los efectos secundarios. Este estilo de programación (minimizando los cambios de estado en su aplicación) debería conducir a un mejor código que es más fácil de mantener.

33

No he visto a nadie discutir esto, así que voy a agregar más ideas para pensar. La respuesta/consejo corto es que no use variables de instancia sobre variables locales solo porque crea que son más fáciles de devolver valores. Vas a hacer que trabajar con tu código sea muy difícil si no usas las variables locales y las variables de instancia de manera apropiada. Producirá algunos errores graves que son realmente difíciles de rastrear. Si quieres entender lo que quiero decir con errores serios, y lo que podría parecer, sigue leyendo.

Probemos y usemos solo las variables de instancia que sugiera para escribir en las funciones. Voy a crear una clase muy simple:

public class BadIdea { 
    public Enum Color { GREEN, RED, BLUE, PURPLE }; 
    public Color[] map = new Colors[] { 
     Color.GREEN, 
     Color.GREEN, 
     Color.RED, 
     Color.BLUE, 
     Color.PURPLE, 
     Color.RED, 
     Color.PURPLE }; 

    List<Integer> indexes = new ArrayList<Integer>(); 
    public int counter = 0; 
    public int index = 0; 

    public void findColor(Color value) { 
     indexes.clear(); 
     for(index = 0; index < map.length; index++) { 
     if(map[index] == value) { 
      indexes.add(index); 
      counter++; 
     } 
     } 
    } 

    public void findOppositeColors(Color value) { 
     indexes.clear(); 
     for(index = 0; i < index < map.length; index++) { 
     if(map[index] != value) { 
      indexes.add(index); 
      counter++; 
     } 
     } 
    } 
} 

Este es un programa tonto lo sé, pero lo podemos usar para ilustrar el concepto de que el uso de variables de instancia para cosas como esta es una idea tremendamente mala. Lo más importante que encontrará es que esos métodos usan todas las variables de instancia que tenemos. Y modifica los índices, el contador y el índice cada vez que son llamados. El primer problema que encontrará es que llamar a esos métodos uno después del otro puede modificar las respuestas de ejecuciones anteriores. Así, por ejemplo, si escribió el siguiente código:

BadIdea idea = new BadIdea(); 
idea.findColor(Color.RED); 
idea.findColor(Color.GREEN); // whoops we just lost the results from finding all Color.RED 

Desde findColor utiliza variables de instancia de seguimiento de los valores devueltos que sólo puede devolver un resultado a la vez. Vamos a tratar de salvar de una referencia a esos resultados antes de llamar de nuevo:?

BadIdea idea = new BadIdea(); 
idea.findColor(Color.RED); 
List<Integer> redPositions = idea.indexes; 
int redCount = idea.counter; 
idea.findColor(Color.GREEN); // this causes red positions to be lost! (i.e. idea.indexes.clear() 
List<Integer> greenPositions = idea.indexes; 
int greenCount = idea.counter; 

En este segundo ejemplo hemos salvado las posiciones rojas en la tercera línea, pero lo mismo sucedió ¿Por qué los perdemos ?! Porque idea.indexes se borró en lugar de asignarse, por lo que solo se puede usar una respuesta por vez. Tienes que terminar de usar ese resultado por completo antes de volver a llamarlo. Una vez que vuelves a llamar a un método, los resultados se borran y pierdes todo. Para solucionarlo, deberá asignar un nuevo resultado cada vez, de modo que las respuestas rojas y verdes estén separadas. Así que vamos a clonar nuestras respuestas para crear nuevas copias de las cosas:

BadIdea idea = new BadIdea(); 
idea.findColor(Color.RED); 
List<Integer> redPositions = idea.indexes.clone(); 
int redCount = idea.counter; 
idea.findColor(Color.GREEN); 
List<Integer> greenPositions = idea.indexes.clone(); 
int greenCount = idea.counter; 

Ok, finalmente tenemos dos resultados separados.Los resultados de rojo y verde ahora están separados. Pero, teníamos que saber mucho sobre cómo funcionaba BadIdea internamente antes de que el programa funcionara, ¿no? Debemos recordar clonar los retornos cada vez que lo llamemos para asegurarnos de que nuestros resultados no se vean perjudicados. ¿Por qué la persona que llama se ve obligada a recordar estos detalles? ¿No sería más fácil si no tuviéramos que hacer eso?

Observe también que la persona que llama tiene que usar variables locales para recordar los resultados, por lo tanto, si no usó variables locales en los métodos de BadIdea, la persona que llama debe usarlos para recordar los resultados. Entonces, ¿qué lograste realmente? Realmente le acabas de pasar el problema a la persona que llama obligándolos a hacer más. Y el trabajo que presiona a la persona que llama no es una regla fácil de seguir porque hay muchas excepciones a la regla.

Ahora intentemos hacerlo con dos métodos diferentes. Observe cómo he sido "inteligente" y reutilicé esas mismas variables de instancia para "guardar memoria" y mantuve el código compacto. ;-)

BadIdea idea = new BadIdea(); 
idea.findColor(Color.RED); 
List<Integer> redPositions = idea.indexes; 
int redCount = idea.counter; 
idea.findOppositeColors(Color.RED); // this causes red positions to be lost again!! 
List<Integer> greenPositions = idea.indexes; 
int greenCount = idea.counter; 

¡Sucedió lo mismo! ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡Maldad, estaba siendo "inteligente" y ahorraba memoria y el código usa menos Este es el verdadero peligro de usar variables de instancia, como esto es que los métodos de llamada ahora dependen del orden. Si cambio el orden de las llamadas al método, los resultados son diferentes, aunque realmente no he cambiado el estado subyacente de BadIdea. No cambié el contenido del mapa. ¿Por qué el programa arroja resultados diferentes cuando llamo a los métodos en diferente orden?

idea.findColor(Color.RED) 
idea.findOppositeColors(Color.RED) 

produce un resultado diferente que si cambié esos dos métodos:

idea.findOppositeColors(Color.RED) 
idea.findColor(Color.RED) 

Este tipo de errores son realmente difíciles de localizar, especialmente cuando esas líneas no están uno al lado del otro. Puede romper completamente su programa simplemente agregando una nueva llamada en cualquier lugar entre esas dos líneas y obtener resultados tremendamente diferentes. Claro que cuando tratamos con pocas líneas, es fácil detectar errores. Pero, en un programa más grande, puede desperdiciar días intentando reproducirlos aunque los datos del programa no hayan cambiado.

Y esto solo tiene en cuenta los problemas de un solo hilo. Si BadIdea se estaba utilizando en una situación de subprocesos múltiples, los errores pueden ser realmente extraños. ¿Qué sucede si se llaman al mismo tiempo a findColors() y findOppositeColors()? Crash, todo tu pelo se cae, la muerte, el espacio y el tiempo colapsan en una singularidad y el universo se traga? Probablemente al menos dos de esos. Es probable que ahora los temas estén por encima de tu cabeza, pero espero que podamos alejarte de hacer cosas malas ahora, así que cuando hagas esas malas prácticas, no te causen verdadero dolor.

¿Notaste qué tan cuidadoso tenías que estar al llamar a los métodos? Se sobreescribieron mutuamente, compartieron memoria posiblemente de forma aleatoria, tenía que recordar los detalles de cómo funcionaba en el interior para que funcionara en el exterior, cambiar el orden en que se llamaban las cosas producía cambios muy grandes en las siguientes líneas, y solo podría funcionar en una sola situación de subproceso. Hacer cosas como esta producirá código realmente frágil que parece derrumbarse cada vez que lo tocas. Estas prácticas que mostré contribuyeron directamente a que el código fuera frágil.

Si bien esto podría parecer encapsulado, es exactamente lo contrario porque los detalles técnicos de cómo lo escribió deben ser conocidos por el que llama. La persona que llama tiene que escribir su código de una manera muy particular para hacer que su código funcione, y no pueden hacerlo sin conocer los detalles técnicos de su código. Esto a menudo se denomina Leaky Abstraction porque se supone que la clase oculta los detalles técnicos detrás de una abstracción/interfaz, pero los detalles técnicos se filtran forzando a la persona que llama a cambiar su comportamiento.Cada solución tiene algún grado de filtración, pero al usar cualquiera de las técnicas anteriores como estas garantías, sin importar el problema que intente resolver, será terriblemente permeable si las aplica. Entonces veamos el GoodIdea ahora.

reescritura Vamos a utilizar las variables locales:

public class GoodIdea { 
    ... 

    public List<Integer> findColor(Color value) { 
     List<Integer> results = new ArrayList<Integer>(); 
     for(int i = 0; i < map.length; i++) { 
     if(map[index] == value) { 
      results.add(i); 
     } 
     } 
     return results; 
    } 

    public List<Integer> findOppositeColors(Color value) { 
     List<Integer> results = new ArrayList<Integer>(); 
     for(int i = 0; i < map.length; i++) { 
     if(map[index] != value) { 
      results.add(i); 
     } 
     } 
     return results; 
    } 
} 

Esto soluciona todos los problemas que discutimos anteriormente. Sé que no estoy rastreando el contador o devolviéndolo, pero si lo hago, puedo crear una nueva clase y devolverla en lugar de List. A veces utilizo el siguiente objeto para devolver resultados múltiples rápidamente:

public class Pair<K,T> { 
    public K first; 
    public T second; 

    public Pair(K first, T second) { 
     this.first = first; 
     this.second = second; 
    } 
} 

Respuesta larga, pero un tema muy importante.

0

Las variables generalmente deben tener un alcance mínimo.

Desafortunadamente, para construir clases con alcance de variable minimizado, a menudo es necesario realizar una gran cantidad de pasos de parámetros de método.

Pero si sigues ese consejo todo el tiempo, minimizando perfectamente el alcance de la variable, puede terminar con mucha redundancia e inflexibilidad de método con todos los objetos requeridos pasados ​​dentro y fuera de los métodos.

imagen una base de código con miles de métodos como esto:

private ClassThatHoldsReturnInfo foo(OneReallyBigClassThatHoldsCertainThings big, 
AnotherClassThatDoesLittle little) { 
    LocalClassObjectJustUsedHere here; 
      ... 
} 
private ClassThatHoldsReturnInfo bar(OneMediumSizedClassThatHoldsCertainThings medium, 
AnotherClassThatDoesLittle little) { 
    ... 
} 

Y, por otra parte, imaginar una base de código con una gran cantidad de variables de instancia como esta:

private OneReallyBigClassThatHoldsCertainThings big; 
private OneMediumSizedClassThatHoldsCertainThings medium; 
private AnotherClassThatDoesLittle little; 
private ClassThatHoldsReturnInfo ret; 

private void foo() { 
    LocalClassObjectJustUsedHere here; 
    .... 
} 
private void bar() { 
    .... 
} 

Como código aumenta, la primera forma puede minimizar el alcance variable mejor, pero puede conducir fácilmente a que se pasen muchos parámetros de método. El código generalmente será más detallado y esto puede llevar a una complejidad ya que uno refactoriza todos estos métodos.

El uso de más variables de instancia puede reducir la complejidad de muchos parámetros de método que se pasan y puede dar flexibilidad a los métodos cuando se reorganizan frecuentemente los métodos para mayor claridad. Pero crea más estado de objeto que debes mantener. En general, el consejo es hacer lo primero y abstenerse de lo último.

Sin embargo, muy a menudo, y puede depender de la persona, uno puede administrar más fácilmente la complejidad del estado en comparación con las miles de referencias de objetos adicionales del primer caso. Uno puede darse cuenta de esto cuando la lógica empresarial dentro de los métodos aumenta y la organización necesita cambiar para mantener el orden y la claridad.

No solo eso. Cuando reorganiza sus métodos para mantener la claridad y realizar muchos cambios de parámetros de método en el proceso, termina con muchas diferencias de control de versión que no son tan buenas para un código de calidad de producción estable. Hay un equilibrio. Una forma causa un tipo de complejidad. La otra forma causa otro tipo de complejidad.

Utilice la manera que mejor se adapte a sus necesidades. Encontrarás ese equilibrio a lo largo del tiempo.

Creo que este joven programador tiene algunas primeras impresiones perspicaces para el código de bajo mantenimiento.

0

uso variable de instancia cuando

  1. si 2 funciones de la clase necesitan el mismo valor, entonces lo convierten en instancia de variable O
  2. Si no se espera que el Estado cambie hacerla variable de instancia. por ejemplo: objeto inmutable, DTO, LinkedList, aquellos con variables finales O
  3. Si se trata de datos subyacentes sobre los cuales se realizan acciones. por ejemplo: final en arr [] en el código fuente PriorityQueue.java O
  4. Incluso si se utiliza se espera solamente una vez & & estado para cambiar, que sea ejemplo, si se utiliza sólo una vez por una función cuyo lista de parámetros debe ser vacío. Por ejemplo: HTTPCookie.java Line: 860 hashcode() function usa 'path variable'.

Similarmente, use la variable local cuando ninguna de estas condiciones concuerde, específicamente el rol de la variable terminaría una vez que la pila se haya apagado. por ejemplo: Comparator.compare (o1, o2);

Cuestiones relacionadas