资料来源:http : //jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C#是一种很棒的语言,在过去的14年中已经成熟并发展了。这对我们开发人员而言非常好,因为成熟的语言为我们提供了可供我们使用的众多语言功能。
但是,拥有更多权力将成为很多责任。其中某些功能可能会被滥用,或者有时很难理解为什么您会选择使用一项功能而不是另一项功能。多年来,我看到许多开发人员都在苦苦挣扎的功能是何时选择使用接口或选择使用抽象类。两者都有优点和缺点,以及使用它们的正确时间和地点。但是我们如何决定???
两者都提供了类型之间通用功能的重用。最明显的区别就是接口不为其功能提供任何实现,而抽象类则允许您实现某些“基本”或“默认”行为,然后可以根据需要使用类的派生类型“覆盖”该默认行为。 。
这一切都很好,并且可以很好地重用代码,并遵循软件开发的DRY(不要自己重复)原理。当您具有“是”关系时,抽象类非常有用。
例如:金毛寻回犬“是”一种狗。贵宾犬也是。它们都可以像所有狗一样吠叫。但是,您可能想指出,贵宾犬公园与“默认”狗吠有很大不同。因此,您可以执行以下操作:
public abstract class Dog
{
public virtual void Bark()
{
Console.WriteLine("Base Class implementation of Bark");
}
}
public class GoldenRetriever : Dog
{
// the Bark method is inherited from the Dog class
}
public class Poodle : Dog
{
// here we are overriding the base functionality of Bark with our new implementation
// specific to the Poodle class
public override void Bark()
{
Console.WriteLine("Poodle's implementation of Bark");
}
}
// Add a list of dogs to a collection and call the bark method.
void Main()
{
var poodle = new Poodle();
var goldenRetriever = new GoldenRetriever();
var dogs = new List<Dog>();
dogs.Add(poodle);
dogs.Add(goldenRetriever);
foreach (var dog in dogs)
{
dog.Bark();
}
}
// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark
//
如您所见,这是使代码保持DRY并允许在任何类型都可以仅依赖默认Bark而不是特殊情况实现的情况下调用基类实现的好方法。诸如GoldenRetriever,Boxer,Lab之类的类都可以免费继承“默认”(bass类)Bark,因为它们实现了Dog抽象类。
但是我相信你已经知道了。
您之所以在这里是因为您想了解为什么您可能想选择一个抽象类上的接口,反之亦然。您可能想要在抽象类上选择接口的一个原因是,当您没有或希望阻止默认实现时。这通常是因为实现接口的类型在“是”关系中不相关。实际上,除了每种类型“能够”或具有“能力”来做某事或拥有某事的事实外,它们根本不需要关联。
现在这意味着什么?好吧,例如:一个人不是鸭子……而鸭子不是人。很明显。但是,鸭子和人都具有“游泳的能力”(假设该人通过了一年级的游泳课:)。另外,由于鸭子不是人类,反之亦然,所以这不是“是”的实现,而是“有能力”的关系,我们可以使用界面来说明:
// Create ISwimable interface
public interface ISwimable
{
public void Swim();
}
// Have Human implement ISwimable Interface
public class Human : ISwimable
public void Swim()
{
//Human's implementation of Swim
Console.WriteLine("I'm a human swimming!");
}
// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
public void Swim()
{
// Duck's implementation of Swim
Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
}
}
//Now they can both be used in places where you just need an object that has the ability "to swim"
public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
somethingThatCanSwim.Swim();
}
public void Main()
{
var human = new Human();
var duck = new Duck();
var listOfThingsThatCanSwim = new List<ISwimable>();
listOfThingsThatCanSwim.Add(duck);
listOfThingsThatCanSwim.Add(human);
foreach (var something in listOfThingsThatCanSwim)
{
ShowHowYouSwim(something);
}
}
// So at runtime the correct implementation of something.Swim() will be called
// Output:
// Quack! Quack! I'm a Duck swimming!
// I'm a human swimming!
使用上面的代码之类的接口,将使您可以将对象传递到“能够”执行某事的方法中。代码并不关心它是如何实现的……它所知道的是,它可以在该对象上调用Swim方法,并且该对象将根据其类型知道在运行时采取哪种行为。
再次,这可以帮助您的代码保持DRY状态,这样您就不必编写调用该对象以执行同一核心功能的多个方法(ShowHowHumanSwims(human),ShowHowDuckSwims(duck)等)。
在这里使用接口可以使调用方法不必担心哪种类型是哪种类型或如何实现行为。它只知道给定接口,每个对象都必须实现Swim方法,因此可以安全地在自己的代码中调用它,并允许在自己的类中处理Swim方法的行为。
摘要:
因此,我的主要经验法则是当您要为类层次结构或/和要使用的类或类型实现“默认”功能时使用抽象类,例如,“贵宾犬”是狗的类型)。
另一方面,当您没有“是”关系但具有共享“做某事或拥有某种能力”的类型时(例如,“鸭子不是人”,则使用界面。但是,鸭子和人共享“游泳的能力”。
抽象类和接口之间要注意的另一个区别是,一个类可以实现一对多的接口,但是一个类只能从一个抽象类(或任何与此相关的类)继承。是的,您可以嵌套类并具有继承层次结构(许多程序都应该这样做),但是您不能在一个派生类定义中继承两个类(此规则适用于C#。在某些其他语言中,您通常可以做到这一点)只是因为这些语言缺少接口)。
还记得使用接口遵守接口隔离原则(ISP)时的情况。ISP指出,不应强迫任何客户端依赖其不使用的方法。因此,接口应专注于特定任务,并且通常很小(例如IDisposable,IComparable)。
另一个技巧是,如果您正在开发小的,简洁的功能,请使用接口。如果要设计大型功能单元,请使用抽象类。
希望这可以为某些人清除一切!
另外,如果您能想到任何更好的示例或要指出一些问题,请在下面的评论中这样做!