Построение консольных приложений

Консольное приложение – это самый простой для композиции вид приложения. В противоположность большинству других фреймворков BCL приложений, консольное приложение, в сущности, не содержит инверсию управления. Когда процесс выполнения доходит до точки входа в приложение (обычно это метод Main), мы берем все в свои руки. Не существует никаких особенных событий, которые нужно было бы описать, интерфейсов, которые нужно было бы реализовать, и мы можем использовать очень ограниченное количество сервисов.

Метод Main соответствует Composition Root. Первое, что нам нужно сделать в методе Main, – скомпоновать модули приложения и позволить им соединиться в единое целое. Это не сложно, но давайте рассмотрим пример.

Пример: актуализация валют

В главе 4 "DI паттерны" мы рассматривали то, как обеспечить возможность конвертации валют в шаблонном приложении Commerce. В разделе "Пример: конвертация валюты в корзине" был введен класс Currency, который предоставляет курс обмена одной валюты на другие. Поскольку Currency является абстрактным классом, мы могли бы создать множество различных реализаций, но в данном примере мы использовали базу данных. Целью примера кода из главы "DI паттерны" было продемонстрировать то, как восстановить и реализовать конвертацию валюты, таким образом, мы никогда не рассматривали то, как актуализировать обменный курс в базе данных.

Для того чтобы продолжить пример, давайте изучим то, как написать простое консольное приложение, которое позволяет администратору или привилегированному пользователю обновлять обменные курсы в базе данных без необходимости прямого взаимодействия с базой данных.

Программа UpdateCurrency

Поскольку целью этой программы является актуализация обменных курсов в базе данных, она имеет название UpdateCurrency.exe. Она будет принимать в качестве входных данных три аргумента командной строки:

  • Код целевой валюты
  • Код исходной валюты
  • Обменный курс

Может показаться странным, что мы указываем цель до исходных данных, но такой способ является наиболее подходящим для большинства людей. Это говорит вам о том, сколько исходной валюты вам понадобится для того, чтобы купить одну единицу целевой валюты; например, обменный курс доллара к евро выражается как 1 евро за 1,44 доллара.

В командной строке это выглядит примерно так:

PS Ploeh:\> .\UpdateCurrency.exe EUR USD "1,44"

Updated: 1 EUR in USD = 1,44.

В результате выполнения программы происходит обновление базы данных и запись обновленных значений обратно в консоль.

Composition Root

UpdateCurrency использует точку входа по умолчанию для консольного приложения: метод Main в классе Program. Это Composition Root для приложения, что продемонстрировано в следующем листинге.

Листинг 7-1: Composition Root консольного приложения
public static void Main(string[] args)
{
	var container = new CurrencyContainer();
	container.ResolveCurrencyParser()
		.Parse(args)
		.Execute();
}

Единственной ответственностью метода Main является компоновка всех соответствующих модулей, и затем он передает ответственность за функциональность сформированной диаграмме объектов. В данном примере пользовательский контейнер инкапсулирует то, как компонуются модули. Поскольку он выполняет точно такую же функцию, как и DI-контейнер, я решил назвать его контейнером, несмотря на то, что он является пользовательским контейнером с жестко подключенными зависимостями. Мы вернемся к нему для того, чтобы вкратце рассмотреть, как он реализован.

После размещения контейнера вы можете теперь попросить его разрешить CurrencyParser, который анализирует входные аргументы и, в итоге, выполняет соотвествующую команду.

Подсказка

Composition Root должна делать всего две вещи: настраивать контейнер и разрешать тип, который реализует требуемую функциональность. Как только он это выполнил, он должен сойти с дистанции и оставить остальную работу разрешенному экземпляру.

Подсказка

Используйте для ваших рабочих приложений настоящий DI-контейнер вместо доморощенного пользовательского контейнера.

В этом примере используется пользовательский контейнер, явно созданный для этого приложения, но необходимо прямо заменить его таким настоящим DI-контейнером, как те, что рассматривались в главе "DI паттерны".

Контейнер

Класс CurrencyContainer – пользовательский контейнер, созданный специально для целей подключения всех зависимостей к программе UpdateCurrency. Следующий листинг демонстрирует реализацию.

Листинг 7-2: Пользовательский CurrencyContainer
public class CurrencyContainer
{
	public CurrencyParser ResolveCurrencyParser()
	{
		string connectionString =
			ConfigurationManager.ConnectionStrings
			["CommerceObjectContext"].ConnectionString;
		CurrencyProvider provider =
			new SqlCurrencyProvider(connectionString);
		return new CurrencyParser(provider);
	}
}

Строка 5-7: Получает строку соединения из config

В этом примере диаграмма зависимостей довольно поверхностная. Для класса CurrencyParser необходим экземпляр абстрактного класса CurrencyProvider, а в CurrencyContainer вы решаете, что реализацией должен быть SqlCurrencyProvider, который предоставляет необходимое взаимодействие с базой данных.

Класс CurrencyParser использует механизм внедрения через конструктор, поэтому вы передаете в него только что созданный экземпляр SqlCurrencyProvider до того, как вернуть его из метода.

Если вдруг вам станет интересно, то ниже я привожу сигнатуру конструктора CurrencyParser:

public CurrencyParser(CurrencyProvider currencyProvider)

Помните о том, что CurrencyProvider – это абстрактный класс, который реализуется SqlCurrencyProvider. Несмотря на то, что CurrencyContainer содержит жестко закодированное преобразование CurrencyProvider в SqlCurrencyProvider, остальная часть кода является слабо связанной, поскольку в ней применяется только абстракция.

Этот пример может показаться простым, но в нем компонуются типы из трех различных уровней приложения. Давайте вкратце исследуем то, как эти уровни взаимодействуют в данном конкретном примере.

Иерархическое представление

Composition Root – это место, в котором компоненты всех уровней соединяются вместе. Точка входа и Composition Root образуют единственный исполняемый код. Вся реализация делегирована более низшим уровням, как это иллюстрирует рисунок 7-4.

Рисунок 7-4: Композиция компонентов приложения UpdateCurrency. CurrencyParser анализирует аргументы командной строки и возвращает соответствующий ICommand. Если аргументы были понятны, то он возвращает CurrencyUpdateCommand, который использует экземпляр Currency для актуализации обменного курса. Вертикальная линия справа показывает соответствующий уровень приложения. Каждый уровень реализуется в отдельной сборке.

Диаграмма на рисунке 7-4 может казаться сложной, но она представляет почти всю базу кода приложения. Большая часть логики приложения состоит из анализа входных аргументов и выбора корректной команды на основании этих входных данных. Все это имеет место на уровне Application Services (сервисы приложения), который взаимодействует напрямую с уровнем доменной модели посредством абстрактных классов CurrencyProvider и Currency.

CurrencyProvider внедряется в CurrencyParser с помощью контейнера и впоследствии используется в качестве абстрактной фабрики для создания экземпляра Currency, который используется CurrencyUpdateCommand.

Уровень доступа к данным поставляет SQL Server реализации доменных классов. Несмотря на то, что никакие другие классы приложения не обращаются напрямую к этим классам, CurrencyContainer преобразует абстракции в конкретные классы.

Использовать механизм внедрения зависимостей в консольном приложении легко, поскольку в действительности в нем нет внешней инверсии зависимостей. .NET Framework просто ускоряет процесс и передает управление методу Main.

В большинстве других BCL фреймворков присутствует более высокая степень инверсии управления, которая подразумевает, что нам нужно уметь определять корректные места расширяемости для того, чтобы подключить требуемую диаграмму объектов. Одним из таких фреймворков является ASP.NET MVC.

или RSS канал: Что новенького на smarly.net