繁体   English   中英

你能模拟一个只能实现它的后代的界面吗?

[英]Can you model an interface where only it's descendants can be implemented?

假设以下代码:

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实例IComplexDerived在同一容器中(如List<IBase> ),也不必重复公共接口成员定义(如CommonMember中的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 派生的接口的类。

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

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

我不确定这在你的实际案例中是否可行,但我认为你可以

  • IComplexDerived继承自IDerived而非IBase
  • 然后你会有一个IDerived而不是IBase的列表,所以即使是新的IBase实现也不会进行类型检查(因为你需要一个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 ,描述了所有派生类型共有的接口成员。

  • IDerivedITimelineTrack ,它描述了时间轴上的轨道,该轨道由具有开始和结束的单个元素组成。

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

与我之前的计划相反,我不是将它们存储在List<IBase> ,而是使用List<IComplexDerived> 或者,就应用而言, List<IMultiPartTimelineTrack>

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

相反,整个库处理单轨道元素( ITimelineTrack )或其集合( IMultiPartTimelineTrack )。 根据需要,前者通过辅助构造SingleTrackToMultiTrackWrapper包装到后者中。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM