接口继承是一种不好的实践吗?

9
我想知道以下代码是否展示了一个不好的实践(关于接口继承):
public interface IFoo : IDisposable
{
    void Test();
}

public class TestImpl : IFoo
{
    public void Test()
    {
        // do something
    }

    public void Dispose()
    {
        // disposing my dependencies
    }
}

public class FooFactory
{
    public IFoo CreateFoo()
    {
        return new TestImpl();
    }
}

public class Client
{
    public void Main()
    {
        FooFactory factory = new FooFactory();
        using(IFoo foo = factory.CreateFoo())
        {
            // do stuff then auto-dispose
            foo.Test();
        }
    }
}

在这里,我想要两件事情: 1. 使用C#的using语句,以便我可以优雅地处理我的具体对象的依赖项(无需将任何内容转换为IDisposable)。 2. 使用工厂方法创建一个具体对象,这样我就可以仅使用接口来工作。
这两个实践都是众所周知的好习惯,但是一起使用呢?我没有找到任何说这是错误的东西 http://msdn.microsoft.com/en-us/library/ms229022.aspx (MSDN - 接口设计),但我觉得这听起来不太好...
我会感激任何关于此事的评论。如果这是一个不好的实践,使用这种方法会遇到哪些问题?
谢谢!

1
几乎每个可处理的.NET对象都基本上是这样的。 - msarchet
请查看我的更新,您可以使用Google在.NET Framework本身中找到这样的接口。 - Dan Abramov
4个回答

20

在实际应用中,不存在绝对的好或坏的做法,每个做法都有其优缺点。
只有当某个做法应用于具体问题时(即:不是IFoo),它才会成为好或坏的做法。

public bool IsGood<TContext>(IPractice practice)
    where TContext : RealWorldApplication, new();

因此,不要试图在MSDN上寻找指导,而是问自己以下问题:

这是否解决了问题并使代码更易于阅读?

如果答案是肯定的话,就采用它。

个人而言,我喜欢这种方法。如果IFoo的合同要求进行适当的处理(例如:IGraphicalObjectIResourceIConnection),那么明确指出这一点是一个非常好的设计决策。

不要被教条限制:使用接口编程会使您不那么依赖具体实现,但仅仅使用接口可能会成为噩梦。要合理,就这样。

更新

您可以使用Google查找在.NET Framework中继承IDisposable的所有接口:

intitle:interface "inherits idisposable" site:msdn.microsoft.com

如我在上面所说的,这通常用于已知消耗资源的实体,例如数据库连接、非托管对象封装、图形对象等。

在没有任何内容需要处理的类上强制实现Dispose()是一个坏主意。
在这种情况下,最好将其保留为可选项并检查IDisposable支持,就像Peter建议的那样


1
在实际应用中,存在很多绝对的糟糕做法。 - H H
3
是的,但你不能在没有看到实际应用的情况下就百分之百地说某个东西是不好的做法。"做法"这个词很难定义;在某些情况下,通常被认为是不好的做法实际上可能是在具体情况下做某事的最佳方式。我只是建议你自己思考(当然,这仅适用于你信任自己的思维——如果我不确定,我会描述我的确切情况并在StackOverflow上寻求建议,而不是询问“做法”)。 - Dan Abramov
@Henk:我进行了一次编辑以突出显示这个问题,也许你想看一下。 - Dan Abramov
1
如果实现接口的对象可能需要知道何时有人完成使用它,特别是最后一个使用它的实体可能不知道其具体类型,则接口应继承IDisposable。即使99.9%的接口实现不关心,省略IDisposable也可能会对最后几个实现造成问题。一个很好的例子是IEnumerator<T>。大多数枚举器不在乎何时不再需要它们,但是有一些类型确实需要。如果IEnumerator<T>没有实现IDisposable,则使用枚举器的代码将不得不... - supercat
1
要么尝试将枚举器转换为IDisposable并在转换成功后调用Dispose,否则跳过处理并希望这不会引起问题,例如保留文件打开等。通常,让IEnumerator<T>继承IDisposable并让使用者无条件地处理,比检查是否需要处理并仅在必要时调用处理更快。 - supercat

10

这是完全有效的。这正是接口的作用。它们定义协议(即契约)。一个协议可以使用多个子协议来构建。


3

我认为这是正确的。你的工厂隐藏了实际实例化的类,所以IFoo必须继承IDisposable,以便客户端能够调用Dispose()。使用这个类,客户端可以完全不知道实际实例化的类。所有相关信息都应该由接口携带 - 包括需要处理的内容。


2

从技术角度来看,这个解决方案没有问题。

从语义上讲,你可以认为需要IDisposable仅仅是一个实现细节,与你的类的实际契约无关。

你可以将对象强制转换为IDisposable,只有当它实现了IDisposable时才调用Dispose()

var disposable = foo as IDisposable;

if (disposable != null)
    disposable.Dispose();

但在这种情况下,我会选择接口继承,正如你建议的那样。虽然它不是最干净的方法,但完美地完成了您的工作,并且需要更少的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接