简体   繁体   English

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

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

Assume the following code: 假设以下代码:

namespace Example {
  public interface IBase {
    string CommonMember { get; set; }
  }

  public interface IDerived : IBase {
    void DoSomething();
  }

  public interface IComplexDerived : IBase {
    IEnumerable<object> Junk { get; }
  }
}

I have a similar structure in the project I'm currently working on. 我正在进行的项目中有类似的结构。 The interface IBase primarily serves the purpose to be able to keep instances of IDerived and IComplexDerived in the same container (like a List<IBase> ) and also not having to repeat common interface member definitions (like CommonMember in the example). 接口IBase主要用于能够将IDerivedIComplexDerived实例IComplexDerived在同一容器中(如List<IBase> ),也不必重复公共接口成员定义(如CommonMember中的CommonMember )。

One way this would then be used would be something like this: 然后使用的一种方法是这样的:

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 */ }
    }
  }
}

So, nothing would stop the user from implementing IBase and passing instances of that class into the system. 因此,没有什么能阻止用户实现IBase并将该类的实例传递到系统中。 But doing that would be completely useless because the whole library only expects to deal with classes that implement interfaces that were derived from IBase . 但这样做完全没用,因为整个库只需要处理实现从IBase 派生的接口的类。

This concept is of course fully documented and shouldn't cause any problems. 这个概念当然是完整记录的,不应该引起任何问题。 However, I was wondering if it would be possible to communicate this through means of the language itself. 但是,我想知道是否可以通过语言本身来传达这一点。 Like having an abstract class, but for interfaces. 就像有一个抽象类,但对于接口。

You might ask why not simply use an abstract class then. 您可能会问为什么不简单地使用抽象类。 The reason for that is that we don't want to impose the requirement to inherit from our class. 原因是我们不想强制要求从我们的类继承。

I'm not sure if this is feasible in your actual case, but I think you could have 我不确定这在你的实际案例中是否可行,但我认为你可以

  • IComplexDerived inherit from IDerived instead of IBase . IComplexDerived继承自IDerived而非IBase
  • You would then have a list of IDerived instead of IBase , so even a new implementation of IBase would not type-check (since you require an IEnumerable<IDerived> ) 然后你会有一个IDerived而不是IBase的列表,所以即使是新的IBase实现也不会进行类型检查(因为你需要一个IEnumerable<IDerived>
  • Your classes inheriting from IComplexDerived would simply implement DoSomething() in a different way. 继承自IComplexDerived类只会以不同的方式实现DoSomething() By doing this you would let your Bar method decide polymorphically what DoSomething it needs to call (and avoid checking on the type) 通过执行此操作,您可以让Bar方法以多态方式决定需要调用的DoSomething(并避免检查类型)

I mean something like this: 我的意思是这样的:

  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
    }
  }
}

One possibility is to remove the common interface from IDerived and IComplexDervied and create a wrapper class which takes an instance of one of them and provides the common functionality: 一种可能性是从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);
    }
}

Then you can use this class instead of your IBase interface if you want to be sure you are dealing with one of those classes: 然后,如果您想确定要处理其中一个类,则可以使用此类而不是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);
    }
}

The suggestion to look for a different design altogether was what I ended up doing. 完全寻找不同设计的建议就是我最终做的事情。 Since this is an open source project, we can look at the actual results. 由于这是一个开源项目,我们可以查看实际结果。

  • IBase is ITimelineTrackBase and describes interface members that are common to all derived types. IBaseITimelineTrackBase ,描述了所有派生类型共有的接口成员。

  • IDerived is ITimelineTrack and it describes a track on a timeline which consists of a single element with a start and end. IDerivedITimelineTrack ,它描述了时间轴上的轨道,该轨道由具有开始和结束的单个元素组成。

  • IComplexDerived is IMultiPartTimelineTrack and it describes a track on a timeline which consists of multiple elements that each have a start and an end. IComplexDerivedIMultiPartTimelineTrack ,它描述了时间轴上的轨道,该轨道由多个元素组成,每个元素都有一个开头和一个结尾。

Contrary to my earlier plans, I'm not storing these in a List<IBase> , but I'm using List<IComplexDerived> . 与我之前的计划相反,我不是将它们存储在List<IBase> ,而是使用List<IComplexDerived> Or, in terms of the application, a List<IMultiPartTimelineTrack> . 或者,就应用而言, List<IMultiPartTimelineTrack>

Now I decided to not accept an IBase anywhere if that's not what I actually want to support in that method. 现在我决定不接受任何地方的IBase ,如果那不是我真正想要支持的方法。 So the ITimelineTrackBase is used purely as a base interface and isn't offered as an accepted parameter type anywhere in the library. 因此, ITimelineTrackBase纯粹用作基本接口,并且不作为库中任何位置的可接受参数类型提供。

Instead the whole library deals either with single track elements ( ITimelineTrack ) or a collection of those ( IMultiPartTimelineTrack ). 相反,整个库处理单轨道元素( ITimelineTrack )或其集合( IMultiPartTimelineTrack )。 As needed, the former is wrapped into the latter by a helper construct SingleTrackToMultiTrackWrapper . 根据需要,前者通过辅助构造SingleTrackToMultiTrackWrapper包装到后者中。

So instead of making it impossible to implement the interface, I just made it pointless to implement it. 因此,我没有让它无法实现接口,而是让它实现它毫无意义。

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

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