0

最近我在学习面向对象编程中的组合。在许多关于它的文章中,有一个 FileStore 类(或者你怎么称呼它)的例子,它实现了如下接口:

interface IStore
{
   void Save(int id, string data);
   string Read(int id);
}

FileStore 类实现了这个接口,一切都很好。它可以保存和读取文件。现在,教程文章经常说 FileStore 可以很容易地交换为,例如 SqlStore,因为它可以实现相同的接口。这样,客户端类可以存储到 SQL 而不是文件系统,只需注入 SqlStore 而不是 FileStore。听起来不错,但当我真正想到它时,我真的不知道如何实现它。数据库操作很适合异步完成。为了使用这个事实,我需要在 Read(int id) 方法中返回一个 Task 而不仅仅是字符串。Save(int id, string data) 也可以返回一个任务。考虑到这一点,我不能真正将 IStore 用于我的 SqlStore 类。

我看到的一种解决方案是使 IStore 像这样:

interface IStore
{
   Task Save(int id, string data);
   Task<string> Read(int id);
}

然后,我的 FileStore 类需要稍作更改,它将使用 Task.FromResult(...)。

这个解决方案对我来说似乎不方便,因为 FileStore 必须假装一些异步特性。

你会提出什么解决方案?我很好奇,因为在教程中一切听起来都很简单可行,但是当涉及到实际编程时,事情就变得复杂了。

4

2 回答 2

2

我见过不同的方法来解决这个问题,但选择的方法总是取决于你的情况。

  1. 将接口更改为Task-based。

如果您没有任何实现(或者它们可以很容易地更改)或者您不想保留任何同步版本,最简单的方法就是更改接口:

interface IStore
{
   Task SaveAsync(int id, string data);
   Task<string> ReadAsync(int id);
}
  1. 扩展接口

另一种选择是将方法的异步版本添加到接口。如果有同步的实现或者已经有太多的实现,这可能不是最佳选择。

interface IStore
{
    void Save(int id, string data);
    Task SaveAsync(int id, string data);

    string Read(int id);
    Task<string> ReadAsync(int id);
}
  1. 通过继承创建异步版本

当您无法更改原始接口或并非所有实现都需要异步版本时,此选项最有意义。当然,您需要决定是要求 anIStore还是IAsyncStore.

interface IStore
{
    void Save(int id, string data);
    string Read(int id);
}

interface IAsyncStore : IStore
{
    Task SaveAsync(int id, string data);
    Task<string> ReadAsync(int id);
}

注意:对于 I/O 操作(在您的示例中两者都FileStore执行SqlStore),应该只有异步方法。

于 2018-08-19T18:12:29.160 回答
0

当我自己创建异步方法时,我总是只为可以在新线程中完成的操作执行此操作,并且这样做是安全的。

因此,如果我知道我可以让FileStore类的Save和/或Read方法毫无问题地在单独的线程中运行,我肯定会将返回值更改为 Tasks。

如果接口必须是完整的(如果它不是由您的项目创建的,或者其他一些代码依赖于它的同步方法),除了原始方法之外,还可以创建方法的异步版本,如下所示:

interface IStore
{
  void Save(int id, string data);
  Task SaveAsync(int id, string data);
  string Read(int id);
  Task<string> ReadAsync(int id);
}

(Microsoft 编码指南建议Async在任何异步方法的末尾使用后缀。)

IStoreAsync根据您的需要,另一种方法可能是使用异步版本创建接口。

在方法的异步版本中,他们现在可以创建一个调用同步方法的新线程,并等待线程完成,如下所示:

async Task<string> ReadAsync(int id) {
  string value = await Task.Run(() => Read(id));
  return value;
}

请注意,可能有很多更安全和更好的方法来创建新线程,但Task.Run作为示例和测试目的。

于 2018-08-19T16:08:28.387 回答