我非常喜欢这样的提示:“针对接口的程序,而不是实现”,我试图始终如一地遵循它。但是,当我必须将我的代码与必须从多个接口继承的对象分离时,我怀疑如何保持这个原则。典型的例子可能是:
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
}
}
}
但我不确定这是否是更紧凑和有效的解决方案:我可能有更复杂的多继承层次结构,这增加了复杂性,因为我必须创建接口才能充当容器。有更好的设计解决方案吗?
答案 0 :(得分:21)
如果作为“IMean”对象涉及始终是一次性的,那么你应该让接口实现它:
public interface IMean : IDisposable
{
...
}
但是,如果有一个对象实现IMean而不是一次性的意义,那么我认为你建议的解决方案是最好的:创建一个中间接口,所以你可能有:
public interface IMean
{
...
}
public interface IDisposableMean : IMean, IDisposable
{
...
}
答案 1 :(得分:10)
您应该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
}
}
}
答案 2 :(得分:7)
您还可以引入必须实现多个接口的泛型类型T
。以下是使用IFoo
和IDisposable
的示例:
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 :(得分:5)
当您拥有需要类型的代码实现多个不同的接口时,这正是您通常必须执行的操作。但是根据代码的语义,可能发生的变化有很多变化。
例如,如果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 :(得分:4)
为了构建,您可以通过强制转换检查对象是否支持特定的功能(接口):
e.g。
// 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)
答案 5 :(得分:2)
不实现接口的代码 - “接口”是一个通用术语,它并不意味着字面意思是关键字interface
。使用abstract
类遵循该原则。如果你需要一些实现,也许更好。此外,子类总是可以根据需要实现interface
。
答案 6 :(得分:1)
在这种情况下,您的替代方案似乎有点人为。让IMean“实现”IDisposable。针对课程的编程并非每次都很糟糕,我依赖于这种情况。