我应该在什么时候使用接口,什么时候使用基类?
如果我不想定义方法的基本实现,是否总是应该使用接口?
如果我有一个Dog和Cat类,为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)等功能提供接口,因为这些功能可以针对每个宠物进行设置,但我不明白为什么要为通用Pet使用哪种方式。
我应该在什么时候使用接口,什么时候使用基类?
如果我不想定义方法的基本实现,是否总是应该使用接口?
如果我有一个Dog和Cat类,为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)等功能提供接口,因为这些功能可以针对每个宠物进行设置,但我不明白为什么要为通用Pet使用哪种方式。
之前有关使用抽象类进行常见实现的评论绝对是正确的。但还有一个好处未被提及,那就是使用接口使得实现单元测试时更容易实现模拟对象。按照Jason Cohen所描述的定义IPet和PetBase可以轻松地模拟不同的数据条件,而无需使用实际数据库(直到你决定测试真正的东西为止)。
除非你知道基类的含义并且在这种情况下它适用,否则不要使用基类。如果适用,请使用它,否则请使用接口。但请注意有关小接口的答案。
公共继承在对象导向设计中被过度使用,并且表达了比大多数开发人员意识到或愿意承担的更多内容。请参阅里氏替换原则
简而言之,如果 A “是一个” B,那么 A 不需要比 B 更多,也不能比 B 更少,对于它公开的每个方法都是如此。
从概念上讲,接口用于正式和半正式地定义对象提供的一组方法。正式意味着一组方法名称和签名,而半正式则指与这些方法相关联的人类可读文档。
接口仅是API的描述(毕竟API代表应用程序编程接口),它们不能包含任何实现,并且无法使用或运行接口。它们只明确了您应如何与对象交互的合同。
类提供实现,它们可以声明自己实现零个、一个或多个接口。如果一个类打算被继承,惯例是在类名前加上“Base”。
基类和抽象基类(ABC)之间有区别。ABC将接口和实现混合在一起。在计算机编程之外,“抽象”意味着“摘要”,即“抽象==接口”。然后,抽象基类可以描述既包括接口,又包括旨在继承的空、部分或完整实现。
使用接口(interfaces)、抽象基类(abstract base classes)或仅使用类(classes)的选择,取决于您正在开发的内容以及您正在使用的编程语言。接口通常只与静态类型语言(如Java或C#)相关联,但动态类型语言也可以有接口和抽象基类。例如,在Python中,Class声明它实现了一个interface,而对象是一个class的实例,并被认为提供该interface。在动态语言中,两个都是同一class实例的对象可能声明它们提供完全不同的interfaces。在Ruby中,对象可以具有每个实例方法,因此两个相同class的对象之间的interface可以根据程序员的需要变化(但是,Ruby没有任何显式声明接口的方式)。我发现在以下用例中,Interface > Abstract > Concrete 的模式是有效的:
1. You have a general interface (eg IPet)
2. You have a implementation that is less general (eg Mammal)
3. You have many concrete members (eg Cat, Dog, Ape)
public interface IPet{
public boolean hasHair();
public boolean walksUprights();
public boolean hasNipples();
}
现在,由于所有哺乳动物都有毛和乳头(据我所知,我不是动物学家),因此我们可以将其整合到抽象基类中。
public abstract class Mammal() implements IPet{
@override
public walksUpright(){
throw new NotSupportedException("Walks Upright not implemented");
}
@override
public hasNipples(){return true}
@override
public hasHair(){return true}
具体来说,具体的类只需定义它们直立行走。
public class Ape extends Mammal(){
@override
public walksUpright(return true)
}
public class Catextends Mammal(){
@override
public walksUpright(return false)
}
当有很多具体类时,这种设计非常好,而且您不想维护样板文件以编程接口。如果向接口添加新方法,则会破坏所有生成的类,因此仍然可以获得接口方法的优势。
在这种情况下,抽象类也可以是具体类;但是,抽象标识有助于强调正在使用此模式。
接口提供了与其他代码通信的层。类的所有公共属性和方法默认情况下都实现了隐式接口。我们还可以将接口定义为角色,每当任何类需要扮演该角色时,它都必须实现它,并根据实现它的类给出不同形式的实现。因此,当您谈论接口时,您正在谈论多态性,而当您谈论基类时,您正在谈论继承。这是面向对象编程的两个概念!!!
关于 C#,在某些方面,接口和抽象类是可以互换的。然而,它们之间的区别在于:i)接口不能实现代码;ii)因此,接口不能调用更高层次的子类;iii)只有抽象类可以被继承到一个类中,而一个类可以实现多个接口。
这取决于您的要求。如果IPet足够简单,我更喜欢实现它。否则,如果PetBase实现了大量您不想复制的功能,则可以使用它。
实现基类的缺点是需要override
(或new
)现有方法。这使它们成为虚拟方法,这意味着您必须小心使用对象实例。
最后,.NET的单一继承让我感到困扰。一个天真的例子:假设您正在制作一个用户控件,因此您继承了UserControl
。但是,现在您无法继承PetBase
。这迫使您重新组织,例如将PetBase
类成员化。
在我需要其中之一时才会实现,我更喜欢接口而不是抽象类,因为这样可以提供更多的灵活性。如果某些继承类中存在共同的行为,我会将其上移并创建一个抽象基类。我认为不需要同时使用两者,因为它们本质上服务于相同的目的,并且同时使用两者是糟糕的代码味道(在我看来),这意味着解决方案被过度设计。
何时应该使用接口,何时应该使用基类?
如果:
抽象
方法,并且没有非抽象方法非抽象
方法(除了Java 8语言,其中接口方法提供默认实现)抽象
类相比,这将使接口
更易于使用。请查看此SE 问题以获取更多详细信息。
如果我不想实际定义方法的基本实现,它是否总是一个接口?
是的。这样做更好,更清晰。即使您拥有带有一些抽象方法的基类,也让基类通过接口扩展抽象
方法。您可以在不更改基类的情况下更改接口。
Java示例:
abstract class PetBase implements IPet {
// Add all abstract methods in IPet interface and keep base class clean.
Base class will contain only non abstract methods and static methods.
}
abstract class PetBase implements IPet {
// Add all abstract methods in IPet
}
/*If ISheds,IBarks is common for Pets, your PetBase can implement ISheds,IBarks.
Respective implementations of PetBase can change the behaviour in their concrete classes*/
abstract class PetBase implements IPet,ISheds,IBarks {
// Add all abstract methods in respective interfaces
}
优点:
如果我想在现有接口中添加一个抽象方法,只需更改接口而不必触碰抽象基类。如果我想更改合同,则更改接口和实现类而不触碰基类。
您可以通过接口为基类提供不可变性。请参阅此 文章
有关更多详细信息,请参阅此相关SE问题: