Come puoi scomporre un costruttore?


21

Diciamo che ho una classe Enemy, e il costruttore sarebbe simile a:

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

Questo sembra male perché il costruttore ha così tanti parametri, ma quando creo un'istanza Enemy ho bisogno di specificare tutte queste cose. Voglio anche questi attributi nella classe Enemy, in modo da poter scorrere un elenco di questi e ottenere / impostare questi parametri. Stavo forse pensando di sottoclasse Enemy in EnemyB, EnemyA, mentre codificavo il loro maxHp e altri attributi specifici, ma poi avrei perso l'accesso ai loro attributi hardcoded se avessi voluto iterare attraverso un elenco di Enemy (composto da EnemyA, EnemyB e EnemyC di).

Sto solo cercando di imparare a programmare in modo pulito. Se fa la differenza, lavoro in Java / C ++ / C #. Ogni punto nella giusta direzione è apprezzato.


5
Non c'è niente di male nell'avere un costruttore che lega tutti gli attributi. In effetti, in alcuni ambienti di persistenza, è necessario. Nulla dice che non puoi avere più costruttori, forse con il metodo di verifica della validità da chiamare dopo aver fatto la costruzione saggia.
BobDalgleish

1
Dovrei chiedermi se hai mai intenzione di costruire oggetti Enemy in codice usando valori letterali. Se non lo fai, e non vedo perché, allora costruisci costruttori che estraggono i dati da un'interfaccia di database, o una stringa di serializzazione, o ...
Zan Lynx


Risposte:


58

La soluzione è raggruppare i parametri in tipi compositi. Larghezza e altezza sono concettualmente correlate: specificano le dimensioni del nemico e di solito saranno necessarie insieme. Potrebbero essere sostituiti con un Dimensionstipo, o forse un Rectangletipo che include anche la posizione. D'altra parte, potrebbe essere più sensato raggruppare positione speedinserire un MovementDatatipo, specialmente se l'accelerazione entra in un secondo momento. Dal contesto Presumo maxHp, attackDamage, defense, ecc appartengono anche insieme in un Statstipo. Quindi, una firma rivista potrebbe essere simile a questa:

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

I dettagli precisi su dove tracciare le linee dipenderanno dal resto del codice e da quali dati vengono comunemente utilizzati insieme.


21
Vorrei anche aggiungere che avere così tanti valori potrebbe indicare una violazione del principio di responsabilità singola. Raggruppare i valori in oggetti specifici è il primo passo per separare tali responsabilità.
Euforico

2
Non penso che l'elenco dei valori sia un problema di SRP; la maggior parte di questi sono probabilmente destinati a costruttori di classi base. Ogni classe nella gerarchia può avere un'unica responsabilità. Enemyè solo la classe che prende di mira la Player, ma la loro classe base comune ha Combatantbisogno delle statistiche di combattimento.
Salterio

@MSalters Non indica necessariamente un problema SRP, ma potrebbe. Se ha bisogno di fare abbastanza scricchiolio dei numeri, quelle funzioni potrebbero trovare la loro strada nella classe Enemy quando dovrebbero essere funzioni statiche / libere (se usa Dimensions/ MovementDatacome semplici vecchi contenitori di dati) o metodi (se le trasforma in dati astratti tipi / oggetti). Ad esempio, se non avesse già creato un Vector2tipo, avrebbe potuto finire per fare matematica vettoriale Enemy.
Doval,

24

Potresti dare un'occhiata al modello Builder . Dal collegamento (con un esempio del modello rispetto alle alternative):

[Il] modello Builder è una buona scelta quando si progettano classi i cui costruttori o fabbriche statiche avrebbero più di una manciata di parametri, specialmente se la maggior parte di questi parametri sono opzionali. Il codice client è molto più facile da leggere e scrivere con i costruttori che con il tradizionale modello di costruzione telescopica, e i costruttori sono molto più sicuri di JavaBeans.


4
Un frammento di codice funzione sarebbe utile. Questo è un ottimo modello per costruire oggetti o strutture complicate con vari input. Potresti anche specializzare i costruttori, come EnemyABuilder, EnemyBBuilder, ecc. Che incapsulano le varie proprietà condivise. Questo è un po 'il rovescio della medaglia del modello Factory (come indicato di seguito), ma la mia preferenza personale è per Builder.
Rob

1
Grazie, sia il modello Builder che i modelli Factory sembrano funzionare bene con quello che sto cercando di fare in generale. Penso che una combinazione di Builder / Factory e il suggerimento di Doval potrebbe essere quello che sto cercando. Modifica: immagino di poter contrassegnare una sola risposta; Lo darò a Doval poiché risponde alla domanda sull'argomento, ma gli altri sono ugualmente utili al mio problema specifico. Grazie a tutti.
Travis

Penso che valga la pena notare che se la tua lingua supporta tipi di fantasmi, puoi scrivere un modello di builder che impone che vengano chiamate alcune / tutte le funzioni di SetX. Permette anche di assicurarsi che vengano chiamati anche una sola volta (se lo si desidera).
Thomas Eding,

1
@ Mark16 Come menzionato nel collegamento, > Il modello Builder simula i parametri opzionali denominati presenti in Ada e Python. Hai detto di usare anche C # nella domanda e che il linguaggio supporta argomenti nominati / opzionali (a partire da C # 4.0), quindi potrebbe essere un'altra opzione.
Bob

5

L'uso di sottoclassi per preimpostare alcuni valori non è auspicabile. Sottoclasse solo quando un nuovo tipo di nemico ha un comportamento diverso o nuovi attributi.

Il modello di fabbrica viene solitamente utilizzato per astrarre sull'esatta classe utilizzata, ma può anche essere utilizzato per fornire modelli per la creazione di oggetti:

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

Riserverei la sottoclasse alle classi che rappresentano oggetti che potresti voler usare indipendentemente, ad esempio la classe di caratteri in cui tutti i personaggi, non solo i nemici hanno nome, velocità, maxHp o una classe per rappresentare gli sprite che hanno una presenza sullo schermo con larghezza, altezza, posizione.

Non vedo nulla di intrinsecamente sbagliato in un costruttore con molti parametri di input ma se vuoi dividerlo un po 'potresti avere un costruttore che imposta la maggior parte dei parametri e un altro costruttore (sovraccarico) che può essere usato per impostare quelli specifici e impostarne altri sui valori predefiniti.

A seconda della lingua che scegli di utilizzare, alcuni possono impostare valori predefiniti per i parametri di input del costruttore come:

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

0

Un esempio di codice da aggiungere alla risposta di Rory Hunter (in 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;
       }
   }
}

Ora puoi creare nuove istanze di Enemy in questo modo:

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

1
Il programmatore è in tour domande e risposte concettuali che dovrebbero spiegare le cose . Lanciare i dump del codice invece della spiegazione è come copiare il codice dall'IDE alla lavagna: può sembrare familiare e persino a volte comprensibile, ma sembra strano ... solo strano. La lavagna non ha compilatore
moscerino
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.