我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
当前回答
感谢Jon Limjap的回答,但我想为接口和抽象基类的概念添加一些解释
接口类型与抽象基类
改编自Pro C#5.0和.NET 4.5 Framework书籍。
接口类型似乎与抽象基类非常相似。回忆起当类被标记为抽象时,它可以定义任意数量的抽象成员来提供所有派生类型的多态接口。然而,即使类确实定义了一组抽象成员,还可以自由定义任意数量的构造函数、字段数据、非抽象成员(使用另一方面,接口只包含抽象成员定义。抽象父类建立的多态接口有一个主要限制因为只有派生类型支持抽象父级定义的成员。然而,在更大范围内软件系统,开发多个没有共同父级的类层次结构是非常常见的超出System.Object。假定抽象基类中的抽象成员仅适用于派生类型,我们无法在不同的层次结构中配置类型以支持相同的多态性界面例如,假设您定义了以下抽象类:
public abstract class CloneableType
{
// Only derived types can support this
// "polymorphic interface." Classes in other
// hierarchies have no access to this abstract
// member.
public abstract object Clone();
}
给定此定义,只有扩展CloneableType的成员才能支持Clone()方法如果创建一组不扩展此基类的新类,则无法获得多态界面。此外,您可能还记得C#不支持类的多重继承。因此,如果您想创建一辆小型货车,它是一辆汽车,并且是一种可克隆类型,那么您无法做到:
// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}
正如您所猜测的,接口类型起到了拯救作用。定义接口后,可以可以由任何类或结构、任何层次结构、任何命名空间或任何程序集中实现(用任何.NET编程语言编写)。正如您所看到的,接口是高度多态的。考虑System命名空间中定义的名为ICloneable的标准.NET接口。这接口定义了一个名为Clone()的方法:
public interface ICloneable
{
object Clone();
}
其他回答
另一个要记住的选项是使用“has-a”关系,也就是“以”或“组合的方式实现”。有时这是一种比使用“is-a”继承更干净、更灵活的结构方式。
从逻辑上说,狗和猫都“拥有”一只宠物可能不太合理,但它避免了常见的多重继承陷阱:
public class Pet
{
void Bathe();
void Train(Trick t);
}
public class Dog
{
private Pet pet;
public void Bathe() { pet.Bathe(); }
public void Train(Trick t) { pet.Train(t); }
}
public class Cat
{
private Pet pet;
public void Bathe() { pet.Bathe(); }
public void Train(Trick t) { pet.Train(t); }
}
是的,这个例子表明,在以这种方式做事时存在大量的代码重复和缺乏优雅。但我们也应该意识到,这有助于保持狗和猫与宠物类的分离(因为狗和猫不能访问宠物的私人成员),并为狗和猫从其他东西(可能是哺乳动物类)继承留下了空间。
当不需要私人访问并且您不需要使用通用的宠物参考/指针来参考狗和猫时,合成是最好的。接口为您提供了通用的引用功能,可以帮助减少代码的冗长,但当组织不好时,它们也会使事情变得模糊。当您需要私人成员访问时,继承是有用的,在使用它时,您将致力于将您的狗和猫类与您的宠物类高度耦合,这是一个巨大的成本。
在继承、组合和接口之间,没有一种方法总是正确的,考虑如何协调地使用这三种选项是有帮助的。在这三个选项中,继承通常是最不常用的选项。
通过def,接口提供了一个与其他代码通信的层。默认情况下,类的所有公共财产和方法都实现隐式接口。我们也可以将接口定义为一个角色,当任何类都需要扮演这个角色时,它必须实现它,根据实现它的类,给它不同的实现形式。oops的两个概念!!!
接口有一个明显的优点,即对类来说有点“热交换”。将一个类从一个父类更改为另一个父级通常会导致大量工作,但通常可以删除和更改接口,而不会对实现类产生很大影响。这在您有几个“可能”希望类实现的窄行为集的情况下尤其有用。
这在我的领域尤其适用:游戏编程。基类可能会因继承对象“可能”需要的大量行为而变得臃肿。通过接口,可以轻松地向对象添加或删除不同的行为。例如,如果我为想要反映伤害的对象创建了“IDamageEffects”界面,那么我可以很容易地将其应用于各种游戏对象,并在以后很容易地改变主意。假设我设计了一个要用于“静态”装饰对象的初始类,我最初决定它们是不可破坏的。稍后,我可能会决定,如果它们可以爆炸,那会更有趣,所以我修改了类以实现“IDamageEffects”接口。这比切换基类或创建新的对象层次结构要容易得多。
现代风格是定义IPet和PetBase。
该接口的优点是其他代码可以使用它,而与其他可执行代码没有任何联系。完全“干净”。界面也可以混合。
但是基类对于简单的实现和通用实用程序很有用。因此,还提供一个抽象基类,以节省时间和代码。
我发现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();
}
现在,由于所有的哺乳动物都有毛发和乳头(AFAIK,我不是动物学家),我们可以将其纳入抽象基类
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)
}
当有很多具体的类时,这种设计是很好的,并且您不希望维护样板只是为了编程到接口。如果向接口中添加了新方法,则会破坏所有生成的类,因此您仍然可以获得接口方法的优势。
在这种情况下,抽象也可以是具体的;然而,抽象的名称有助于强调这种模式正在被使用。