接口和抽象类只是具有虚拟抽象方法是一回事
本文关键字:虚拟 抽象方法 一回事 抽象类 接口 | 更新日期: 2023-09-27 18:05:39
如果我有一个包含类似类的项目,其中一些可能使用相同的实现,但在大多数情况下,它们实现自己处理接口或抽象类中定义的方法的方式。我试图弄清楚如果一个接口/抽象类是更好的或不。如果你只能使用带有虚拟抽象方法的抽象类,我就不明白接口的意义了。
下面是一个接口:
public interface IAthlete
{
void Run();
}
下面是一个抽象类:
public abstract class Athlete
{
public abstract void Run();
}
下面是接口的实现:
public class Sprinter : IAthlete
{
public void Run()
{
Console.WriteLine("Running Fast....");
}
}
下面是抽象类的扩展:
public class MarathonRunner : Athlete
{
public override void Run()
{
Console.Write("Jogging....");
}
}
现在,如果我决定在接口或抽象方法中添加一个名为Stop的方法,Sprinter和MarathonRunner都会中断,因为我可以为抽象提供一些默认实现,这似乎是一个更好的选择。我错过什么了吗?
接口和抽象超类有两个主要区别:
抽象类- 代码重用可以通过使用抽象超类 实现
- 你只能继承一个超类
- 每个方法必须在每个子类中实现
- 一个类可以继承多个接口(多重继承)
如果您只有一个共性要提取,那么您非常正确地认为两者之间没有实质性的区别。但这就像说"在将1
添加到2
的情况下,int
和double
之间没有区别"-这是技术上正确的,但不是一个特别有用的指导如何思考。
在比这更复杂的情况下(也就是说,在大多数情况下),将会有更多的类,以及需要提取的公共属性片段。然后,您必须开始在类继承和接口实现之间做出重要的选择,考虑如下事项:
- 你只有一次选择基类的机会,但是你可以实现尽可能多的接口
- 如果你想让你的'父'做任何工作,它需要是一个类而不是一个接口
等等
一般来说,类的语义应该指导你——当"事物"具有"IS - A"关系时(如MarathonRunner到Athlete),建议使用继承;当"事物"具有"我可以履行A的契约"(例如,Person to Runner)时,建议使用接口实现。
接口是一种更好的方法,因为目前。net开发者社区的共识是,你应该更喜欢组合而不是继承,所以接口是一种更好的策略(想想注入容器以及它们有多有用,让我们不要开始单元测试)。此外,类可以实现许多接口,但只能继承一个类(抽象或其他)。此外,结构可以实现接口,但不能从另一个类继承。在运行时级别,接口的效率更高,因为运行时不必遍历继承堆栈来计算调用特定成员的多态含义。
接口是一个非常有用的特性,与抽象类非常相似,在某些情况下,可以与抽象类交换。
但是,不要直接跳转到接口,除非必须这样做(Java开发人员中非常常见的反模式)。我建议,通过阅读你的例子,坚持使用抽象类。大多数情况下,我只使用接口,当我有几个不相关的类,我需要他们有共同的成员,好像这些类来自同一个基类。
在你的例子中,你试图找到如果你需要一个新的stop
方法,当添加一个基本的虚方法。这些问题可以用另一种方法解决,即不是抽象类vs 接口。
有三个选择:
(1)添加一个抽象方法,强制程序员重写它,以便实例化对象。
(2)添加一个新的虚拟方法,它可以做一些事情,但不需要被重写。
(3)添加一个不做任何事情的新方法,也许适用于你的情况。
// cannot instantiate an abstract class
public abstract class Athlete
{
// helper method:
public /* non-abstract */ void DoNothing()
{
// does nothing on purpouse !!!
}
// (1) virtual & abstract method, must be overriden
public abstract void Run();
// (2) new virtual method, doesn't need to be overriden,
// but, maybe you dont like what it does
public virtual void Stop()
{
Message.Show("Stop !!!");
}
// (3) new virtual method, doesn't need to be overriden,
// its safe to be called
public virtual void TakeBreak()
{
// works like an abstract virtual method, but, you don't need to override
DoNothing();
}
} // class Athlete
// in a non abstract class, you must override all abstract methods
public /* non-abstract */ class Runner: Athlete
{
public override void Run()
{
DoNothing();
}
public override void Stop()
{
DoNothing();
}
// don't need to override this method
// public virtual void TakeBreak();
} // class Trekker
// ...
Runner ARunner = new Runner();
ARunner.Run();
ARunner.Stop();
ARunner.TakeBreak();
第三种虚拟方法,可能适用于你的例子,没有一个特殊的名称,我已经在stackoverflow上发布了一个关于它的问题,但是,没有人知道这种情况下的特殊名称。
欢呼。
接口和抽象类之间的一个重要区别是它们的成员如何处理多代继承。假设有一个抽象类BaseFoo
和抽象方法Bar
,接口IFoo
和方法Boz
;类Foo
继承BaseFoo
并实现IFoo
,类DerivedFoo
继承Foo
。
如果DerivedFoo
需要覆盖BaseFoo.Bar
,它可以这样做,如果它需要使用父类的实现,它的覆盖可以调用base.Bar()
。如果Foo
使用虚方法隐式实现Boz
,那么DerivedFoo
可以覆盖该方法并调用base.Boz()
(覆盖是类的函数而不是接口),但如果Foo
显式实现IFoo.Boz
,那么DerivedFoo
改变IFoo.Boz
行为的唯一方法将是重新实现它。如果这样做,那么Foo
对IFoo.Boz
的实现将变得不可访问,即使在DerivedFoo
对相同接口成员的实现中也是如此。