针对多个接口进行编程

本文关键字:编程 接口 | 更新日期: 2023-09-27 18:10:41

我非常喜欢这个提示:"根据接口编程,而不是根据实现编程",并且我一直在努力遵循它。然而,当我必须将代码与必须从多个接口继承的对象解耦时,我怀疑如何保持这一原则的工作。一个典型的例子可以是:

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

解决这个问题的一个可能的方法是定义一个从两个接口继承的特别接口:

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

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

针对多个接口进行编程

如果"IMean"对象总是可丢弃的,那么你应该让接口实现它:

public interface IMean : IDisposable
{
    ...
}

然而,如果有一个对象实现IMean而不是一次性的,那么我认为你建议的解决方案是最好的:创建一个中间接口,这样你可能有:

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

您应该让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
        }
   }   
}

您还可以引入必须实现多个接口的泛型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 是错误的

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

例如,如果IMean不一定是IDisposable,但有许多消费者确实要求他们的IMean是一次性的,那么您自己提出的解决方案是可以接受的。你也可以使用一个抽象基类来做到这一点——"编程到接口"并不像"由interface关键字定义的语言结构"那样使用"接口",而是使用"对象的抽象版本"。

事实上,你可以要求你消费的任何类型实现ITry(因此是一次性的),并简单地记录一些类型将Dispose实现为no-op是可以的。如果使用抽象基类,也可以默认提供这种无操作实现。

另一个解决方案是使用泛型:
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"不仅仅是任何接口"),但如果你发现自己一次又一次地这样做,你绝对应该退后一步;一般来说,任何形式的"类型切换"都被认为是不好的做法。

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

// 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 ref)

代码到接口而不是实现 - "接口"是一个通用术语,它并不意味着字面上的关键字interface。使用abstract类遵循这一原则。如果你需要一些实现可能会更好。此外,子类总是可以根据需要实现interface s。

在这种情况下,您的替代方案似乎有点人为。让我的意思是"实现"一次性的。基于类的编程并不总是不好的,这取决于具体情况。