我看到一段代码,一个抽象类实现了一个接口?为什么要这样做?我们不是可以直接把抽象成员放在接口中吗?
为什么会有人写这样的代码?它的目的或需求是什么?
接口只是与其他代码达成的协议。而抽象类则可以更加复杂。它们可以:
这种情况下,适合使用模板方法模式作为一个好的例子。你可以为命令创建一个接口:
public interface IMyCommand
{
void Execute();
}
你有一组按照特定顺序执行的命令。要强制执行此顺序,您可以让它们派生自一个抽象基类:
public abstract class MyTemplateClass : IMyCommand
{
public void Execute()
{
MyProcessFirst();
MyProcessSecond();
}
protected abstract void MyProcessFirst();
protected abstract void MyProcessSecond();
}
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.ReadAsync
和 IStream.WriteAsync
的实现,而无需编写任何其他代码。他们的流也可以直接在您的新型异步 API 中使用,而无需进行任何工作,这样就有可能不会出现问题。
另一方面,实现接口的人现在必须处理自己的那些 IStream
方法的实现,即使他们没有兴趣使用异步 API。也许他们会抛出 NotSupportedException 来消除错误。现在他们需要确保不调用任何可能调用 IStream.ReadAsync
或 IStream.WriteAsync
的东西。他们不开心。他们没有遵循您的建议而自食其果,但仍然很难不同情他们。
让抽象类实现接口是一个很大的优势。事实上,有些人可能会认为IStream
根本不应该存在,就是因为这个原因,抽象的Stream
类应该出现在所有API中,而不是IStream
。瞎猜一下:也许这正是为什么有System.IO.Stream,但没有System.IO.IStream
的确切原因。虽然我个人更喜欢保留IStream
。