在接口继承中使用Java泛型进行代码重用

时间:2016-12-29 22:17:44

标签: java generics inheritance design-patterns

我正在尝试使用Java中的面向对象技术为状态机创建框架。每个System一次只能激活一个State

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

假设我想添加一种新类型的系统,应该定期更新以检查和更改其状态,称为UpdateableSystem。它只能包含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();
}

我将currentState方法覆盖为仅返回UpdateableState,而不是State,因为UpdateableSystem只能包含UpdateableState类型的状态,使用UpdateableSystem的客户端需要UpdateableState s。

为了避免在创建更多子类时多次覆盖多个方法,泛型似乎就是解决方案。 以下是更新的界面。

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

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

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

现在,假设我想添加另一种类型,接口Context,系统和状态需要注意。 Context应该能够进行子类型化,客户端和ContextState子类型应该能够使用该Context子类型的完整界面。

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
}

突然间,那些参数化类型变成了一大堆样板,这让人感到困惑和难以维护。

这是具体实施的一个例子:

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

每个具体的CarState都可以访问CarContext接口提供的其他方法,因为从ContextState继承的方法签名将由CarContext类型参数化。因此,updateWithContext的上下文参数不需要显式转换。

最终,在为参数化添加额外类型时,问题是太多样板,并且我不知道通过继承实现代码重用的设计替代方案,并且还维护强类型系统,尽可能避免显式转换。有什么改进这种设计的建议吗?

1 个答案:

答案 0 :(得分:1)

在花了一天时间思考这个问题并稍微使用您的示例代码之后,我认为您可以找到解决此问题的最佳解决方案,尽管您可能会因正确遵守Java Generics code style 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> {...}

声明仍然冗长,但不那么严重。此外,我们现在明确区分完整引用和泛型类型参数。

最重要的是这个解决方案有效。

但是,如果您想进一步简化此操作,则可能会冒这些接口被滥用的风险。例如:

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;
    }

}

在这里,我认为接口声明大大减少了。这里的一切都像以前一样顺利。问题是有人可以去做一些像哑巴一样愚蠢的事情。

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

    @Override
    public String update()
    {
        return "Garbage";
    }
}

幸运的是,这种滥用行为无法真正进入我们的ExampleContextSystem,因此我们仍然无法尝试将String分配给ExampleContextState参考,但它仍然表明我们已经失去了对如何使用这些接口的一些控制。即使我们制作一个通用的混凝土系统类......

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;
    }

}

类型系统仍然足够强大以防止我们爆炸......

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

TL; DR:我认为你可以放松你的通用约束,提供一个不那么详细的接口声明,而不会承担很多(如果有的话)运行时失败的风险。