你能为一个只有它的界面建模吗;s的后代可以实现

本文关键字:建模 界面 后代 实现 一个 | 更新日期: 2023-09-27 18:27:02

假设以下代码:

namespace Example {
  public interface IBase {
    string CommonMember { get; set; }
  }
  public interface IDerived : IBase {
    void DoSomething();
  }
  public interface IComplexDerived : IBase {
    IEnumerable<object> Junk { get; }
  }
}

我目前正在处理的项目中也有类似的结构。接口IBase主要用于将IDerivedIComplexDerived的实例保持在同一容器中(如List<IBase>),并且不必重复常见的接口成员定义(如示例中的CommonMember)。

然后使用的一种方法是这样的:

public class Foo {
  public void Bar( IEnumerable<IBase> instances ) {
    foreach( IBase instance in instances ) {
      if( instance is IDerived ) { /* do something */ } 
      else if( instance is IComplexDerived ) {  /* do something else */ }
    }
  }
}

因此,没有什么可以阻止用户实现IBase并将该类的实例传递到系统中。但是这样做是完全无用的,因为整个库只期望处理实现从IBase派生的接口的类。

这个概念当然有充分的文档记录,不应该引起任何问题。然而,我想知道是否有可能通过语言本身来传达这一点。就像有一个抽象类,但对于接口。

你可能会问为什么不简单地使用一个抽象类。原因是我们不想强加从类继承的要求。

你能为一个只有它的界面建模吗;s的后代可以实现

我不确定这在您的实际情况下是否可行,但我认为您可以使用

  • IComplexDerived继承自IDerived而非IBase
  • 然后您将有一个IDerived而不是IBase的列表,因此即使是IBase的新实现也不会键入check(因为您需要IEnumerable<IDerived>
  • IComplexDerived继承的类将以不同的方式简单地实现DoSomething()。通过这样做,您可以让Bar方法以多态方式决定它需要调用什么DoSomething(并避免检查类型)

我的意思是:

  public interface IBase {
    string CommonMember { get; set; }
  }
  public interface IDerived : IBase {
    void DoSomething();
  }
  //IComplexDerived isnow a IDerived    
  public interface IComplexDerived : IDerived { 
    IEnumerable<object> Junk { get; }
  }
public class Foo 
{
  // Bar requires IEnumerable<IDerived> so you can't call it with a collection
  // of classes implementing IBase
  public void Bar( IEnumerable<IDerived> instances ) {
    foreach( IDerived instance in instances ) {
        instance.DoSomething(); // DoSomething will "do something else" in 
                                // classes implementing IComplexDerived
    }
  }
}

一种可能性是从IDerivedIComplexDervied中删除通用接口,并创建一个包装器类,该类使用其中一个的实例并提供通用功能:

public interface IDerived
{
    void DoSomething();
    string CommonMember { get; set; }
}
public interface IComplexDerived
{
    IEnumerable<object> Junk { get; }
    string CommonMember { get; set; }
}
public class EitherDerived : IBase
{
    private readonly IDerived derived;
    private readonly IComplexDerived complex;
    private readonly bool isComplex;
    public EitherDerived(IDerived derived)
    {
        this.derived = derived;
        this.isComplex = false;
    }
    public EitherDerived(IComplexDerived complex)
    {
        this.complext = complex;
        this.isComplex = true;
    }
    public string CommonMember
    {
        get
        {
            return isComplex ? complex.CommonMember : derived.CommonMember;
        }
        set
        {
            //...
        }
    }
    public TOut Either<TOut>(Func<IDerived, TOut> mapDerived, Func<IComplexDerived, TOut> mapComplex)
    {
        return isComplex ? mapComplex(complex) : mapDerived(derived);
    }
}

然后,如果你想确保你正在处理其中一个类:,你可以使用这个类而不是IBase接口

private object HandleDerived(IDerived derived) { ... }
private object HandleComplex(IComplexDerived complex) { ... }
public void Bar(IEnumerable<EitherDerived> instances)
{
    foreach(var either in instances)
    {
        object _ = either.SelectEither(HandleDerived, HandleComplex);
    }
}

我最终提出了寻找不同设计的建议。由于这是一个开源项目,我们可以看看实际结果。

  • IBaseITimelineTrackBase,描述了所有派生类型所共有的接口成员。

  • CCD_ 23是CCD_ 24,它描述了由具有开始和结束的单个元素组成的时间线上的轨道。

  • IComplexDerivedIMultiPartTimelineTrack,它描述了时间线上的轨道,该轨道由多个元素组成,每个元素都有开始和结束。

与我之前的计划相反,我没有将这些存储在List<IBase>中,而是使用List<IComplexDerived>。或者,就应用而言,List<IMultiPartTimelineTrack>

现在,如果IBase不是我真正想在该方法中支持的,我决定不在任何地方接受它。因此,ITimelineTrackBase纯粹用作基本接口,在库中的任何地方都不作为可接受的参数类型提供。

相反,整个库要么处理单轨元素(ITimelineTrack),要么处理这些元素的集合(IMultiPartTimelineTrack)。根据需要,前者通过辅助构造SingleTrackToMultiTrackWrapper被封装到后者中。

因此,我并没有让接口无法实现,而是让实现它变得毫无意义。