使用BOTH抽象类和接口的原因? (抽象类实现接口)

时间:2011-05-31 19:01:08

标签: c# oop interface abstract-class

最近我在一些代码中遇到了一个奇怪的模式。我们知道所有事情都有时间和地点,特别是涉及到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以及定义相同成员的界面的具体优势是什么?

14 个答案:

答案 0 :(得分:17)

就个人而言,我觉得使用界面来描述一个“名词”,比如一个人,它通常是一个糟糕的设计选择。

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

答案 1 :(得分:10)

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

基类倾向于实现应该由该基类的所有后代使用的公共代码。如果这是你想要的,那就没关系了,但是可能会遇到需要完全不同的IPerson实现的情况,其中基类Person根本不被使用。现在你有两个共同IPerson的类层次结构,但事情仍然有效。您将无法仅使用Person执行此操作。

总是有一个接口冗余的另一个好理由是COM互操作。

答案 2 :(得分:4)

界面和ABC都有意义的情况是使用装饰器模式。 ABC用于为不同的具体实现类提供通用实现代码。所有实现类都可能来自ABC。

装饰器包装现有实例并调整其功能通常只实现接口而不是从ABC派生。如果有很多装饰器,可能会有另一个ABC提供装饰者需要的常见合成处理和函数调用转发。

明确提及界面有时会使其更具可读性。 MSDN文档经常这样做,例如表明List<>同时实现了ICollection<>IList<>,尽管IList<>来自ICollection<>

答案 3 :(得分:3)

我可以想到的一个派生类显式实现与其基类相同的接口的唯一优势是禁止派生类隐藏成员并因此破坏接口。

答案 4 :(得分:3)

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

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

答案 5 :(得分:2)

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

如果你要在有两条腿和两条腿的物体上实施方法 - &gt; IThingWithArmsAndLegs::DoTheHokeyPokey()可以很好地利用界面。然后,可以在Person : IThingWithArmsAndLegsAlien : IThingWithArmsAndLegs之间共享此界面。

答案 6 :(得分:2)

我是两个在适当情况下的粉丝,为什么?

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

它是最好的恕我直言。特别是在多目标解决方案中。

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

答案 7 :(得分:1)

仅仅为了参数,您可以在抽象Person基类中具有通用功能,而不是所有实现接口IPerson的东西都需要减少重复代码。与此同时,您可能会遇到一些期望IPerson执行某些常见逻辑的例程。

话虽如此,我根本不会推荐这种做法。

答案 8 :(得分:1)

对我来说,在Person的声明中指定IPersonCoalMiner看起来很糟糕。我只想从Person推导出来。结构界面 - &gt;抽象类 - &gt;具体课对我很好,但在大多数情况下都是矫枉过正。如果实现接口的大多数类共享大量代码,我有时会使用它。因此,从抽象类派生是95%简单情况的默认情况,但我想保留选项以完全独立地实现接口。

答案 9 :(得分:1)

接口往往在Dependency Injection场景中被大量使用,因为它们被认为是“轻量级”依赖项。使用这种方法,您倾向于使用定义很多内容的接口,并且通常最终会生成实现接口的抽象类,以提供部分或全部接口成员的基本实现。

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

答案 10 :(得分:1)

虽然CoalMiner没有 NEED 来拥有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
   }
}

答案 11 :(得分:1)

我发现我经常需要同时使用通用基类。通常在某些时候我需要传递对open class泛型基类的引用,遗憾的是你不能在C#中做,所以我创建了一个非泛型接口。

答案 12 :(得分:1)

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

但是,在派生类中同时指定父类和接口是多余的(已经隐含了它,因为抽象类必须实现接口或者它不会编译)。

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

答案 13 :(得分:0)

无,如果您将相同的界面扩展两次,则仅在第一次使用时使用。您可以删除第二个IPerson,但您的代码仍可正常运行。