Как совместить слабосвязанный и расширяемый дизайн с возможной асинхронной реализацией?

Я программист, работающий над проектом, использующим .Net 4.0, и пытаюсь найти лучший способ совместить слабосвязанный и расширяемый дизайн с тем фактом, что одна и та же логическая операция может иногда выполняться асинхронно, а иногда только синхронно (каждая реализация, которая поддерживает асинхронность, также поддерживает синхронизацию, но не наоборот). В этом проекте каждая такая логическая операция представлена ​​интерфейсом (требования к слабой связи и расширяемости высоки). Давайте сосредоточим мой вопрос на конкретном интерфейсе: IDataDictLoader. Этот интерфейс отвечает за загрузку объекта с именем DataDict. Существует две возможные реализации этого интерфейса: одна называется LocalDataDictLoader, которая использует локальные библиотеки DLL и выполняет операцию синхронно (возвращая DataDict), а другая называется WebServiceDataDictLoader, которая использует веб-службу и выполняет операцию . асинхронно (возвращая Task<DataDict>) или синхронно. Значение в файле конфигурации определяет реализацию, которую нужно создать: если значение «Local», то создается LocalDataDictLoader, если значение «WebService», то создается WebServiceDataDictLoader. Часть создания использует отражение, руководствуясь соглашениями, в функции IDataDictLoader CreateLoader(string configValue). Код, вызывающий эту функцию и использующий интерфейс, не знает заранее, какой будет реализация, у него нет доступа к значению конфигурации, даже если он хотел знать. Вопрос в том, как спроектировать интерфейс? На данный момент я рассматривал несколько вариантов:

  1. Имейте два метода в интерфейсе, один синхронный и один асинхронный, и используйте асинхронную оболочку в LocalDataDictLoader для версии синхронизации. Как предложил Стивен Туб здесь, это не рекомендуемое решение.
  2. Сделайте так, как предложено Джошем в ответе на вопрос >здесь, в его примере IIMViewModelDL, так что интерфейс будет иметь метод void с параметром обратного вызова. Хотя это решение будет скрывать детали реализации от клиентского кода, мне кажется, что оно имеет семантику асинхронного вызова с использованием обратного вызова. На мой взгляд, это эквивалентно постоянному возврату Task<DataDict> и синхронной реализации, выполняющей синхронную загрузку, а затем повторной настройке завершенной задачи с результатом с использованием TaskCompletionSource<DataDict>. Это неправильно, как вариант 1, по тем же причинам.
  3. Разделите интерфейс на два интерфейса: IDataDictLoader для синхронного, IAsyncDataDictLoader : IDataDictLoader для асинхронного и синхронного. Это решение предоставляет клиентскому коду информацию о том, может ли операция выполняться асинхронно или нет, оно не «лжет» о ее природе. Недостатком является то, что теперь клиентский код, который называется CreateLoader(string configValue), теперь должен будет использовать стиль условного кодирования as/is, чтобы знать, что он получил IDataDictLoader, который на самом деле является IAsyncDataDictLoader или нет.

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


person Eldar    schedule 30.05.2013    source источник


Ответы (1)


Я думаю, что вы должны сделать свой интерфейс асинхронным. Если реализация является синхронной, она может вернуть уже завершенный Task. Таким образом, ваш потребляющий код не должен знать о реализации и может обрабатывать ее так, как если бы она была асинхронной.

person svick    schedule 30.05.2013
comment
Я упомянул это решение во втором варианте. Реализация, которая возвращает Task и при этом работает синхронно, очень запутана и несколько небезопасна. Код, написанный в пользовательском интерфейсе, может вызывать этот метод в потоке пользовательского интерфейса, предполагая, что, поскольку он возвращает задачу, она является асинхронной и, следовательно, будет возвращаться быстро, в то время как на самом деле возврат может занять несколько минут. Я предпочитаю запускать код синхронизации в задаче и возвращать его, а не запускать его синхронно и возвращать завершенную задачу, если вообще. - person Eldar; 31.05.2013
comment
@Eldar Если метод занимает много времени, это еще один вариант, да. - person svick; 31.05.2013
comment
Могу я спросить, что вы думаете о возможности разделения интерфейсов на синхронный и расширяющий его асинхронный? - person Eldar; 01.06.2013