Using Java Generics for Code Reuse in Interface Inheritance

Question

I am trying to create a framework for state machines using object oriented techniques in Java. Every System can have one and only one State active at a time.

public interface State {}
public interface System { 
    State currentState();
}

Let's say I want to add a new type of system that should be updated regularly to check and change its state, called an UpdateableSystem. It can only have States that are of the type UpdateableState.

public interface UpdateableState extends State {
    /**
     * Check to see if the system should switch to a different state.
     * @returns new state of the system
     */
    UpdateableState update();
}

public interface UpdateableSystem extends System {
    @Override
    UpdateableState currentState();
    /** 
     * Runs update method of the current state, or otherwise updates the system.
     * @returns the current state of the system after the update
     */
    UpdateableState update();
}

I overrode the currentState method to only return only UpdateableState, not State, because the UpdateableSystem can only have states that are of the type UpdateableState, and the client using the UpdateableSystem will expect UpdateableStates.

To avoid overriding many methods many times as more subclasses are created, generics seem to be the solution. Here are the updated interfaces.

public interface System<SystemState extends State> {
    SystemState currentState();
}

and

public interface UpdateableState<SystemState extends UpdateableState<SystemState>> extends State {
    SystemState update();
}

public interface UpdateableSystem<SystemState extends UpdateableState<SystemState>> extends System<SystemState> {
    SystemState update();
}

Now, lets say I wanted to add another type, interface Context, that systems and states need to be aware of. Context should be able to be subtyped, and clients and ContextState subtypes should be able to use the full interface of that Context subtype.

public interface Context {}
public interface ContextState<SystemContext extends Context, SystemState extends ContextState<SystemContext, SystemState>> extends UpdateableState<SystemState> {
    SystemState updateWithContext(SystemContext context);
}
public interface ContextSystem<SystemContext extends Context, SystemState extends ContextState<SystemContext, SystemState>> extends UpdateableSystem<SystemState> {
    // Some methods here that require SystemContext type
}

Suddenly, those parameterized types turned into a whole ton of boilerplate, which is confusing and hard to maintain.

This would be an example with concrete implementations:

class CarContext implements Context {...}
interface CarState extends ContextState<CarContext, CarState> {...}
    class CarDrivingState implements CarState {}
    class CarIdleState implements CarState {}
class Car implements ContextSystem<CarContext, CarState> {...}

Each concrete CarState would be able to access the additional methods provided by the CarContext interface, because the method signatures inherited from ContextState would be parameterized by the CarContext type. So no explicit casting would be required on the context parameter of updateWithContext.

Ultimately, the issue is too much boilerplate when adding an additional type to parameterize, and I do not know of a design alternative that would achieve code reuse through inheritance, and also maintain a strong type system, avoiding explicit casting when possible. Any suggestions to improve this design?


Show source
| generics   | java   | inheritance   | design-patterns   2016-12-29 23:12 1 Answers

Answers ( 1 )

  1. 2016-12-30 21:12

    After spending a day thinking about this, and playing with your example code a bit, I think you have the best solution for this problem, although you might benefit from proper adherence to the Java Generics code style standards.

    By having full names for the generic types in your interface declarations, you actually make them harder to read, especially when you get to the methods. The generic types are easily confused for actual interface or class names. Not to mention they take up more space. Let's look at what your code would look like following the standards:

    public interface State{}
    public interface System<S extends State> {
        S currentState();
    }
    
    public interface UpdateableState<S extends UpdateableState<S>> extends State {
        S update();
    }
    public interface UpdateableSystem<S extends UpdateableState<S>> extends System<S> {
        S update();
    }
    
    public interface Context{}
    public interface ContextState<C extends Context, S extends ContextState<C, S>> extends UpdateableState<S> {
        S updateWithContext(C context);
    }
    public interface ContextSystem<C extends Context, S extends ContextState<C, S>> extends UpdateableSystem<S> {
        // Some methods here that require SystemContext type
    }
    
    class CarContext implements Context {...}
    interface CarState extends ContextState<CarContext, CarState> {...}
    class CarDrivingState implements CarState {}
    class CarIdleState implements CarState {}
    class Car implements ContextSystem<CarContext, CarState> {...}
    

    The declarations are still verbose, but less so. Additionally, we now have a clear distinction between full reference and generic type parameters.

    What's most important is that this solution works.


    However, if you want to simplify this further, you can, at the risk that these interfaces might be misused. For instance:

    public interface State{}
    public interface System<S> {
        S currentState();
    }
    
    public interface UpdateableState<S> extends State {
        S update();
    }
    public interface UpdateableSystem<S> extends System<S> {
        S update();
    }
    
    public interface Context{}
    public interface ContextState<C, S> extends UpdateableState<S> {
        S updateWithContext(C context);
    }
    public interface ContextSystem<C, S> extends UpdateableSystem<S> {
        S updateWithContext(C context);
    }
    
    public class ExampleContext implements Context{}
    public class ExampleContextState implements ContextState<ExampleContext, ExampleContextState>{
        @Override
        public ExampleContextState updateWithContext(ExampleContext context)
        {
            return this;
        }
    
        @Override
        public ExampleContextState update()
        {
            return this;
        }
    }
    public class ExampleContextSystem implements ContextSystem<ExampleContext, ExampleContextState>{
        private ExampleContextState currentState;
        public ExampleContextSystem(){
            currentState = new ExampleContextState();
        }
        @Override
        public ExampleContextState updateWithContext(ExampleContext context)
        {
            this.currentState = currentState.updateWithContext(context);
            return currentState;
        }
    
        @Override
        public ExampleContextState update()
        {
            this.currentState = currentState.update();
            return currentState;
        }
    
        @Override
        public ExampleContextState currentState()
        {
            return currentState;
        }
    
    }
    

    Here, I think the interface declarations are greatly reduced. Everything here works just as smoothly as before. The problem is that someone can go and do something dumb like...

    public class BadExampleContextState implements ContextState<Integer, String>{
        @Override
        public String updateWithContext(Integer context)
        {
            return "Garbage";
        }
    
        @Override
        public String update()
        {
            return "Garbage";
        }
    }
    

    Luckily, this misuse can't really find its way into our ExampleContextSystem, so we still can't end up trying to assign a String to an ExampleContextState reference, but it still demonstrates that we have lost some of our control of how these interfaces will be used. Still, even if we make a generic concrete system class...

    public class GenericSystem<S extends UpdateableState<S>> implements UpdateableSystem<S>{
        private S currentState;
        public GenericSystem(S startingState){
            this.currentState = startingState;
        }
        @Override
        public S update()
        {
            this.currentState = currentState.update();
            return currentState;
        }
    
        @Override
        public S currentState()
        {
            return currentState;
        }
    
    }
    

    The type system is still strong enough to prevent us from blowing up...

    GenericSystem<BadExampleContextState> s = new GenericSystem<>(new BadExampleContextState()); 
    // error: type argument Generics.BadExampleContextState is not within bounds of type-variable S
    

    TL;DR: I think you would be fine to loosen up on your generic constraints, giving a less verbose interface declaration without taking on much (if any) risk of runtime failure.

◀ Go back