3 added 1231 characters in body
source | link
// 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);
}
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.
}
// 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);
}
2 added 1231 characters in body
source | link

Finally, you can consider putting pos and mom together, into a class Motion, and then add it as a field to the Player class.

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.
}

By making this refactoring (extract class), now Motion class only has two fields, and therefore it qualifies for the immutability treatment, i.e. cloning a Motion to modify its values only involves copying two references to immutable Vec2.

To summarize, immutable objects are suitable when:

  • When it's needed by the application logic (need permanent records of objects; need frozen copies of objects shared by multiple threads, etc), or ...
  • When objects are very small (2-4 fields) and not part of any inheritance chain. Note that interface implementations is still allowed.

Finally, you can consider putting pos and mom together, into a class Motion, and then add it as a field to the Player class.

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.
}

By making this refactoring (extract class), now Motion class only has two fields, and therefore it qualifies for the immutability treatment, i.e. cloning a Motion to modify its values only involves copying two references to immutable Vec2.

To summarize, immutable objects are suitable when:

  • When it's needed by the application logic (need permanent records of objects; need frozen copies of objects shared by multiple threads, etc), or ...
  • When objects are very small (2-4 fields) and not part of any inheritance chain. Note that interface implementations is still allowed.
1
source | link

You can consider allowing your callers to pass in DoubleDuple. (To save some typing, below I will use Vec2 in place of DoubleDuple.)

Example:

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.
}

While it makes sense to make small objects immutable (to allow worry-free shared read-only access to getters without having to defensively clone it), I am not sure about making large objects immutable, because of the need to clone every one of its fields when a change needs to be made.

In other words, if the object is large (having many fields), and if its typical usage (semantically) often requires modification, and if there is no need to keep the old object state around (as in memento pattern), then I would think that modifying the object in-place would be a more natural way of programming in Java.

Note that this opinion isn't applicable to other languages, because languages that are designed for functional programming would have other mechanisms for resolving this issue.

Therefore, I would implement Player as follows:

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
}