My problem is quite complex and hard to explain, so I built a smaller example. It's still complex, but let me try my best... You can download the full example here: https://mega.co.nz/#!400lSbqa!NoyflWYk6uaQToVDEwXyn22Bdcn_6GdTxB6dPUfU5FU
I recommend importing this into your favourite IDE and playing around.
Imagine you are programming a multiplayer game. It is supposed to have a world with entities. The code should be split into server, client, and shared stuff.
Each of my entities consist of 3 files:
Because I can only derive from one class but want my client/server entities to have some shared code too, I tried it with a delegation-style structure. Let's name the actual entity [default], mark interfaces with *'s and extends / implements
with <-
- *BaseEntity*
- [DefaultBaseEntity] <- *BaseEntity*
- *ClientEntity* <- *BaseEntity*
- [DefaultClientEntity] <- [DefaultBaseEntity], *ClientEntity*
- *ServerEntity* <- *BaseEntity*
- [DefaultServerEntity] <- [DefaultBaseEntity], *ServerEntity*
This way, I can also duck-typing-access The server/client specific implementations plus the base implementations with only holding ClientEntity/ServerEntity.
Now I want to program a world containing those entities. The world's code shall also be split into three parts and be generic to either contain server or client entities.
package base;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseWorld<E extends BaseEntity> {
private List<E> entities;
public BaseWorld() {
entities = new ArrayList<>();
}
public void addEntity(E entity) {
entity.setWorld(this);
entities.add(entity);
}
public List<E> getEntities() {
return entities;
}
public void doStuffWithBuilding(E entity) {
entity.doBasestuff();
}
}
package client;
import base.BaseWorld;
public class ClientWorld extends BaseWorld<ClientEntity>{
}
package server;
import base.BaseWorld;
public class ServerWorld extends BaseWorld<ServerEntity> {
}
As you see, I am giving my entities a backreference to the world they are in. And this contains the actual problem.
Here's a look into the corresponding entity code:
package base;
public class DefaultBaseEntity implements BaseEntity {
private BaseWorld world;
@Override
public void doBasestuff() {
System.out.println("I am base entity");
}
@Override
public void setWorld(BaseWorld world) {
this.world = world;
}
@Override
public BaseWorld getWorld() {
return world;
}
}
Now this works, but BaseWorld is a raw type. Obviously, every IDE starts to complain. I also do not want to suppress warnings.
I cannot use wildcard types like BaseWorld<? extends BaseEntity>
either, because they produce compile errors, when I call world methods like doStuffWithBuilding():
package client;
import base.DefaultBaseEntity;
public class DefaultClientEntity extends DefaultBaseEntity implements ClientEntity {
@Override
public void doClientstuff() {
System.out.println("I am client");
getWorld().doStuffWithBuilding(this);
}
}
The method doStuffWithBuilding(capture#1-of ? extends BaseEntity) in the type BaseWorld is not applicable for the arguments (DefaultClientEntity)
Is there any solution to this? I tried removing the set/getWorld() from the base interface and adding it to client and server, but that was very clunky and causes a lot of repitition because of the delegation.
You can probably get around this by parameterizing DefaultBaseEntity:
public class DefaultBaseEntity <E extends BaseEntity>
implements BaseEntity<E> {
private BaseWorld<E> world;
// ...
}
public class DefaultClientEntity extends DefaultBaseEntity<ClientEntity>
implements ClientEntity {
// ...
}
Observe that DefaultClientEntity
does not need to be parameterized (at least not for this purpose), even though its superclass is.
Update: Furthermore, you can perform analogous parameterization with your interfaces:
interface BaseEntity <E extends BaseEntity> {
public void setWorld(BaseWorld<E> world);
// ...
}
interface ClientEntity extends BaseEntity<ClientEntity> {
// ...
}
The example DefaultBaseEntity
code above is updated to implement that generic BaseEntity
interface.