【问题标题】:Programming against multiple interfaces针对多个接口进行编程
【发布时间】:2013-04-16 11:30:49
【问题描述】:

我非常喜欢这个提示:“针对接口编程,而不是针对实现编程”,并且我一直在努力遵循它。然而,当我必须将我的代码与 必须 从多个接口继承的对象中分离时,我怀疑如何保持这一原则有效。一个典型的例子可能是:

namespace ProgramAgainstInterfaces
{
    interface IMean
    {
            void foo();
    }  

    class Meaning : IMean , IDisposable
    {
        public void Dispose()
        {
            Console .WriteLine("Disposing..." );
        }

        public void foo()
        {
            Console .WriteLine("Doing something..." );           
        }
    }

   class DoingSomething
   {
        static void Main( string[] args)
        {
            IMean ThisMeaning = (IMean ) new Meaning ();  // Here the issue: I am losing the IDisposable methods
            ThisMeaning.foo();
            ThisMeaning.Dispose();                     // Error: i cannot call this method losing functionality
        }
   }   
}

解决此问题的一种可能方法是定义一个继承自两个接口的 ad-hoc 接口:

namespace ProgramAgainstInterfaces
{
    interface IMean
    {
            void foo();
    }

    interface ITry : IMean , IDisposable
    {
    }

    class Meaning : ITry
    {
        public void Dispose()
        {
            Console .WriteLine("Disposing..." );
        }

        public void foo()
        {
            Console .WriteLine("Doing something..." );           
        }
    }

   class DoingSomething
   {
        static void Main( string[] args)
        {
            ITry ThisMeaning = (ITry ) new Meaning ();  // This works
            ThisMeaning.foo();
            ThisMeaning.Dispose();   // The method is available
        }
   }   
}

但我不确定这是否是更紧凑和更有效的解决方案:我可以拥有更复杂的多重继承层次结构,这会增加复杂性,因为我必须创建接口仅用作容器。有更好的设计方案吗?

【问题讨论】:

  • 这可能是一个有趣的问题,可以在programmers.stackexchange.com 提出。
  • 通常你会检查接口的实现——例如var disposable = ThisMeaning as IDisposable; if(disposable != null) disposable.Dispose() - 这意味着您在运行时发现实现,而不是在编译时了解它们
  • @Charleh:回答一下
  • @Thilo 完成!我更喜欢这种方法,因为这意味着您可以通过支持越来越多的接口来附加地构建类型 - 显然,请留意任何警告,但在类设计阶段,您应该知道无论如何应该支持接口明智
  • 我不喜欢进行运行时检查以查看您的对象是否实际上实现了除了您知道它实现的接口之外的其他接口。为什么会呢?我会说您正在使用具体类(含义)的隐性知识来影响您的代码,我认为这破坏了对接口进行编程的整个要点。使用类型系统使您的假设明确。如果您需要一次性类型,您应该针对扩展 IDisposable 的接口编写代码,或者直接针对具体类型编写代码。

标签: c# oop


【解决方案1】:

代码到接口而不是实现 - “接口”是一个通用术语,它并不是字面上的关键字interface。使用abstract 类符合该原则。如果您需要一些实施,也许会更好。此外,子类总是可以根据需要实现interfaces。

【讨论】:

  • 我不认为这是正确的。对抽象类进行编码根本不像对接口进行编码。您对接口进行编码的主要原因之一是其他一些类可以使用完全独立的实现对同一个接口进行编码——扩展抽象类将类更紧密地绑定在一起,这是您在说“接口代码”
  • @Bill K,当然,abstract 类与实现interface 并不完全相同。但它们都是多态机制,它们都可以定义一个公共接口。它们不是相互排斥的。在任何设计中,将抽象(甚至具体)类与“接口”实现混合以表达业务/域正是人们应该做的。太糟糕了“接口”的意思是重载了,但是“..code to interfaces..”绝对不代表“不使用抽象类”。
  • 让我说清楚。根据定义,abstract 类有 1 个或多个声明为 abstract 的方法 - 这是子类必须实现的签名。此外,任何修饰符都可以应用于其方法,从而为子类提供不可变和/或可选的实现。然后子类被引用为它们的基类,因此客户端代码有一个接口来编码,同时保证 a) 所有需要的方法(即接口)都在子类中 b) 常见行为是相同的 c) 子类表现出适当的多态性。这就是抽象类是接口的方式和原因。
  • 我理解其中的区别。问题不是一个确切的定义,但是抽象类的重点是继承一些功能/实现。接口的重点是能够完全替换实现,只需提供一个模板来访问您的功能。即使没有实现方法的抽象类在实现中非常接近接口,它们的用途也非常不同。
  • 抱歉继续,但更重要的是抽象类定义了它的子类,因为你只能有一个父类。这意味着如果另一个系统(具有另一个继承树)想要使用您的库,如果您的库要求它们从抽象类继承,那么它们不能成为它们自己的继承树的一部分。这是一个巨大的问题,也是在 20 年的 OO 编程中,我将抽象类的使用从一些减少到几乎没有使用接口的原因。
【解决方案2】:

您还可以引入必须实现多个接口的泛型类型T。以下是使用IFooIDisposable 的示例:

class Program
{
    static void Main(string[] args)
    {
    }

    interface IFoo
    {
        void Foo();
    }

    class Bar<T> where T : IFoo, IDisposable
    {
        public Bar(T foo)
        {
            foo.Foo();
            foo.Dispose();
        }
    }
}

这有点复杂。如果IFoo : IDisposable 从设计的角度来看是错误的,这可能是有道理的。

【讨论】:

    【解决方案3】:

    当您的代码需要一个类型实现多个不同的接口时,这正是您通常必须做的。但是根据代码的语义,可能会发生很多变化。

    例如,如果IMean 不一定是IDisposable,但有许多消费者确实要求他们的IMean 是一次性的,那么您自己提出的解决方案是可以接受的。您也可以使用抽象基类来执行此操作——“接口程序”不使用“接口”,如“interface 关键字定义的语言构造”,而是“对象的抽象版本”。

    事实上,您可以要求您使用的任何类型都实现ITry(因此是一次性的),并简单地记录某些类型可以将Dispose 作为无操作实现。如果使用抽象基类,您还可以默认提供此无操作实现。

    另一种解决方案是使用泛型:

    void UseDisposableMeaning<T>(T meaning) where T : IMean, IDisposable
    {
        meaning.foo();
        meaning.Dispose();
    }
    
    // This allows you to transparently write UseDisposableMeaning(new Meaning());
    

    还有一种情况是消费者严格要求只需要IMean,但也需要具有一次性意识。你可以通过寻找类型来解决这个问题:

    IMean mean = new Meaning();
    var disposable = mean as IDisposable;
    if (disposable != null) disposable.Dispose();
    

    虽然这是一个可以接受的实用解决方案(特别是考虑到IDisposable 是“不仅仅是任何 界面”),但如果您发现自己一次又一次地这样做,您绝对应该退后一步;一般来说,任何形式的“类型转换”都被认为是不好的做法。

    【讨论】:

      【解决方案4】:

      在这种情况下,您的替代方案似乎有点人为。让 IMean “实现” IDisposable。针对类进行编程并非每次都不好,我视情况而定。

      【讨论】:

        【解决方案5】:

        如果作为“IMean”对象始终是一次性的,那么你应该让接口实现它:

        public interface IMean : IDisposable
        {
            ...
        }
        

        但是,如果让一个对象实现 IMean 而不是一次性的有意义,那么我认为您建议的解决方案是最好的:创建一个中间接口,以便您可以:

        public interface IMean
        {
            ...
        }
        
        public interface IDisposableMean : IMean, IDisposable
        {
            ...
        }
        

        【讨论】:

          【解决方案6】:

          为了组合构建,您可以通过强制转换来检查对象是否支持特定的功能(接口):

          例如

          // Try and cast
          var disposable = ThisMeaning as IDisposable; 
          
          // If the cast succeeded you can safely call the interface methods
          if(disposable != null) 
              disposable.Dispose();
          

          这意味着您在运行时发现实现,而不必在编译时知道它们,并且您的类型不需要实现IDisposable

          这满足了一次性要求,而不必知道IMean 的类型为Meaning(您仍然可以使用IMean refs)

          【讨论】:

            【解决方案7】:

            您应该让interface 实现IDisposable 而不是Meaning。这样,当投射到 interface 时,您不会失去 IDisposable 能力(因为它是在您的 interface 级别定义的)。

            像这样:

            namespace ProgramAgainstInterfaces
            {
                interface IMean : IDisposable
                {
                    void foo();
                }
            
                interface ITry : IMean
                {
                }
            
                class Meaning : ITry
                {
                    public void Dispose()
                    {
                        Console .WriteLine("Disposing..." );
                    }
            
                    public void foo()
                    {
                        Console .WriteLine("Doing something..." );           
                    }
                }
            
               class DoingSomething
               {
                    static void Main( string[] args)
                    {
                        ITry ThisMeaning = (ITry ) new Meaning ();  // This works
                        ThisMeaning.foo();
                        ThisMeaning.Dispose();   // The method is available
                    }
               }   
            }
            

            【讨论】:

            • 那么,如果不是所有提供IMean 接口的对象都需要处理,那么您只需创建一个空的处理方法来满足接口吗? (不说这是好是坏,只是想了解一下)。
            • @GeorgeDuckett 对于这个场景,他必须在接口IMean 上实现IDisposable,纯粹是因为他在将Dispose 转换为interface 之后尝试调用它。如果IMean 接口中的所有对象都不需要处理(并且说他在调用处理之前没有强制转换到接口),那么Dispose 实现将更适合Meaning 类。仅仅为了满足一个接口而实现的方法可以指向一个不应该使用的接口,或者一个不属于该接口的方法。希望这是有道理的。
            • 感谢您的解释。
            • +1。这实际上是 .NET 框架使用的方法:Stream 实现 IDisposable,这是 MemoryStream 的 NOP,但不是 FileStreamDbCommand 实现了IDisposable,这(基本上)是SqlCommand 的NOP,但不是OleDbCommand
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-29
            • 1970-01-01
            • 1970-01-01
            • 2010-12-08
            • 1970-01-01
            • 2014-10-13
            相关资源
            最近更新 更多