0

假设以下代码:

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一个容器中(如 a 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

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

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

4

3 回答 3

2

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

  • 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
    }
  }
}
于 2013-08-08T10:56:30.123 回答
0

IDerived一种可能性是从and中删除公共接口IComplexDervied并创建一个包装类,该类采用其中之一的实例并提供公共功能:

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);
    }
}
于 2013-08-08T10:56:42.077 回答
0

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

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

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

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

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

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

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

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

于 2013-08-12T19:51:29.447 回答