我正在尝试使用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
的上下文参数不需要显式转换。
最终,在为参数化添加额外类型时,问题是太多样板,并且我不知道通过继承实现代码重用的设计替代方案,并且还维护强类型系统,尽可能避免显式转换。有什么改进这种设计的建议吗?
答案 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:我认为你可以放松你的通用约束,提供一个不那么详细的接口声明,而不会承担很多(如果有的话)运行时失败的风险。