parameter passing - passing - ¿Es “pasado por referencia” un mal diseño?
pass by reference c++ (8)
¿Es “pasado por referencia” un mal diseño?
No en general. Debe comprender su situación específica y preguntarse qué hace una función. Y necesita definir su estilo de codificación correctamente, especialmente si está codificando para otros (distribuyendo bibliotecas).
El paso por referencia suele estar en su lugar cuando su función devuelve múltiples salidas. A menudo es una buena idea devolver un objeto ResultContainer
que contenga toda la información que devuelve su función. Tome el siguiente ejemplo de C #:
bool isTokenValid(string token, out string username)
VS
AuthenticationResult isTokenValid(string token)
class AuthenticationResult {
public bool AuthenticationResult;
public string Username;
}
La diferencia es que el método con el parámetro de referencia (en este caso, out
) destaca claramente que solo se puede utilizar para validar un token u, opcionalmente, para extraer información del usuario. Así que incluso si está obligado a pasar un parámetro, puede eliminarlo si no lo necesita. El segundo ejemplo es más código verboso.
Por supuesto, el segundo diseño es preferible si tiene tal método.
bool doSomething(object a, ref object b, ref object c, ref object d, ... ref object z);
Porque los envolverías todos en un contenedor.
Permítanme aclarar ahora: en Java y C #, los tipos no primitivos siempre se pasan como referencia clonada . Significa que los object
no se clonan ellos mismos, sino que solo la referencia a ellos se clona en la pila, y luego no se puede esperar que apunten a un objeto totalmente diferente después del retorno. En su lugar, siempre espera que el estado del objeto sea modificado por el método. De lo contrario, simplemente clone()
el objeto y voilà.
Así que aquí viene el truco: MutableInteger
, o mejor el patrón Holder
, es la solución para pasar valores primitivos por referencia.
Actualmente lo utilizan los compiladores idl2java
CORBA cuando su IDL tiene un parámetro de referencia.
En su caso específico, no puedo responderle sobre el diseño bueno o malo porque el método que mostró es demasiado genérico. Así que piénsalo. Solo como entrada, si tuviera algún tipo de función de postprocesamiento aplicada a la información multimedia, incluso como el cifrado, usaría el paso de referencia. Para mí, lo siguiente parece un buen diseño.
encrypt(x);
VS
x = encrypt(x);
Entonces, empecé a aprender Java y descubrí que no existe tal cosa como "pasar por referencia". Estoy portando una aplicación de C # a Java, y la aplicación original tiene ints y dobles que son parámetros "ref" o "out".
Al principio pensé que podía pasar un "Integer" o un "Double", y como ese era un tipo de Referencia, el valor cambiaría. Pero luego aprendí que esos tipos de referencia son inmutables.
Entonces, hice una clase "MutableInteger" y una clase "MutableDouble", y las pasé a mis funciones. Funciona, pero creo que debo ir en contra de las intenciones de diseño originales del lenguaje.
Es "pasar por referencia" en general mal diseño? ¿Cómo debo cambiar mi forma de pensar?
Parece razonable tener una función como esta:
bool MyClass::changeMyAandB(ref int a, ref int b)
{
// perform some computation on a and b here
if (success)
return true;
else return false;
}
¿Es ese mal diseño?
El mal diseño en el que se está involucrando está en el uso de una clase MutableInteger
. 2 siempre será 2. Mañana será 2.
El patrón estándar de Java / OO generalmente es permitir que dichos elementos sean una instancia de una clase y dejar que esa clase los opere / administre.
El siguiente es AtomicInteger
. Una vez más, nunca he tenido una situación en la que necesito transmitirla, pero si no quieres refactorizar muchos códigos (tu pregunta fue de "buena práctica", así que tuve que ser duro contigo) es mejor. opción. La razón es que si está permitiendo que un entero se escape a otra función, desde un punto de vista de encapsulación, no sabe que la otra función se ejecutará en el mismo hilo. Como tal concurrencia está en juego y probablemente querrá la concurrencia proporcionada por las clases de referencia atómica. (También vea la AtomicReference
).
Es "pasar por referencia" en general mal diseño? ¿Cómo debo cambiar mi forma de pensar? No solo necesita hacer un bean POJO para mantener sus valores, enviar ese bean a la función y recuperar los nuevos valores. Por supuesto, en algunos casos solo puede tener la función que su llamada devuelve el valor (si siempre es solo una cosa que quiere recuperar, pero está hablando sobre nuestras variables, por lo que creo que es más de una).
Tradicionalmente, haga un bean que tenga las propiedades que necesitan cambiar de ejemplo:
class MyProps{
int val1;
int val2;//similarly can have strings etc here
public int getVal1(){
return val1;
}
public void setVal1(int p){
val1 = p;
}// and so on other getters & setters
}
O puede hacer una clase con genéricos para contener cualquier objeto.
class TVal<E>{
E val;
public E getValue(){
return val;
}
public void setValue(E p){
val = p;
}
}
Ahora usa tu clase para pasar por referencia del contenedor:
public class UseIt{
void a (){
TVal<Integer> v1 = new TVal<Integer>();
v1.setValue(1);//auto boxed to Integer from int
TVal<Integer> v2 = new TVal<Integer>();
v2.setValue(3);
process(v1,v2);
System.out.println("v1 " + v1 + " v2 " + v2);
}
void process(TVal<Integer> v,TVal<Integer> cc){
v.setValue(v.getValue() +1);
cc.setValue(cc.getValue() * 2);
}
}
La programación orientada a objetos se realiza mejor si estructura su código en abstracciones claras y comprensibles.
Los números, como una abstracción, son inmutables y no tienen identidad (es decir, un "cinco" siempre es un "cinco" y no existe tal cosa como "múltiples instancias de cinco").
Lo que estás tratando de inventar es un "número mutable" que es mutable y tiene identidad. Este concepto es un poco difícil de manejar, y probablemente sería mejor modelar su problema con abstracciones más significativas (objetos) que eso.
Piense en objetos que representan algo y tienen una interfaz específica, en lugar de en grupos de valores individuales.
No está mal diseñado en un lenguaje con soporte adecuado para él, (*) pero cuando tiene que definir una clase MutableInt
solo para comunicarse entre dos métodos, algo está mal.
La solución para el ejemplo que publicó es devolver una matriz de dos ints y señalar el fallo por un retorno null
o una excepción. Esto no siempre funcionará, así que a veces tienes que ...
- establecer atributos en el objeto actual (cuando se comunican dos métodos dentro de una clase);
- pase una matriz de enteros que el método puede modificar (cuando hay muchos enteros que se van a pasar varias veces);
- cree una clase auxiliar, digamos
Result
, que encapsule el resultado de un cálculo (cuando se trata de unint
y unfloat
lugar de dos ints), y tal vez tenga el cálculo real como un método o constructor; - use el idioma que sugirió, pero luego considere usar el soporte para él en Apache Commons u otra buena biblioteca.
(*) Solo mal diseño del lenguaje. En Python o Go, devolverías múltiples valores y dejarías de preocuparte.
Pasar objetos de valor por referencia es, en general, un mal diseño.
Hay ciertos escenarios para los que es válido, como el intercambio de posición de matriz para operaciones de clasificación de alto rendimiento.
Hay muy pocas razones por las que debería necesitar esta funcionalidad. En C #, el uso de la palabra clave OUT es generalmente un defecto en sí mismo. Hay algunos usos aceptables de la palabra clave out como DateTime.TryParse(datetext, out DateValue)
pero el uso estándar de los parámetros out es un diseño de software deficiente que desea emular generalmente la mala práctica de usar indicadores para el estado de todo.
Por supuesto, esto depende del problema particular que esté manejando, pero diría que en la mayoría de los casos, si necesita tal función, su diseño no está muy orientado a objetos.
¿Qué está tratando de lograr? Si estos números a y b deben operar juntos, tal vez pertenecen a una clase MyClass y lo que necesita es un método de instancia. Algo como:
class MyClass{
private int a;
private int b;
//getters/setters/constructors
public boolean dothings(){
// perform some computation on a and b here
if (success)
return true;
else return false;
//why not just return success?
}
}
Un método para modificar una var en la pila de llamadas puede ser bastante confuso.
Idealmente, el lenguaje debería permitir devolver múltiples valores, lo que resolverá este tipo de problemas.
Pero antes de eso, si tiene que usar un parámetro "fuera", debe hacerlo.