假设我有一个支持一些潜在操作的界面:
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*
方法可能会重命名为as*
。
这个问题的一个问题是命名:Frobnicator
真的不是一个frobnicator,它是一种获取frobnicators的方式(除非我使用as*
命名)。它也有点笨拙。命名可能会更复杂,因为Frobnicator
将从FrobnicatorEngine
服务中检索。
有没有人对这个问题有一个好的,最好被广泛接受的解决方案?有适当的设计模式吗?访问者在这种情况下是不合适的,因为客户端代码需要特定类型的接口(并且如果它无法获得它,最好应该快速失败),而不是调度它获得的对象类型。是否支持不同的功能可能会因各种因素而异 - Frobnicator
的实现,该实现的运行时配置(例如,只有在启用某些系统服务时才支持doFoo
Foo
)等。
更新:运行时配置是此业务中的另一个猴子扳手。可以通过FooFrobnicator和BarFrobnicator类型来避免这个问题,特别是如果我使用Guice-modules-as-configuration,但它会将复杂性引入其他周围的接口(例如生产Frobnicators的工厂/构建器)首先)。基本上,生成frobnicator的工厂的实现是在运行时配置的(通过属性或Guice模块),我希望它能让用户很容易地说“将这个frobnicator提供者连接到这个客户端” 。我承认这是一个潜在的固有设计问题的问题,我也可能会过度思考一些泛化问题,但我会考虑一些最不实用和最不惊讶的组合。
答案 0 :(得分:4)
我看到了一些处理这个问题的方法。其中一个似乎是Java API中采用的一般策略,就是只使用如上所示的接口并使用不受支持的方法引发UnsupportedOperationException。然而,这样做的缺点是没有快速失败 - 客户端代码无法判断doFoo是否会在尝试调用doFoo之前工作。
正如您所说,一般策略是使用template method设计模式。一个很好的例子是HttpServlet
。
这是你如何实现同样的目标。
public interface Frobnicator {
int doFoo(double v);
int doBar();
}
public abstract class BaseFrobnicator implements Frobnicator {
public int doFoo(double v) {
throw new UnsupportedOperationException();
}
public int doBar() {
throw new UnsupportedOperationException();
}
}
/**
* This concrete frobnicator only supports the {@link #doBar()} method.
*/
public class ConcreteFrobnicator extends BaseFrobnicator {
public int doBar() {
return 42;
}
}
客户端只需阅读文档并相应地处理UnsupportedOperationException
。因为它是RuntimeException
,所以它是“程序员错误”的完美案例。没错,它不是快速失败(即不是编译时),但这就是你作为开发人员获得的报酬。只是阻止它或抓住并处理它。
答案 1 :(得分:3)
我认为你有一个问题,如果你说FooImpl类实现接口Foo,但它并没有真正实现所有的Foo。接口应该是一个契约,表明实现类(至少)实现的方法。
如果某个东西正在调用FooFactory来获取Foo对象,那么该对象应该能够做Foos所做的事。
那么,你从哪里去?我建议你的构图想法很好,使用的继承略少。
使用getFooFrobnicator()
和getBarFrobnicator()
方法,合成将完全与您一样。如果您不喜欢Frobnicator
没有真正反对,请将其称为FrobnicatorHolder
。
继承将使您的Frobnicator
仅包含所有Frobnicator所具有的方法,并且子接口将具有其他方法。
public interface Frobnicator{
public void frobnicate();
}
public interface FooFrobnicator{
public void doFoo();
}
public interface BarFrobnicator{
public void doBar();
}
然后你可以使用if (frobnicator instanceof FooFrobnicator) { ((FooFrobnicator) frobnicator).doFoo() }
并继续生活。
赞成合成而不是继承。你会更开心。
答案 2 :(得分:2)
如何只添加空的非实现方法而不是抛出UnsupportedOperationExceptions
?这样,调用不受支持的方法将没有副作用,并且不会导致运行时错误。
如果您确实希望能够判断某个对象是否支持某个方法,我建议您将界面拆分为两个带有通用超级界面的界面,然后键入检查您获取的任何对象以确定支持方法。这可能比我的第一个建议更清晰,更明智。
interface Frobnicator {}
interface FooFrobnicator extends Frobnicator {
void doFoo();
}
interface BarFrobnicator extends Frobnicator {
void doBar();
}
修改强>
另一种方法是在方法中添加一个布尔返回类型,并确保只有在不支持该方法时它们才返回false。因此:
interface Frobnicator
{
boolean doFoo();
boolean doBar();
}
class FooFrobnicator implements Frobnicator
{
public boolean doFoo() { code, code, code; return true; }
public boolean doBar() { return false; }
}
答案 3 :(得分:2)
最明显的解决方案是只定义两个接口FooFrobnicator和BarFrobnicator。这应该没问题,因为类可以实现多个接口。
如果需要,您可以提供超级(标记)界面。
答案 4 :(得分:0)
更简单的解决方案是添加supports*()
方法,
如果您认为这不是好的风格,那么只需忘记Frobnicator
(因为它不是您可以依赖的界面,它只给您很少)并直接针对FooFrobnicator
和{{ 1}},如果碰巧由同一个对象实现,那很好。这也使您可以在客户端代码中要求特定的接口:
BarFrobnicator
答案 5 :(得分:-1)