是否有为接口方法创建默认实现的首选方法或样式?假设我有一个常用的界面,在90%的情况下我想要的功能是相同的。
我的第一直觉是用静态方法创建一个具体的类。然后,当我想要默认功能时,我会将功能委托给静态方法。
这是一个简单的例子:
接口
public interface StuffDoer{
public abstract void doStuff();
}
方法的具体实施
public class ConcreteStuffDoer{
public static void doStuff(){
dosomestuff...
}
}
使用默认功能的具体实施
public class MyClass implements StuffDoer{
public void doStuff(){
ConcreteSuffDoer.doStuff();
}
}
这里有更好的方法吗?
修改
在看到一些提议的解决方案后,我想我应该更清楚我的意图。本质上我试图解决Java不允许多重继承。另外要明确我不是要声明Java是否应该允许多重继承。我只是在寻找为实现接口的类创建默认方法实现的最佳方法。
答案 0 :(得分:25)
这是我要采取的方法:
public interface MyInterface {
MyInterface DEFAULT = new MyDefaultImplementation();
public static class MyDefaultImplemenation implements MyInterface {
}
}
当然,MyDefaultImplementation可能需要是私有的,或者它自己的顶级类,具体取决于具体意义。
然后,您可以在实施中使用以下内容:
public class MyClass implements MyInterface {
@Override
public int someInterfaceMethod(String param) {
return DEFAULT.someInterfaceMethod(param);
}
}
它比其他地方存在但未被接口引用的默认实现类更自我记录,并且最终更灵活。有了这个,您可以在需要时将默认实现作为方法参数传递(使用静态方法无法做到)。
当然,只有在没有涉及国家的情况下,上述情况才有效。
答案 1 :(得分:16)
您可以将接口转换为抽象类,并根据需要为方法提供默认实现。
更新:我看到,多重继承关闭了将接口更改为抽象类...在这种情况下,我会像你一样做。如果方法的默认实现不依赖于状态,那么它们的最佳位置确实在静态实用程序类中。但是,如果涉及到状态,我会考虑对象组合,它甚至可能最终像Decorator那样。
答案 2 :(得分:7)
现在Java 8已经出局,这种模式更好:
public interface StuffDoer{
default void doStuff() {
dosomestuff...
}
}
public class MyClass implements StuffDoer {
// doStuff automatically defined
}
Lambda得到了所有人的关注,但这是Java 8对我们公司最切实的好处。当我们不再需要使用抽象类来进行默认方法实现时,我们的继承层次结构变得更加简单。
答案 3 :(得分:1)
我或多或少地遵循了我最初从Swing学到的模式。我有一个接口,然后我创建一个“基地”或“适配器”类。对我来说,适配器通常会对所有接口方法执行无操作实现,以允许实现者只编写他们需要的方法而忽略其他方法。 base将是一个抽象类,为某些接口方法提供方便的实现 - 希望是“最有可能”的实现。
例如,我有一个SearchFilter接口,除其他外,还有apply(Collection<T>)
方法。该方法几乎始终遍历集合并调用接口方法boolean include(T item)
来决定是否保留或过滤掉项目。我的SearchFilterBase提供了作为apply()的实现,实现者只需要编写他们的include()逻辑。
当然,实现者可以自由地简单地实现整个接口,而不是从Base派生,这比将接口更改为抽象类更有优势,这迫使他们使用单个继承(这就是问题)使用java.util.Observable)
响应N8g的注释 - 您可以将基类或适配器子类化,但不是必需子类 - 您可以从头开始实现接口。提供基础或适配器是为了方便,实现无操作方法,因此您不必这样做,或在抽象基类(如我的SearchFilterBase
类)中实现方便的常用功能。与将接口转换为抽象类相比,它的优势在于您不会强制从抽象类继承。
答案 4 :(得分:1)
我还使用静态方法为无状态功能声明默认功能。
对于有状态功能,我更喜欢使用合成而不是继承。通过组合和使用委托/适配器,您可以组合来自许多来源的默认功能。
e.g。
public interface StuffDoer{
void doStuff();
void doOtherStuff();
}
public class MyStuffDoer implements StuffDoer{
private final StuffDoer mixin;
public MyStuffDoer(StuffDoer mixin){
this.mixin = mixin;
}
public void doStuff(){
mixin.doStuff();
}
public void doOtherStuff(){
mixin.doOtherStuff();
}
}
public class MyStuffDoer2 implements StuffDoer{
private final StuffDoer mixin1, mixin2;
public MyStuffDoer(StuffDoer mixin1, StuffDoer mixin2){
this.mixin1 = mixin1;
this.mixin2 = mixin2;
}
public void doStuff(){
mixin1.doStuff();
}
public void doOtherStuff(){
mixin2.doOtherStuff();
}
}
对于简单的情况,继承也是可以的,但实际上并不是非常灵活。
实现多个接口也是这种方法可以更好地扩展的情况。
public interface A{
void doStuff();
}
public interface B{
void doOtherStuff();
}
public class MyStuffDoer implements A, B{
private final A mixin1;
private final B mixin2;
public MyStuffDoer(A mixin1, B mixin2){
this.mixin1 = mixin1;
this.mixin2 = mixin2;
}
public void doStuff(){
mixin1.doStuff();
}
public void doOtherStuff(){
mixin2.doOtherStuff();
}
}
你不能用抽象类来做这件事。我在一些项目中使用了这种组合方法,并且工作得非常好。
答案 5 :(得分:1)
静态实现有两个问题:
你无法真正找到任何静态代码分析,比如列出所有的实现者,因为静态方法没有实现接口,所以你不得不提到javadoc中的默认实现
有时需要在实现者中有一些状态,这在静态方法中无法真正完成
因此,我更喜欢使用一个允许继承(如果你不受单一继承限制)和组合的具体类,并调用结果类* Peer,因为它通常与主类实现一起使用界面。对等体将实现接口,并且如果需要以主类的名称触发事件,也可以引用主对象。
答案 6 :(得分:1)
大部分时间,我都会这样:
public interface Something {
public void doSomething();
public static class Static {
public static void doSomething(Something self) {
// default implementation of doSomething()
// the name of the method is often the same
// the arguments might differ, so state can be passed
}
}
public static abstract class Abstract implements Something {
// the default abstract implementation
}
public static class Default extends Abstract {
// the default implementation
public void doSomething() {
Static.doSomething(this);
}
}
public static interface Builder extends Provider<Something> {
// initializes the object
}
}
我倾向于使用内部类,因为这些类确实相关,但您也可以使用常规类。
此外,如果方法不仅涉及接口,您可能希望将实用程序类(Static
)放在单独的文件中。
顺便说一句,装饰器的问题在于它们隐藏了其他实现的接口。换句话说,如果你有一个装饰器,如果new SomethingDecorator(new ArrayList<Object>()) instanceof List<?>
没有实现列表的接口,则以下是假SomethingDecorator
。
您可以使用反射API工作,特别是使用Proxy。
PersicsB指出,另一个好的方法是适配器。
答案 7 :(得分:1)
Dependency injection可以提供委托,并指定默认实现 - 可以在单元测试中覆盖。
例如,Guice框架在接口上支持其默认实现的注释,可以通过单元测试中的显式绑定覆盖。
@ImplementedBy( ConcreteStuffDoer.class )
public interface StuffDoer{
void doStuff();
}
public class ConcreteStuffDoer implements StuffDoer {
public void doStuff(){ ... }
}
public class MyClass implements StuffDoer{
@Inject StuffDoer m_delegate;
public void doStuff(){ m_delegate.doStuff(); }
}
这确实引入了创建MyClass实例需要Guice方法而不仅仅是new
的要求。
答案 8 :(得分:0)
组合在灵活性方面胜过多重继承。与适配器模式(参见GOF书籍)一样,它明确允许我们根据呼叫者请求将(变形)类调整为不同的行为。请参阅Eclipse RCP中的IAdaptable
接口。基本上使用接口与直接使用Java语言内置的适配器模式相同。但是使用自适应类比实现多个接口要好。适应性类更灵活,但它们有一些CPU开销:instanceof
运算符比调用canAdapt(Class clazz)消耗更少的CPU。但是在现代JVM中,这可能是错误的,因为使用接口计算instanceof
比使用继承计算instanceof更复杂。
答案 9 :(得分:0)
如果它是你的任何函数:-)作为静态工作,那么你不需要委托给它所有,因为这意味着它不需要“这个” - 它是一个实用函数。
你可以重新检查你是否真的有压倒性的理由不将你认为的默认实现合并到实现该接口的第一个抽象类中 - 你知道,适应现实: - )
此外,您可以在抽象类中创建辅助的受保护虚拟方法,您的接口派生方法将调用这些方法,您可以更好地控制它,即派生类不必替换方法并复制和粘贴90%的方法代码 - 它可以覆盖从main方法调用的方法。给你一定程度的行为继承。