为什么同时使用抽象类和接口?(抽象类实现接口)

本文关键字:接口 抽象类 实现 为什么 | 更新日期: 2023-09-27 17:58:09

最近我在一些代码中遇到了一个奇怪的模式。我们知道每件事都有时间和地点,尤其是当涉及到ABC和接口的问题时,但这对我来说似乎是多余的

  // This describes a person....
  public interface IPerson
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int BasePay { get; set; }
    public string Address { get; set; }
  }
  // And so does this, but it also uses the interface....
  public abstract class Person : IPerson
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int BasePay { get; set; }
    public string Address { get; set; }
  }
  // This uses both ?!
  public class CoalMiner : Person, IPerson
  {
    public CoalMiner()
    {
      BasePay = 10000;
    }
  }

有人能想到同时使用和ABC以及定义相同成员的接口的具体优势是什么吗?

为什么同时使用抽象类和接口?(抽象类实现接口)

就我个人而言,我觉得使用接口来描述"名词",比如人,通常是一个糟糕的设计选择。

接口应该用于契约——所有人都是Person,所以抽象类在这里更有意义。可以为特定类型的人的行为添加接口,或者允许通过履行合同以某种方式使用Person(即:Person : IComparable<Person>)。

只要在IPerson接口而不是Person基类下传递对象,同时拥有IPerson接口和Person基类就可以获得某些自由。

基类倾向于实现该基类的所有子代都应该使用的公共代码。如果这是您想要的,那也没关系,但可能会遇到需要完全不同的IPerson实现的情况,其中根本不使用基类Person。现在,您有两个类层次结构,它们有IPerson的共同点,并且仍然可以正常工作。仅使用Person是无法做到这一点的。

总是有一个接口的冗余性的另一个很好的原因是COM互操作。

接口和ABC都有意义的情况是在使用decorator模式时。ABC用于为不同的具体实现类提供通用的实现代码。所有实现类可能都是从ABC派生的。

包装现有实例并调整其功能的decorator通常只实现接口,而不是从ABC派生。如果有许多decorator,那么可能还有另一个ABC,它提供decorator所需的常见组合处理和函数调用转发。

明确提及接口有时会使其可读性更强。MSDN文档经常这样做,例如显示List<>同时实现ICollection<>IList<>,尽管IList<>是从ICollection<>派生而来的。

我能想到的派生类显式实现与其基类相同接口的唯一优势是禁止派生类隐藏成员,从而破坏接口。

接口为行为指定了一个契约,因此只有当你有足够的行为(除了简单的属性访问器之外),非个人可能想要实现IPerson时,这才有意义。例如,如果IPerson可以HoldConversation(Stream input, Stream output),那么您可能有一个实现IPerson的TuringTestCandidate,而实际上并不是从Person派生的。

在更实用的术语中,当您想要对依赖于接口的某个类的行为进行单元测试,并且不希望基类中的更改带来开销或可能的干扰时,通常会使用此模式。对于这个简化的例子来说,这是没有意义的,但在实践中通常是有用的。

在这种情况下,除了抽象类满足接口的方法要求外,您的接口和抽象类是相当冗余的。我认为在这种情况下没有接口的必要,特别是考虑到有一个抽象类。

如果您要在具有双臂和双腿的对象上实现方法->IThingWithArmsAndLegs::DoTheHokeyPokey(),那么这可能是对接口的一个很好的使用。然后该接口可以在Person : IThingWithArmsAndLegsAlien : IThingWithArmsAndLegs之间共享。

在正确的情况下,我是两者的粉丝,为什么?

即使您只需要一个用于某些类型的IOC/DI的接口,它也不提供通用功能。您可以对Inject执行此操作,并通过公共抽象基类覆盖基本功能。然后根据需要只使用抽象/虚拟方法。

这是最好的IMHO接力。尤其是在多目标解决方案中。

我将为所有设备制作一次接口,然后为每个设备创建一个抽象类,该抽象类涵盖该设备类型共有的所有相同公共功能。

为了便于论证,您可以在抽象的Person基类中拥有通用功能,而不是所有实现接口IPerson的东西都需要减少重复代码。同时,您可以有一些例程,期望IPerson执行一些通用逻辑。

话虽如此,我根本不建议这样做。

对我来说,在CoalMiner的声明中指定PersonIPerson看起来很糟糕。我只想从Person中得到它。structur接口->抽象类->具体类对我来说很好,但在大多数情况下都过于夸张。如果实现接口的大多数类共享大量代码,我有时会使用它。因此,对于95%的简单情况,从抽象类派生是默认情况,但我希望保留完全独立实现接口的选项。

接口往往在依赖注入场景中被大量使用,因为它们被认为是"轻量级"依赖。使用这种方法,您往往会有定义很多东西的接口,并且通常最终会有实现接口的抽象类,以提供一些或所有接口成员的基本实现。

我倾向于认为这有点极端,特别是在您提供的示例中,抽象类除了属性之外没有提供任何内容。我不得不说,我自己有时也会对此感到内疚,通常是以接口使其更"可测试"为借口,并且对我选择的IoC容器更友好。最近,我一直在努力减少代码中的接口膨胀,这源于一种普遍的心态,即通过接口的松散耦合是正确的依赖注入所必需的,我意识到有些事情会变得愚蠢。

虽然CoalMiner不需要拥有IPerson接口,但我知道有些人更喜欢这样,所以很明显,该类型实现了接口。话虽如此,我不认为这样很有用。

定义名词的接口在企业系统中非常常见,因为您的系统处理多个其他系统,因此您可能需要支持多个数据访问层(DAL)。在这种情况下,您可能有以下抽象类

public interface ICustomer {}
public abstract class SapEntity {}
public abstract class NHibernateEntity {}    
public class SapCustomer : SapEntity, ICustomer {}
public class NHibernateCustomer : NHibernateEntity, ICustomer {}
public class CustomerProcessor
{
   public ICustomer GetCustomer(int customerID)
   {
      // business logic here
   }
}

我发现我经常需要将两者用于泛型基类。通常在某个时候,我需要传递对开放类泛型基类的引用,不幸的是,在C#中无法做到这一点,所以我创建了一个非泛型接口。

我可以看到同时拥有接口和抽象类的意义(接口定义了契约,抽象类可以有派生类可以共享的部分实现)。

然而,在派生类中同时指定父类和接口是多余的(这已经是隐含的,因为抽象类必须实现接口,否则它将无法编译)。

这种模式可能只是作为一种编码标准存在,因此其他程序员在查看其祖先实现该接口的具体类时会发现这一点。

无,如果您对同一接口扩展两次,它只在第一次使用。您可以删除第二个IPerson,您的代码仍然可以正常运行。