Использование async Tasks с шаблоном построителя

В настоящее время я использую шаблон построителя для создания моделей MVC.

var viewModel = builder
                  .WithCarousel(),
                  .WithFeaturedItems(3),
                  .Build()

Проблема, с которой я сталкиваюсь, заключается в том, когда мне нужно сделать вызов службы для метода async. Это означает, что мой метод-строитель должен вернуть Task<HomeViewModelBuilder> вместо HomeViewModelBuilder. Это мешает мне связывать методы сборки, как я должен await их.

Пример метода

public async Task<HomeViewModelBuilder> WithCarousel()
{   
    var carouselItems = await _service.GetAsync();
    _viewModel.Carousel = carouselItems;
    return this;
}

Теперь я должен использовать await для вызова методов построения.

await builder.WithCarousel();
await builder.WithFeaturedItems(3);

Кто-нибудь использовал методы async с шаблоном построителя? Если да, возможно ли иметь возможность связать методы или отложить метод await к методу сборки.

Ответ 1

Я не делал этого раньше, но здесь альтернатива решению Sriram.

Идея состоит в том, чтобы захватить задачи в объекте builder вместо результата задач. Затем метод Build ждет их завершения и возвращает построенный объект.

public sealed class HomeViewModelBuilder
{
  // Example async
  private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
  public HomeViewModelBuilder WithCarousel()
  {
    _carouselTask = _service.GetAsync();
    return this;
  }

  // Example sync
  private int _featuredItems;
  public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
  {
    _featuredItems = featuredItems;
    return this;
  }

  public async Task<HomeViewModel> BuildAsync()
  {
    return new HomeViewModel(await _carouselTask, _featuredItems);
  }
}

Использование:

var viewModel = await builder
    .WithCarousel(),
    .WithFeaturedItems(3),
    .BuildAsync();

Этот шаблон построителя работает с любыми числами асинхронных или синхронных методов, например:

public sealed class HomeViewModelBuilder
{
  private Task<Carousel> _carouselTask = Task.FromResult<Carousel>(null);
  public HomeViewModelBuilder WithCarousel()
  {
    _carouselTask = _service.GetAsync();
    return this;
  }

  private Task<int> _featuredItemsTask;
  public HomeViewModelBuilder WithFeaturedItems(int featuredItems)
  {
    _featuredItemsTask = _featuredService.GetAsync(featuredItems);
    return this;
  }

  public async Task<HomeViewModel> BuildAsync()
  {
    return new HomeViewModel(await _carouselTask, await _featuredItemsTask);
  }
}

Использование все равно.

Ответ 2

С шаблоном построителя вы можете создать стратегию построения объекта. Он не создает объект до тех пор, пока не будет вызван метод сборки. Если логика для заполнения объекта находится в методе сборки, вы можете соединить все методы async вместе.

См. пример кода для вашего строителя ниже. Это просто демонстрация концепции, поэтому вы можете улучшить ее.

    public class Builder
    {
        private bool hasCarousel = false;
        private int featuredItems = 0;

        public Builder WithCarousel()
        {
            hasCarousel = true;
            return this;
        }

        public Builder WithFeaturedItems(int featured)
        {
            featuredItems = featured;
            return this;
        }

        public BuiltObject Build()
        {
            if (hasCarousel)
            {
                // do carousel related calls
            }
            if (featuredItems > 0)
            {
                // do featured items related calls.
            }

            // build and return the actual object.
        }
    }

Ответ 3

Как я уже сказал в комментариях, вы можете написать метод расширения для HomeViewModelBuilder, а также Task<HomeViewModelBuilder> и связать его.

public static class HomeViewModelBuilderExtension
{
    public static Task<HomeViewModelBuilder> WithCarousel(this HomeViewModelBuilder antecedent)
    {
        return WithCarousel(Task.FromResult(antecedent));
    }

    public static async Task<HomeViewModelBuilder> WithCarousel(this Task<HomeViewModelBuilder> antecedent)
    {
        var builder = await antecedent;
        var carouselItems = await builder.Service.GetAsync();
        builder.ViewModel.Carousel = carouselItems;
        return builder;
    }

    public static Task<HomeViewModelBuilder> WithFeaturedItems(this HomeViewModelBuilder antecedent, int number)
    {
        return WithFeaturedItems(Task.FromResult(antecedent), number);
    }

    public static async Task<HomeViewModelBuilder> WithFeaturedItems(this Task<HomeViewModelBuilder> antecedent, int number)
    {
        var builder = await antecedent;
        builder.ViewModel.FeaturedItems = number;
        return builder;
    }
}

Мы добавляем несколько методов для одной операции, чтобы вы могли связать ее с помощью HomeViewModelBuilder или Task<HomeViewModelBuilder>. В противном случае вы не сможете вызвать builder.WithCarousel()

Затем используйте его как

private static void Main()
{
    HomeViewModelBuilder builder = new HomeViewModelBuilder();
    var task = builder
        .WithCarousel()
        .WithFeaturedItems(3);        
}

Ответ 4

Сделка с async заключается в том, что она имеет эффект пульсации. Он имеет тенденцию распространяться через ваш код, чтобы сохранить его async асинхронный путь полностью.

Если вы хотите разрешить шаблон построителя (или любой другой плавный рисунок, например LINQ), сохраняя при этом async, вам нужно иметь перегрузку async для каждого из возможных вызовов, которые вы хотите сделать. В противном случае вы не сможете их использовать или будете использовать их неправильно (например, "sync over async" ).

async-await является довольно новым, но я уверен, что с течением времени у вас будет опция async почти для всего.