如果某个接口继承自其他接口,是否将其视为“空”?


12

据我所知,空接口通常被认为是不好的做法-特别是在语言支持的属性之类的地方。

但是,如果某个接口继承自其他接口,是否将其视为“空”?

interface I1 { ... }
interface I2 { ... } //unrelated to I1

interface I3
    : I1, I2
{
    // empty body
}

任何实现I3将需要实现I1I2,并从不同的类,这些类继承的对象I3然后可以互换使用(见下文),所以是不是叫I3 ?如果是这样,哪种更好的架构方法呢?

// with I3 interface
class A : I3 { ... }
class B : I3 { ... }

class Test {
    void foo() {
        I3 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function();
    }
}

// without I3 interface
class A : I1, I2 { ... }
class B : I1, I2 { ... }

class Test {
    void foo() {
        I1 something = new A();
        something = new B();
        something.SomeI1Function();
        something.SomeI2Function(); // we can't do this without casting first
    }
}

1
无法指定多个接口作为参数/字段等的类型是C#/ .NET的设计缺陷。
CodesInChaos

我认为构建此部分的更好方法应该进入一个单独的问题。现在,已接受的答案与问题标题无关。
卡波尔2015年

@CodesInChaos不,它不是,因为已经有两种方法可以做到这一点。一种是在这个问题中,另一种方式是为方法参数指定通用类型参数,并使用where限制指定两个接口。
安迪

是否有意义,一个类实现I1和I2,但不I3,应该能够被使用foo?(如果答案为“是”,请继续!)
user253751

@Andy虽然我并不完全同意CodesInChaos的观点,即它本身就是一个缺陷,但我认为方法上的泛型类型参数不是解决方案-它将了解方法实现中使用的类型的责任推给了其他人正在调用该方法,感觉不对。也许像var : I1, I2 something = new A();局部变量这样的语法会很好(如果我们忽略Konrad的回答中提出的要点)
Kai

Answers:


27

任何实现I3的东西都需要实现I1和I2,然后可以互换使用来自继承I3的不同类的对象(请参见下文),因此将I3称为空是正确的吗?如果是这样,哪种更好的架构方法呢?

“捆绑” I1,并I2I3提供了一个很好(?)的快捷方式,或某种语法糖用于无论你想既来实现。

这种方法的问题是,你不能明确地执行停止其他开发商I1 I2并排,但它不工作两者兼得-而: I3相当的: I1, I2: I1, I2是不是等同: I3。即使它们在功能上相同,但同时支持这两种I1并且I2不会被检测为support的类I3

这意味着您提供两种完成同一件事的方式-“手动”和“甜”(使用语法糖)。这可能会对代码一致性产生严重影响。提供两种不同的方式可以在这里,那里和另一个地方完成同一件事,这已经导致8种可能的组合:)

void foo() {
    I1 something = new A();
    something = new B();
    something.SomeI1Function();
    something.SomeI2Function(); // we can't do this without casting first
}

好的,你不能。但这也许实际上是一个好兆头?

如果I1并且I2暗示着不同的责任(否则就没有必要首先将它们分开),那么foo()尝试一次something执行两个不同的责任也许是错误的?

换句话说,可能是它foo()本身违反了“单一职责”原则,并且需要强制转换和运行时类型检查才能实现这一事实,这是一个警告我们的红旗。

编辑:

如果您坚持要确保foo采用实现I1和的东西,则I2可以将它们作为单独的参数传递:

void foo(I1 i1, I2 i2) 

它们可能是同一对象:

foo(something, something);

什么foo照顾,如果他们是相同的实例或不是?

但是,如果它确实在乎(例如,因为它们不是无状态的),或者您只是觉得这很丑陋,则可以改用泛型:

void Foo<T>(T something) where T: I1, I2

现在,通用约束可以照顾到所需的效果,同时又不会污染任何外部环境。这是基于编译时检查的惯用C#,总体上感觉更合适。

我认为这I3 : I2, I1是一种尝试,以一种不开箱即用的语言来模拟联合类型(C#或Java不支持,而联合类型存在于功能语言中)。

试图模拟一种语言并不真正支持的构造通常很笨拙。同样,在C#中,如果要模拟mixins,则可以使用扩展方法和接口。可能?是。好主意?我不确定。


4

如果让一个类实现两个接口特别重要,那么创建第三个接口并从两个接口继承都没有错。但是,我要小心不要陷入过度设计的陷阱。一个类可以从一个或另一个继承。除非我的程序在这三种情况下都有大量的类,否则为这两种情况创建一个接口要花点功夫,并且如果这两个接口彼此之间没有关系(当然也请没有IComparableAndSerializable接口),那肯定不是一件容易的事。

我提醒您,您也可以创建一个从两个接口继承的抽象类,并且可以使用该抽象类代替I3。我认为说您不应该这样做是错误的,但是至少应该有充分的理由。


??您当然可以在一个类中实现两个接口。您不继承接口,而是实现它们。
安迪

@Andy我在哪里说过一个类不能实现两个接口?关于用“继承”一词代替“实现”一词的语义。在C ++中,继承最有效,因为最接近接口的抽象类。
尼尔

继承和接口实现不是相同的概念。继承多个子类的类可能会导致一些奇怪的问题,而实现多个接口却并非如此。缺少接口的C ++并不意味着区别就消失了,这意味着C ++的功能有限。继承是“是”关系,而接口则指定“可以”。
安迪

@Andy Weird问题是由上述类中实现的方法之间的冲突引起的。但是,它不再是名称上或实践上都不是的接口。声明但未实现方法的抽象类在所有意义上都是除名称(接口)以外的词。术语在语言之间会发生变化,但这并不意味着引用和指针不是同一概念。这是一个古怪的观点,但是如果您坚持这一点,那么我们将不得不不同意。
尼尔

“语言在语言之间变化”不,不是。OO术语在各种语言中是一致的;我从未听说过一个抽象类,它在我使用的每种OO语言中都没有其他含义。是的,您可以在C ++中拥有接口的语义,但是重点是没有什么可以阻止某人去使用该语言向抽象类中添加行为并破坏这些语义。
安迪

2

空接口通常被认为是不好的做法

我不同意。阅读有关标记接口的信息

典型的接口指定实现类必须支持的功能(以方法声明的形式),而标记器接口则不必这样做。这种接口的存在表明在实现类方面的特定行为。


我想OP会意识到它们,但是它们实际上有点争议。尽管有些作者推荐了它们(例如,“ Effective Java”中的Josh Bloch),但有些人声称它们是Java尚无注释时的反模式和遗留物。(Java中的注释与.NET中的属性大致等效)。我的印象是,它们在Java世界中比.NET更受欢迎。
Konrad Morawski

1
我偶尔使用它们,但感觉有点kind头-不好的一面是,您无法忍受它们被继承的事实,因此,一旦您将一个班级标记为一个班级,所有的孩子都会以及是否要标记。而且他们似乎鼓励使用instanceof替代多态的不良做法
Konrad Morawski
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.