This is the code in question, comments point it out:
class Actor extends Entity {
private MutableVector2f position;
private MutableIdentity identity;
public Actor(MutableVector2f initialPosition, MutableIdentity initialIdentity) {
super(initialPosition, initialIdentity); // !! pass to supertype !!
this.position = initialPosition; // !! reference in subtype !!
this.identity = initialIdentity; // !! "" "" !!
}
public void moveTowards(Vector2f targetPosition) {
// !! change position member, purpose of keeping reference !!
}
public void changeNameTo(String name) {
identity.changeNameTo(name);
}
}
It seems like a code smell. Is it? Maybe there's a problem with the type structure?
Are there any downfalls to this type of design?
The base class Entity
is used for any object with a position:
abstract class Entity {
private Vector2f position;
private Identity identity;
public Entity(Vector2f position, Identity identity) {
this.position = position;
this.identity = identity;
}
public Vector2f getPosition() {
return position;
}
public Identity getIdentity() {
return identity;
}
}
The Actor
subtype has a mutable position to reduce the amount of objects created (could have hundreds of thousands of actors moving around and changing identity)
The WorldObject
subtype has an immutable position and identity:
class WorldObject extends Entity {
public WorldObject(ImmutableVector2f position, ImmutableIdentity identity) {
super(position, identity);
}
}
Vector2f
is an interface which 'ImmutableVector2fand
MutableVector2f` implement:
interface Vector2f {
float getX();
float getY();
}
MutableVector2f
exposes methods such as add
, multiply
and normalize
.
Identity
follows the same structure (is an interface, has immutable and mutable subtypes)
I feel this may violate LSP. The purpose of the interfaces is to prevent modification from an Entity
perspective.
It's not to say Entity
is immutable, but to discourage mutation at that level of abstraction, since systems using Entity
should only be reading values.
Keeping a reference in the subclass will prevent the need to cast Entity#getPosition()
, but it feels wrong. Making the properties generic could lead to a long list of type parameters if I were to continue scaling up Entity
in this way.
Is it possible that Entity#getPosition()
should be abstract, and the subtypes should maintain their own position variables? It would require me to declare getters in the subtypes (duplicate code), so I'm not a huge fan of the idea.
Entity
for the type ofVector2f
?Entity
in this way. I also don't wantActor#getPosition
to be exposed as aMutableVector2f
, as that'll further encourage users to simply mutate the vector instead of using behavioral methods. Of course they can still castEntity#getPosition
, or even use reflection if they really wanted, but the point is to discourage us I such tactics by making the (correct) alternatives easier. I edited my postgetPosition
return after a call tomoveTowards
- the original value or the updated value? As a user of that class, I'd expect the updated position, but withgetPosition
defined in the base class and using a private non-updated member, it looks to me that it's the original value. (Note: I use C#, not Java, and there might be implementation differences)