为什么要使用抽象类实现接口?

4

我看到一段代码,一个抽象类实现了一个接口?为什么要这样做?我们不是可以直接把抽象成员放在接口中吗?

为什么会有人写这样的代码?它的目的或需求是什么?

4个回答

6

接口只是与其他代码达成的协议。而抽象类则可以更加复杂。它们可以:

  • 拥有数据成员(字段)
  • 拥有实际的代码实现
  • 派生自另一个基类
  • 拥有被保护的成员

这种情况下,适合使用模板方法模式作为一个好的例子。你可以为命令创建一个接口:

public interface IMyCommand
{
    void Execute();
}

你有一组按照特定顺序执行的命令。要强制执行此顺序,您可以让它们派生自一个抽象基类:

public abstract class MyTemplateClass : IMyCommand
{
    public void Execute()
    {
         MyProcessFirst();
         MyProcessSecond();
    }

    protected abstract void MyProcessFirst();
    protected abstract void MyProcessSecond();
}

现在,您可以让遵循模板方法的对象从抽象基类继承,并让其他对象只实现接口。

是的。确定对于你正在处理的类型层次结构来说什么最有意义,确实是一种情况分析的方法。也许你想要一个抽象类来表示一些基本功能,同时你也可能想要一个接口来模拟单元测试。你可能处于一个大型类型层次结构中,其中有许多抽象类,所有这些抽象类都共享一个接口。这是一种情况分析的基础,以满足应用程序的需求。 - Ken Mason
@Bradley...非常感谢您,我真的很感激。这是一个非常好的例子,对于实时项目非常有用。 - user2683098

2
我们不能直接将抽象成员放入接口,因为我们可以在抽象类中拥有实现的一部分,从而获得重用的所有优势。但是,我们可以问相反的问题; 为什么不只有抽象类而没有接口呢?通常情况下,特别是对于抽象类和接口的私有或内部组合,这确实是一种改进。但是,接口仍然具有抽象类没有的一些优点。一个明显的优点是,如果我们有或可能有没有从该抽象类派生的实现,那么就可以利用接口。另一个优点是利用接口中的差异,而类无法做到这一点。即使现在没有任何实现通过抽象类而不是实现接口,如果接口是公共的(YAGNI适用于公共接口,其中删除任何内容都将是破坏性的更改),考虑到这一点也是值得的。与此相关的是,公共接口可能是客户端代码实现以及您的代码所使用的抽象类的有用组合,因为它包含您的实现的共同点,这些共同点不一定对其他人的实现来说是共同的。也许您会允许其他人从中继承,而不坚持这样做,也许您只会有内部构造函数,因此只有您的代码可以使用它。

0
假设您正在开发一个流行的库,它有一个名为 IStream 的接口,该接口在整个库中的各种 API 中使用。 IStream 有以下方法:
int Read(byte[] buffer, int offset, int count);
void Write(byte[] buffer, int offset, int count);

但是,您建议用户不要直接实施该界面,而是高度建议他们从您的抽象类 Stream 继承,该类如下实现该界面:

public abstract int Read(byte[] buffer, int offset, int count);
public abstract void Write(byte[] buffer, int offset, int count);

很多人都会遵循你的建议,但并不是每个人都会阅读文档,所以有些人会直接实现IStream
现在你的库推出了下一个版本。你非常兴奋,因为你将为你的流实现异步操作。所以你向IStream添加了以下方法:
Task<int> ReadAsync(byte[] buffer, int offset, int count);
Task WriteAsync(byte[] buffer, int offset, int count);

现在你需要更新你的抽象类以使其编译。你可以将新方法设为抽象方法,但事实证明,还有一种不完全疯狂的替代方案(省略错误处理):

public virtual Task<int> ReadAsync(byte[] buffer, int offset, int count)
{
    return Task.Run(() => this.Read(buffer, offset, count));
}

public virtual Task WriteAsync(byte[] buffer, int offset, int count)
{
    return Task.Run(() => this.Write(buffer, offset, count);
}

对于许多真实世界的流类型,通常会有一种不同的处理异步读写的方式,它可能比这个方法高效得多(这就是为什么它是虚拟的而不是密封的),但这可能对大多数消费者来说已经足够好了。

现在让我们看看这两组人。继承抽象类的人自动获得了 IStream.ReadAsyncIStream.WriteAsync 的实现,而无需编写任何其他代码。他们的流也可以直接在您的新型异步 API 中使用,而无需进行任何工作,这样就有可能不会出现问题。

另一方面,实现接口的人现在必须处理自己的那些 IStream 方法的实现,即使他们没有兴趣使用异步 API。也许他们会抛出 NotSupportedException 来消除错误。现在他们需要确保不调用任何可能调用 IStream.ReadAsyncIStream.WriteAsync 的东西。他们不开心。他们没有遵循您的建议而自食其果,但仍然很难不同情他们。

让抽象类实现接口是一个很大的优势。事实上,有些人可能会认为IStream根本不应该存在,就是因为这个原因,抽象的Stream类应该出现在所有API中,而不是IStream。瞎猜一下:也许这正是为什么有System.IO.Stream,但没有System.IO.IStream确切原因。虽然我个人更喜欢保留IStream


0
一个可能的原因是,如果你使用抽象类实现接口,你不需要在抽象类中实现所有的方法。你可以只留下方法声明(间接地实现它),而不实际实现它们。

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