¿Cómo puedes descomponer un constructor?


21

Digamos que tengo una clase de Enemigo, y el constructor se vería así:

public Enemy(String name, float width, float height, Vector2 position, 
             float speed, int maxHp, int attackDamage, int defense... etc.){}

Esto se ve mal porque el constructor tiene muchos parámetros, pero cuando creo una instancia de Enemy necesito especificar todas estas cosas. También quiero estos atributos en la clase Enemy, para poder iterar a través de una lista de ellos y obtener / establecer estos parámetros. Estaba pensando en subclasificar Enemy en EnemyB, EnemyA, mientras codificaba su maxHp y otros atributos específicos, pero luego perdería el acceso a sus atributos codificados si quisiera recorrer una lista de Enemy (que consta de EnemyA, EnemyB y EnemyC's).

Solo estoy tratando de aprender a codificar limpiamente. Si hace la diferencia, trabajo en Java / C ++ / C #. Cualquier punto en la dirección correcta es apreciado.


55
No tiene nada de malo tener un constructor que una todos los atributos. De hecho, en algunos entornos de persistencia, es obligatorio. Nada dice que no puede tener múltiples constructores, quizás con el método de verificación de validez que se llamará después de hacer la construcción por partes.
BobDalgleish

1
Tendría que preguntar si alguna vez tiene la intención de construir objetos Enemigos en código usando literales. Si no lo hace, y no veo por qué lo haría, entonces construya constructores que extraigan los datos de una interfaz de base de datos, o una cadena de serialización, o ...
Zan Lynx


Respuestas:


58

La solución es agrupar los parámetros en tipos compuestos. El ancho y la altura están relacionados conceptualmente: especifican las dimensiones del enemigo y generalmente se necesitarán juntos. Podrían reemplazarse con un Dimensionstipo, o tal vez un Rectangletipo que también incluya la posición. Por otro lado, podría tener más sentido agrupar positiony speedformar un MovementDatatipo, especialmente si la aceleración luego entra en escena. A partir del contexto Asumo maxHp, attackDamage, defense,, etc también pertenecen juntos en un Statstipo. Entonces, una firma revisada podría verse así:

public Enemy(String name, Dimensions dimensions, MovementData movementData, Stats stats)

Los detalles finos de dónde dibujar las líneas dependerán del resto de su código y qué datos se usan comúnmente juntos.


21
También agregaría que tener tantos valores podría indicar una violación del Principio de responsabilidad única. Y agrupar valores en objetos específicos es el primer paso para separar esas responsabilidades.
Eufórico

2
No creo que la lista de valores sea un problema de SRP; la mayoría de ellos probablemente estén destinados a constructores de clase base. Cada clase en la jerarquía puede tener una sola responsabilidad. Enemyes solo la clase que apunta al Player, pero su clase base común Combatantnecesita las estadísticas de lucha.
MSalters

@MSalters No necesariamente indica un problema de SRP, pero podría. Si necesita hacer suficientes cálculos numéricos, esas funciones podrían llegar a la clase Enemigo cuando deberían ser funciones estáticas / libres (si usa Dimensions/ MovementDatacomo contenedores de datos antiguos) o métodos (si los convierte en datos abstractos tipos / objetos). Como ejemplo, si aún no hubiera creado un Vector2tipo, podría haber terminado haciendo matemática vectorial Enemy.
Doval

24

Es posible que desee echar un vistazo al patrón Builder . Desde el enlace (con ejemplos del patrón versus las alternativas):

[El] patrón de constructor es una buena opción cuando se diseñan clases cuyos constructores o fábricas estáticas tendrían más de un puñado de parámetros, especialmente si la mayoría de esos parámetros son opcionales. El código del cliente es mucho más fácil de leer y escribir con los constructores que con el patrón de constructor telescópico tradicional, y los constructores son mucho más seguros que JavaBeans.


44
Un breve fragmento de código sería útil. Este es un gran patrón para construir objetos o estructuras complicadas con varias entradas. También puede especializar los constructores, como EnemyABuilder, EnemyBBuilder, etc. que encapsulan las diversas propiedades compartidas. Este es el otro lado del patrón Factory (como se responde a continuación), pero mi preferencia personal es Builder.
Rob

1
Gracias, tanto el patrón Builder como los patrones Factory parecen funcionar bien con lo que estoy tratando de hacer en general. Creo que una combinación de Builder / Factory y la sugerencia de Doval podría ser lo que estoy buscando. Editar: supongo que solo puedo marcar una respuesta; Se lo daré a Doval ya que responde la pregunta del tema, pero los demás son igualmente útiles para mi problema específico. Gracias a todos.
Travis

Creo que vale la pena señalar que si su idioma admite tipos fantasma, entonces puede escribir un patrón generador que haga cumplir que se invoquen algunas / todas las funciones SetX. También le permite a uno asegurarse de que solo se les llame una vez (si lo desea).
Thomas Eding

1
@ Mark16 Como se menciona en el enlace, > El patrón Builder simula los parámetros opcionales con nombre que se encuentran en Ada y Python. Mencionó que también usa C # en la pregunta, y que el lenguaje admite argumentos con nombre / opcionales (a partir de C # 4.0), por lo que puede ser otra opción.
Bob

5

Usar subclases para preestablecer algunos valores no es deseable. Solo subclase cuando un nuevo tipo de enemigo tiene un comportamiento diferente o nuevos atributos.

El patrón de fábrica generalmente se usa para abstraer sobre la clase exacta utilizada, pero también se puede usar para proporcionar plantillas para la creación de objetos:

class EnemyFactory {

    // each of these methods is essentially a template for a kind of enemy

    Enemy enemyA(String name, ...) {
        return new Enemy(name, ..., presetValue, ...);
    }

    Enemy enemyB(String name, ...) {
        return new Enemy(name, ..., otherValue, ...);
    }

    Enemy enemyC(String name, ...) {
        return new EnemySubclass(name, ..., otherValue, ...);
    }

    ...
}

EnemyFactory factory = new EnemyFactory();
Enemy a = factory.enemyA("fred", ...);
Enemy b = factory.enemyB("willy", ...);

0

Me gustaría reservar subclases para las clases que representan objetos que tal vez quieras usar de forma independiente, por ejemplo, la clase de personaje donde todos los personajes, no solo los enemigos, tienen nombre, velocidad, maxHp o una clase para representar sprites que tienen presencia en pantalla con ancho, altura, posición.

No veo nada inherentemente incorrecto con un constructor con muchos parámetros de entrada, pero si desea dividirlo un poco, entonces podría tener un constructor que configure la mayoría de los parámetros y otro constructor (sobrecargado) que pueda usarse para establecer valores específicos y hacer que otros establezcan valores predeterminados.

Dependiendo del idioma que elija usar, algunos pueden establecer valores predeterminados para los parámetros de entrada de su constructor como:

Enemy(float height = 42, float width = 42);

0

Un ejemplo de código para agregar a la respuesta de Rory Hunter (en Java):

public class Enemy{
   private String name;
   private float width;
   ...

   public static class Builder{
       private Enemy instance;

       public Builder(){
           this.instance = new Enemy();
       }


       public Builder withName(String name){
           instance.name = name;
           return this;
       }

       ...

       public Enemy build(){
           return instance;
       }
   }
}

Ahora, puedes crear nuevas instancias de Enemy como esta:

Enemy myEnemy = new Enemy.Builder().withName("John").withX(x).build();

1
Los programadores se gira preguntas conceptuales y son respuestas espera que explicar las cosas . Lanzar volcados de código en lugar de una explicación es como copiar código del IDE a la pizarra: puede parecer familiar e incluso a veces comprensible, pero se siente extraño ... simplemente extraño. Whiteboard no tiene compilador
mosquito
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.