【问题标题】:Referencing an implementing base type in an interface在接口中引用实现基类型
【发布时间】:2010-09-30 12:10:46
【问题描述】:

我面临的设计决策对我来说并不陌生,但却让我停下来。看看下面的代码示例:

public interface IGenerator
{
  ///<summary>
  /// Combines two generators; performs magic as well
  /// </summary>
  BaseGenerator Combine(BaseGenerator next);

  ///<summary>
  /// Does what a generator does.
  /// </summary>
  object GenerateLolKThx();
}

public abstract class BaseGenerator : IGenerator
{  
  ///<summary>
  /// Combines two generators; performs magic as well
  /// </summary>
  public BaseGenerator Combine(BaseGenerator next)
  {
     // do stuff that I really don't want implementors to try and do
     // because its complex and can result in bad juju if done wrong
     return SuperSecretCombine(this, next);
  }

  ///<summary>
  /// Does what a generator does.
  /// </summary>
  public abstract object GenerateLolKThx();

  /* other base class methods */
}

我不想更详细地说明为什么我不想使用 Combine 方法信任实现者;足以说明它的复杂性。但是,我确实想尽我所能强迫任何想要实现 IGenerator 的人扩展 BaseGenerator,因为这是正确组合两个生成器的唯一方法。这是由接口本身强制执行的。

我担心我在该接口中引用某个接口的实现会导致出现意外问题(由“气味”表示)。但我也知道这种事情在 CS 中并非闻所未闻,它本身也不是坏事(即描述 XSD 的 XML 模式和用它们编译的语言编写的语言编译器)。

【问题讨论】:

  • 请注意,您的方法不能保证使用 your 版本;请参阅我的(更新的)答案,以展示实现如何窃取 Combine() 逻辑,以及如何使用扩展方法方法避免这个陷阱。

标签: .net class design-patterns interface


【解决方案1】:

我对这可能是代码异味的原因很感兴趣

这是我的答案:将接口与实现分离的目的之一(甚至可能是主要目的)是为了可以为接口创建许多不同的实现。将接口绑定到特定的实现类会破坏这一点。

您实际上是在说 - “此接口必须由 BaseGenerator 或其子类实现”,但是为什么要将 IGeneratorBaseGenerator 分开?

【讨论】:

    【解决方案2】:

    可以用扩展方法来代替吗?然后你就可以使用界面了。扩展方法是向接口添加通用实现逻辑的好方法。

    这对你们最好主要谈论IGenerator(不是具体类型)-因为扩展解析更喜欢类型-即FredsGenerator可以声明一个定制的Combine方法,这将(如果您输入的变量为FredsGenerator) 优先。但这与您的示例没有什么不同,因为 FredsGenerator 可以重新实现接口,窃取实现:

    using System;
    interface IFoo {
        void Bar();
    }
    abstract class FooBase {
        public void Bar() { Console.WriteLine("Non-virtual; you can't override me!!!"); }
    }
    class FooImpl : FooBase, IFoo {
        new public void Bar() { Console.WriteLine("mwahahahahah"); }
    }
    static class Program {
        static void Main() {
            IFoo foo = new FooImpl();
            foo.Bar();
        }
    }
    

    至少使用扩展方法方法您的代码不会被欺骗运行重新实现的版本;您的代码只知道IGenerator,因此将使用Generator.Combine 版本。永远。


    示例(edit从您的示例中修改):

    using System;
    public interface IGenerator
    {
        // note: no Combine
        object GenerateLolKThx();
    }
    
    public static class Generator
    {
        ///<summary>
        /// Combines two generators; performs magic as well
        /// </summary>
        public static IGenerator Combine(this IGenerator current, IGenerator next)
        {
            // do stuff that I really don't want implementors to try and do
            // because its complex and can result in bad juju if done wrong
            Console.WriteLine("Super secret logic here...");
            // and prove we have access the the objects...
            Console.WriteLine(current.GenerateLolKThx());
            Console.WriteLine(next.GenerateLolKThx());
            return next; // just for illustration
        }
    }
    class MyGenerator : IGenerator
    {
        private readonly object state;
        public MyGenerator(object state) {this.state = state;}
        public object GenerateLolKThx() { return state; }
    }
    public static class Program
    {
        static void Main()
        {
            IGenerator foo = new MyGenerator("Hello"),
                 bar = new MyGenerator("world");
    
            IGenerator combined = foo.Combine(bar);
        }
    }
    

    【讨论】:

    • 请解释一下。最好在你达到 20k 之前
    • 这方面的终极例子当然是 LINQ:如果每个 IEnumerable 实现都必须为 Where、OrderBy、GroupBy、Sum 等提供逻辑 - 这将是疯狂的。 LINQ 通过扩展方法方法提供了这些的共享实现。
    • 我喜欢这个并且一定会把它放在工具箱里。谢谢。
    【解决方案3】:

    根据您显示的有限代码,在我看来,您使用接口只是为了使用抽象基类更合适的接口。如果您不信任其他任何人来合并 BaseGenerator,那么这应该是您的层次结构的基础,并且 Combine 不应该是虚拟的,因此没有人可以覆盖它。

    【讨论】:

      【解决方案4】:

      BaseGenerator(及其子类)是唯一实现 IGenerator 接口的类吗?

      • 如果是这样,为什么要使用 IGenerator(为什么不使用 BaseGenerator)?
      • 如果没有,您是否期望(或不)实现 IGenerator 的其他非 BaseGenerator 类将能够定义 Combine 方法的合理实现?

      [这看起来像是一个问题而不是答案,但它是苏格拉底式的。]

      【讨论】:

        【解决方案5】:

        为什么不引用接口?

        public interface IGenerator
        {
          ///<summary>
          /// Combines two generators; performs magic as well
          /// </summary>
          IGenerator Combine(IGenerator next);
        
          ///<summary>
          /// Does what a generator does.
          /// </summary>
          object GenerateLolKThx();
        }
        

        【讨论】:

        • 猜测一下:因为这意味着 IGenerator 的实现者需要每个编写“我真的不希望实现者尝试做的东西,因为它很复杂并且可以如果做错了会导致坏juju”
        • 啊,这很有道理……在那种情况下,我可能会倾向于扩展方法的想法。
        • @Marc Gravell:接口不能有一个嵌套的静态类来提供应该是常见的实现,文档建议接口调用静态类方法,接口实例作为第一个参数?跨度>
        【解决方案6】:

        听起来如果多重继承可用,这整个事情可以更好地实现。如果我是对的,这里没有代码气味。如果我错了,很可能会有一种奇怪的代码味道,因为该接口与其实现之一紧密耦合。

        【讨论】:

          猜你喜欢
          • 2016-07-22
          • 1970-01-01
          • 1970-01-01
          • 2020-03-13
          • 1970-01-01
          • 1970-01-01
          • 2012-12-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多