Una forma de devolver múltiples valores de retorno de un método: poner el método dentro de la clase que representa el valor de retorno ¿Es un buen diseño?

15

Necesito devolver 2 valores de un método. Mi enfoque es el siguiente:

  1. crear una clase interna con 2 campos que se usarán para mantener esos 2 valores
  2. poner el método dentro de esa clase
  3. instanciar la clase y llamar al método

Lo único que cambiará en el método es que al final asignará esos 2 valores a los campos de la instancia. Entonces puedo abordar esos valores haciendo referencia a los campos de ese objeto.

¿Es un buen diseño y por qué?

dhblah
fuente
Otra opción (probablemente una mala): ver BitInteger[] java.math.BigInteger.divideAndRemainder(BitInteger val). Devuelve 2 enteros como sus valores de retorno en una matriz.
earlSin nombre
¿De qué tipo son los dos valores que desea devolver?
Tulains Córdova
Posible duplicado - stackoverflow.com/questions/12668186/… .
m3th0dman

Respuestas:

15

Yo diría esto en las siguientes líneas:

  • ¿Por qué exactamente su método devuelve múltiples valores? ¿De qué tipo de cohesión estamos hablando? ¿Deberían esos valores ser realmente campos en una sola clase, o simplemente son devueltos por el mismo método, pero por lo demás no están relacionados? Si es lo último, puede considerar dividir el método en dos métodos. Editar: usa tu juicio aquí; a veces un tipo de cohesión "coincidente" puede ser la mejor opción. Otra opción es utilizar una construcción de par o tupla, aunque en OOP, estos generalmente no se ven en API públicas (algunas excepciones notables son colecciones estándar, etc.).
  • Si los valores merecen formar una clase, probablemente aconsejaría no usar una clase interna. Las clases internas generalmente se usan como detalles de implementación internos, que están ocultos desde el exterior. ¿Hay alguna razón por la cual este resultado no debería ser una clase "completa", por derecho propio?
  • Además de mantener datos, ¿qué operaciones son aplicables a esta nueva clase? En el diseño orientado a objetos, desea tener el comportamiento relacionado cerca de los datos relevantes (que también parecen ser sus intenciones). ¿Debería el método al que te refieres no vivir en esta clase?

Para resumir, vería si puedo convertir este "objeto de datos" en una clase completa con datos y comportamiento. Como comentario adicional, es posible que desee hacer que la clase sea inmutable, ya que su estado se establece una vez. Hacerlo inmutable ayudará a evitar que se configure incorrectamente o se modifique más tarde (por ejemplo, alguien que configura uno de los campos como nulo y lo pasa).

Editar: como Patkos Csaba señala correctamente, el principio que se aplica aquí es el Principio de responsabilidad única ( SRP ): la clase que está tratando de crear realmente debería tener una responsabilidad (definida como una razón para cambiar ). Esta guía de diseño debería ayudarlo a determinar si sus dos campos pertenecen o no a una sola clase. Para seguir con el ejemplo de Wikipedia, su clase podría verse como un tipo de informe, en cuyo caso se ajusta a SRP, pero es difícil comentar sin más información.

Daniel B
fuente
Si bien estoy de acuerdo con la idea general de esta respuesta, hay casos legítimos en los que dos datos estrechamente relacionados se calculan juntos, pero no tiene sentido unirlos de otra manera en cualquier otro lugar del programa. En tal caso, puede estar lo suficientemente limpio como para devolver algo como Pair<OneClass, AnotherClass>. Algunas personas estarían en desacuerdo . En cualquier caso, Pairdebe ser un detalle de implementación y nunca aparecer en un método de API público.
9000
@ 9000 Estoy de acuerdo; En realidad, quise decir que la palabra se considera literalmente en este caso, es decir, dividir el método no siempre es la mejor solución, es solo una indicación aproximada en esa dirección. Haré una edición en ese sentido.
Daniel B
1
Buena respuesta. Lo único que agregaría es una referencia al Principio de Responsabilidad Individual (SRP) ( en.wikipedia.org/wiki/Single_responILITY_principle ). Si se marca, es muy posible que el método en discusión sea solo una simple violación de SRP y una división sea una solución simple. En mi experiencia, cada vez que un método quiere devolver 2 o más valores, en el 90% de los casos hay 2 métodos allí u otra clase debería extraerse.
Patkos Csaba
@PatkosCsaba gracias, hizo una edición para incluirlo. Normalmente me limito a explicar las cosas en términos de acoplamiento y cohesión, pero creo que los principios SÓLIDOS son vistos como las reglas básicas para vivir en estos días.
Daniel B
@DanielB: Veo SOLID como un nivel superior y probablemente conceptos más fáciles de entender. El acoplamiento y la cohesión siguen siendo la línea de base, pero son más bajos. SOLID hace un gran uso del acoplamiento y la cohesión para explicar sus principios y presentarlos en un nivel más genérico.
Patkos Csaba
10

Existe el concepto de una tupla que se presenta en otros lenguajes, como Python.

Se podría devolver una instancia de esta clase genérica que es fácilmente reutilizable:

public class TypedTuple<L, R> implements Serializable {
private static final long serialVersionUID = 1L;

  protected L left;
  protected R right;

  protected TypedTuple() {
    // Default constructor for serialization
  }

  public TypedTuple(L inLeft, R inRight) {
    left = inLeft;
    right = inRight;
  }

  public L getLeft() {
    return left;
  }

  public R getRight() {
    return right;
  }
}
Cuga
fuente
2
A veces es bueno tener un método estático genérico create(), para evitar tener que especificar los parámetros de tipo en el constructor. Además, nombraría esta clase en Pairlugar de Tuple, dado que solo puede representar 2-tuplas de valores.
augurar
5

Parece que esta clase le quita la responsabilidad a otra clase, y esto me hace pensar que este diseño no es excelente.

Para un método que devuelve valores multibles, prefiero

  • devolver un contactor genérico (por ejemplo, una Lista o Mapa) que contiene los valores de retorno

o

  • crear una clase para el valor de retorno, que contiene solo los campos necesarios + getters + un constructor con todos los campos

Ejemplo para la segunda opción:

public Class FooBarRetval {
   private String foo;
   private int bar;

   public FooBarRetval (String foo, int bar) {
      this.foo = foo;
      this.bar = bar;
   }

   public String getFoo() {
      return foo;
   }

   public int getBar() {
      return bar;
   }
}
usuario281377
fuente
Hacer públicos los campos agrega la opción de alterar los valores por separado, aunque claramente los valores tienen una relación (de lo contrario, no tendrían que ser devueltos por el mismo método). Desalentaría mucho este patrón en esta situación especial. Pero el punto principal es que la discusión de los atributos públicos frente a los privados no tiene nada que ver con la pregunta de los OP y no veo ninguna razón para la última oración en una buena respuesta.
scarfridge
El primero debe ser preferido.
Juanin
Scarfridge: punto tomado, última oración eliminada.
usuario281377
2
Simplemente use los campos públicos finales, no hay razón para perder el tiempo con los accesores.
augurar
0

Respuesta corta: puede devolver una matriz o una Lista con los dos valores.

Yo personalmente escribiría dos métodos distintos, como

int x = obj.getX();
int y = obj.getY();
Tulains Córdova
fuente