Search code examples
javagenericsstructuredelegation

avoid raw generic type in inheritance/delegation structure


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:

  • the base, which contains shared code and resources like the name
  • the clientside, which derive the base and contain rendering etc.
  • the serverside, which also derive the base and contain network events etc.

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.


Solution

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