Dovrei forzare l'utente a racchiudere gli argomenti o consentire loro di passare i dati al costo di avere più argomenti?

3

Ho il costruttore:

public class Player {
    final private DoubleDuple position;
    final private DoubleDuple momentum;

    public Player(double xPos, double yPos, double xMom, double yMom) {
        position = new DoubleDuple(xPos, yPos);
        momentum = new DoubleDuple(xMom, yMom);
    }
}

Devo permettere che lo slancio venga passato poiché sto rendendo la classe immutabile, quindi avrò bisogno della capacità di costruire un Player con una posizione diversa, ma con lo slancio precedente.

Il problema è che questo costruttore ora ha 4 campi e potrebbe diventare più grande se in seguito ci sono più campi che devo passare in un oggetto di nuova costruzione (salute, ad esempio).

Potrei combinare gli argomenti forzando l'utente a racchiudere coordinate posizionali e valori di quantità di moto in un DoubleDuple prima di passarli al costruttore, il che ridurrà il numero di argomenti. Penso che questo renderà anche più chiaro che i primi 2 e secondi 2 argomenti sono associati tra loro. Sfortunatamente, questo ha anche l'effetto di rendere più massiccia la costruzione di ogni oggetto, dal momento che ci sarà un po 'di% extra di% in% di mescolanza.

È meglio fare in modo che argomenti simili vengano inclusi internamente passati in wrapper al costruttore, o dovrebbero essere passati in "loose"?

    
posta Carcigenicate 09.08.2015 - 02:08
fonte

1 risposta

4

Puoi considerare di consentire ai chiamanti di passare in DoubleDuple . (Per risparmiare un po 'di digitazione, di seguito userò Vec2 al posto di DoubleDuple .)

Esempio:

public class Vec2 {
    public final double x;
    public final double y;
    public Vec2(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public Vec2 add(Vec2 other) {
        return new Vec2(this.x + other.x, this.y + other.y);
    }
    // ... there should be other methods applicable to 2D vectors.
}

Anche se è ragionevole rendere immutabili gli oggetti di piccole dimensioni (per consentire l'accesso in sola lettura condiviso ai getter senza doverli clonare in modo difensivo), non sono sicuro di rendere immutabili gli oggetti di grandi dimensioni, a causa della necessità di clonare ogni uno dei suoi campi quando è necessario apportare una modifica.

In altre parole, se l'oggetto è di grandi dimensioni (con molti campi) e se il suo uso tipico (semanticamente) spesso richiede modifiche e se non è necessario mantenere lo stato dell'oggetto precedente (come nel modello di memento), quindi penserei che modificare l'oggetto sul posto sarebbe un modo più naturale di programmare in Java.

Nota che questo parere non è applicabile ad altre lingue, perché le lingue progettate per la programmazione funzionale avrebbero altri meccanismi per risolvere questo problema.

Pertanto, implementerei Player come segue:

public class Player
{
    // ... same fields and constructors as yours
    // ... but they will be mutable.

    public void setPosition(Vec2 newPos) {
        // Vec2 being immutable means you do not have to worry 
        // about the caller mutating newPos after calling 
        // Player.setPosition(Vec2)
        // Meanwhile, Player is mutable.
        this.pos = newPos; 
    }
    public void addPosition(Vec2 deltaPos) {
        // The cost we pay for making Vec2 immutable 
        // (in terms of lines of code) is minimal. However,
        // same cannot be said of the cost of making Player
        // immutable. My suspicion is that it could greatly
        // increase the lines of code for Player.
        this.pos = this.pos.add(deltaPos);
    }
    public Vec2 getPosition() {
        // Vec2 being immutable means you do not have to worry 
        // about the caller mutating the returned Vec2, which
        // would have caused havoc on the Player.pos field
        // if Vec2 isn't immutable.
        return this.pos;
    }
    // ... and others
}

Infine, puoi considerare di mettere insieme pos e mom , in una classe Motion , e poi aggiungerlo come un campo alla classe Player .

// Motion can be mutable or immutable; 
// shown below is the mutable implementation.
public class Motion
{
    private Vec2 pos;
    private Vec2 mom;
    public Motion(Vec2 pos, Vec2 mom) { ... }
    public void setPosition(Vec2 newPos) { ... }
    public void addPosition(Vec2 deltaPos) { ... }
    public Vec2 getPosition() { ... }
    public void setMomentum(Vec2 newMom) { ... }
    public void addMomentum(Vec2 deltaMom) { ... }
    public Vec2 getMomentum() { ... }
    // ... add other methods as needed.
}

public class Player {
    private Motion motion;
    // ... constructors and other fields

    // ... Now, we face a dilemma. Motion has a lot of methods,
    // ... but we don't want to copy all those methods here.

    // Option 1 - bite the bullet. Each method will delegate
    // to the method of same name on the Motion instance.

    public void setPosition(Vec2 newPos) { ... }
    public void addPosition(Vec2 deltaPos) { ... }
    public Vec2 getPosition() { ... }
    public void setMomentum(Vec2 newMom) { ... }
    public void addMomentum(Vec2 deltaMom) { ... }
    public Vec2 getMomentum() { ... }

    // Option 2 - getters and setters
    // As discussed above, getters and setters benefit from
    // immutable objects by not having to defensively clone them.
    public Motion getMotion() { return motion; }
    public void setMotion(Motion newMotion) { motion = newMotion; }

    // Option 3 
    // Requires Java 8 Lambda. 
    // Requires the MotionUpdater interface below.
    // Motion can be mutable or immutable; doesn't care.
    public void changeMotion(MotionUpdater motionUpdater) {
        this.motion = motionUpdater.update(this.motion);
    }
}

// Used by Option 3 above. It can be implemented by a class
// (class, inner class, inline class, etc.), or Java 8 Lambda.
public interface MotionUpdater
{
    Motion update(Motion oldMotion);
}

Facendo questo refactoring (classe extract), ora la classe Motion ha solo due campi, e quindi si qualifica per il trattamento di immutabilità, cioè la clonazione di una Motion per modificare i suoi valori implica solo copiare due riferimenti a Vec2 immutabili.

Per riassumere, gli oggetti immutabili sono adatti quando:

  • Quando è necessario dalla logica dell'applicazione (richiede record permanenti di oggetti, occorrono copie congelate di oggetti condivisi da più thread, ecc.) o ...
  • Quando gli oggetti sono molto piccoli (2-4 campi) e non fanno parte di alcuna catena ereditaria. Nota che le implementazioni dell'interfaccia sono ancora consentite.
risposta data 09.08.2015 - 08:08
fonte

Leggi altre domande sui tag