假设我有一个支持一些潜在操作的接口:
interface Frobnicator {
int doFoo(double v);
int doBar();
}
现在,某些实例仅支持这些操作中的一个或另一个.他们可能都支持两者.客户端代码不一定知道,直到它实际从相关工厂获得一个,通过依赖注入,或从它获取实例的任何地方.
我看到了一些处理这个问题的方法.其中一个似乎是Java API中采用的一般策略,就是只使用如上所示的接口并使用不受支持的方法引发UnsupportedOperationException.然而,这样做的缺点是没有快速失败 – 客户端代码无法判断doFoo是否会在尝试调用doFoo之前工作.
这可以通过supportsFoo()和supportsBar()方法进行扩充,如果相应的do方法有效,则定义为返回true.
另一种策略是将doFoo和doBar方法分别分解为FooFrobnicator和BarFrobnicator方法.如果操作不受支持,这些方法将返回null.为了防止客户端代码执行instanceof检查,我定义了一个Frobnicator接口,如下所示:
interface Frobnicator {
/* Get a foo frobnicator, returning null if not possible */
FooFrobnicator getFooFrobnicator();
/* Get a bar frobnicator, returning null if not possible */
BarFrobnicator getBarFrobnicator();
}
interface FooFrobnicator {
int doFoo(double v);
}
interface BarFrobnicator {
int doBar();
}
或者,FooFrobnicator和BarFrobnicator可以扩展Frobnicator,get *方法可能会重命名为*.
这个问题的一个问题是命名:Frobnicator真的不是一个frobnicator,它是一种获取frobnicators的方式(除非我使用as *命名).它也有点笨拙.命名可能更复杂,因为将从FrobnicatorEngine服务检索Frobnicator.
有没有人对这个问题有一个好的,最好被广泛接受的解决方案?有适当的设计模式吗?访问者在这种情况下是不合适的,因为客户端代码需要特定类型的接口(并且如果它无法获得它,最好应该快速失败),而不是调度它获得的对象类型.是否支持不同的功能可能因各种因素而异 – Frobnicator的实现,该实现的运行时配置(例如,仅当存在启用Foo的某些系统服务时才支持doFoo),等等.
更新:运行时配置是此业务中的另一个猴子扳手.可以通过FooFrobnicator和BarFrobnicator类型来避免这个问题,特别是如果我使用Guice-modules-as-configuration,但它会将复杂性引入其他周围的接口(例如生产Frobnicators的工厂/构建器)首先).基本上,生成frobnicator的工厂的实现是在运行时配置的(通过属性或Guice模块),我希望它能让用户很容易地说“将这个frobnicator提供者连接到这个客户端” .我承认这是一个潜在的固有设计问题的问题,我也可能会过度思考一些泛化问题,但我会考虑一些最不实用和最不惊讶的组合.