1

Suppose I have a Player class with an Inventory:

public abstract class Player {

    private final List<GameObject> gameObjects;

    public Player(){
        this.gameObjects = new ArrayList<GameObject>();
    }

    public add(GameObject item);
    public drop()
}

I also have two items, Night Vision Goggles and a Tablet, with the following behaviors:

NVG:

  • turn on/off
  • utilize
  • zoom

Tablet:

  • turn on/off
  • change system date
  • schedule backup
  • change username

For basic usage, I could have the following interface and implementation:

GameObject:

public interface GameObject {

    boolean prepare();
    boolean utilize();
    boolean cleanup();
}

NightVisionGoggles:

public final class NightVisionGoggles implements GameObject {

    // constructor left out

    @override
    public boolean prepare(){
        System.out.println("Turning on Night Vision")
    }

    @override
    public boolean utilize(){
        System.out.println("Looking around in the dark")
    }

    @override
    public boolean cleanup(){
        System.out.println("Taking off Night Vision")
    }
}

Tablet:

public final class DataPad implements GameObject {

    // constructor left out

    @override
    public boolean prepare(){
        System.out.println("Turning on Datapad")
    }

    @override
    public boolean utilize(){
        System.out.println("Browsing data")
    }

    @override
    public boolean cleanup(){
        System.out.println("Shutting down for the night")
    }
}

The properties missing are the NVG's Zoom (0, 50, 100), and the tablets backup, system time and username. Both are properties but obviously of different types.

Because both are settings of each object, I could restructure my interface like this:

public interface GameObject {

    boolean prepare();
    boolean utilize();
    boolean cleanup();

    boolean tryChange(String name, String value);
    boolean tryChange(String name, int value);
    boolean tryChange(String name, DataTime value);

    Map<String,String getSettings();
    Map<String,int> getSettings();
    Map<String,LocalDateTime> getSettings();
}

This would mean the objects would require at least 2 Maps<K,V>. The NVG can return false for the tryChange(String name, DataTime value) method and return an empty collection in the Map<String,LocalDateTime> getSettings() method. The tablet might use all 3, because a number of settings are either string, int or LocalDateTime.

It doesn't feel right to me that the NVG would be aware of the LocalDateime.

As the title says, without violating LSP and SRP how would you structure the interface and classes so that I can have the required behavior and store it in one collection?

All of this is assuming that a player inventory is only one collection, in real world games I don't know how it's modeled or stored, but I don't want to avoid the issue.

1

All of this is assuming that a player inventory is only one collection

But that is actually one approach to solve the problem here: if you want to get the player's tablet from the player as a Tablet object and not just as a GameObject object, so you can call methods specifically from the Tablet class, you need to design the interface of the Player class in a way it can be asked specificially for tablets (and not just for game objects). If this is accomplished by storing tablets internally in their own list, or by filtering /downcasting the objects in gameObjects is just a minor implementation detail.

That will remove the requirement of overcomplicating the GameObject interface.

Furthermore, it leads to a design where the Player class can get 3 methods for querying the inventory

  • one to get all GameObjects from it, to be called in generic contexts where only methods from the common interface are required

  • one to get all Tablet objects, to be called in a context where tablets are expected

  • and one to get all NVG objects of a player, to be called where the latter is required.

Of course, there are alternative designs possible, but what makes most sense here depends heavily on the context in which the Player inventory will be used. It will also depend on how many more types of GameObject you have with how many different variants of behaviours and methods to control this behaviour.

So it is quite impossible to make a good design decision without knowing this context.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.