Используемый инструментарий
Для примера, я буду использовать Ninject в качестве DI и Windows Form, в качестве UI. Понятное дело, что это совершенно не принципиально, так как тонкости различных DI систем я тут не рассматриваю, а принципиально они все решают одни и те же задачи.Постановка задачи
Предположим, нашей задачей является обслуживание транспортной компании. В нашем распоряжении будет база данных транспортных единиц, принадлежащих компании. И нашей целью будет написание кода по работе с этой базой данных (добавление новых моделей, новых экземпляров, получение различных сводных отчетов и т. д.). Естественно, заложить всю структуру базы данных одним махом мы не можем - будут доработки и переработки. Но начать стоит именно со структуры - это наш фундамент, наше все.Простейшая структура
Для примера, создадим структуру, состоящую всего из трех классов.// Тип транспорта public class TransportType { public int Id { get; set; } public string Caption { get; set; } } // Модель транспорта public class TransportModel { public int Id { get; set; } public string Caption { get; set; } public string Description { get; set; } } // Конкретная единица - транспортное средство public class Transport { public int Id { get; set; } public string Caption { get; set; } public TransportType Type { get; set; } public TransportModel Model { get; set; } }Естественно, я предполагаю, что каждый класс будет отмапирован на табличку в некотором репозитории/базе данных с использованием какого-нибудь ORM fraimwork-а. Однако, для данной статьи это не принципиально.
Пишем первый контроль
Следующим шагом я предлагаю начать писать первый редактор - UserControl, редактирующий и добавляющий новый экземпляр класса Transport. Возможно, для кого-то покажется странным такой резкий скачек от структуры данных к UI. Однако, в этом есть свой резон - наметив крайние точки программы, вам будет гораздо легче писать середину. Итак, заготовка нашего первого редактора будет выглядеть примерно так:
public partial class TransportEditor : UserControl { public TransportEditor() { InitializeComponent(); } // Код, обслуживающий кнопочки, поля ввода, валидацию и т. д. }Очевидно, в нашем редакторе должно быть поле ввода, для редактирования свойства Caption и пару элементов управления, позволяющие выбрать из списков нужную модель и тип транспортного средства. Не имеет смысла описывать тут способ размещения указанных элементов управления и варианты валидации вводимых данных - это дело вкуса. Зато, весьма интересно подумать над тем, как наш редактор будет взаимодействовать с данными.
Структура взаимодействия
Итак, мы видим, что для правильной работы редактора нам необходимо получить два списка объектов TransportType и TransportModel. Как именно эти объекты будут доставаться из базы данных, мы подумаем потом. На стадии проектирования UI нам это совершенно не интересно и отвлекаться на это не стоит. Вместо этого, мы просто объявим два интерфейса (такие интерфейсы я рекомендую размещать в отдельной сборке - так будет удобнее потом):
Остались вопросы? Пишите в комментарии. :)
public interface ITransportTypeManager { IEnumerable<TransportType> FullList { get; } } public interface ITransportModelManager { IEnumerable<TransportModel> FullList { get; } }Где будут находиться реализации этих интерфейсов и как они будут работать нас сейчас не интересует. Однако, без них наш контроль корректно работать не может. Поэтому, потребуем передать нам эти интерфейсы в конструкторе. И код нашего редактора преобразуется в:
public partial class TransportEditor : UserControl { ITransportTypeManager typeManager; ITransportModelManager modelManager; public TransportEditor( ITransportTypeManager typeManager, ITransportModelManager modelManager ) { InitializeComponent(); this.typeManager = typeManager; this.modelManager = modelManager; } // Код, обслуживающий кнопочки, поля ввода, валидацию и т. д. }После чего, мы спокойно начинаем использовать полученные интерфейсы, запрашивая списки и формируя их визуальное представление. После того, как пользователь введет все необходимые данные, нам нужно будет создать по ним новый объект транспортного средства. Как это сделать, мы пока не знаем - ведь мы еще даже не определились окончательно с движком ORM (может, попробовать EntityFramework или написать на чистом SQL? Еще есть неплохое решение от Telerik.. Или старый-добрый Genome?). Однако, это ни сколько не мешает нам завершить работу над контролем. Просто добавим еще один интерфейс:
public interface ITransportManager { Transport Create( string caption, TransportType type, TransportModel model ); }Конечно же, его тоже надо потребовать в конструкторе:
public partial class TransportEditor : UserControl { ITransportTypeManager typeManager; ITransportModelManager modelManager; ITransportManager transportManager; public TransportEditor( ITransportTypeManager typeManager, ITransportModelManager modelManager, ITransportManager transportManager ) { InitializeComponent(); this.typeManager = typeManager; this.modelManager = modelManager; this.transportManager = transportManager; } // Код, обслуживающий кнопочки, поля ввода, валидацию и т. д. }Обращаясь к transportManager, завершим создание нового транспортного средства и отложим контроль в сторону.
Ninject
Теперь пришло время задуматься, как контроль будет создаваться. Предположим, в нашем главном окне есть специальная кнопочка, по клику на которую мы предполагали сделать примерно следующее:public void OnCreateNewClick() { var editor = new TransportEditor(); editor.Dock = DockStyle.Fill; Controls.Add( editor ); }Однако, сейчас такой код работать не будет - ведь TransportEditor больше не имеет конструктора без параметров. Вот тут-то на сцене и появляется Ninject. Там же, в главном окне, заведем переменную ядра Ninject и проинициализируем ее:
IKernel kernel; void InitInjections() { kernel = new StandardKernel(); }После чего, слегка модифицируем код по созданию редактора:
public void OnCreateNewClick() { var editor = kernel.Get<TransportEditor>(); editor.Dock = DockStyle.Fill; Controls.Add( editor ); }Такой код уже скомпилируется и даже запустится. И, лишь в момент создания редактора, Ninject сообщит нам, что реализации интерфейсов отсутствуют. Однако, это все еще не повод писать полноценные реализации. Что бы отладить уже написанный код, нам достаточно написать реализации-заглушки.
Ninject модули
Конечно, мы понимаем, что заглушки - это решение временное и, конечно же, нам хочется затратить минимум усилий, что бы перейти на полноценные реализации. Я, к примеру, не стал бы писать никаких заглушек, если бы это потребовало от меня более 10-ти минут моего драгоценного времени. К счастью в Ninject есть очень удобный механизм модулей. Итак, сами заглушки будут выглядеть так:class DummyTransportTypeManager : ITransportTypeManager { public IEnumerable<TransportType> FullList { get { return Enumerable.Empty<TransportType>(); } } } class DummyTransportModelManager : ITransportModelManager { public IEnumerable<TransportModel> FullList { get { return Enumerable.Empty<TransportModel>(); } } } class DummyTransportManager : ITransportManager { public Transport Create( string caption, TransportType type, TransportModel model ) { return null; } }Далее пишем наш первый модуль, в котором эти заглушки будут подключаться:
class TestNinjectModule : NinjectModule { public override void Load() { Bind<ITransportTypeManager>().To<DummyTransportTypeManager>(); Bind<ITransportModelManager>().To<DummyTransportModelManager>(); Bind<ITransportManager>().To<DummyTransportManager>(); } }Как мы видим, модуль - это класс наследник NinjectModule и реализующий абстрактный метод Load. Итак, нам осталось сделать всего один шаг - подключить написанный модуль к нашей программе. Для этого, слегка подкорректируем код инициализации Ninject:
void InitInjections() { kernel = new StandardKernel( new TestNinjectModule() ); }Поле этого наша программа становится целиком и полностью рабочей. Аналогично, мы можем добавить и другие элементы управления, полностью отладив весь UI. И лишь после этого преступить к написанию настоящих реализаций. Конечно, я не утверждаю, что этот последний этап будет совсем простым. Однако, к этому моменту наша система будет полностью спроектирована. У нас уже не будут возникать философские вопросы - мы будем точно знать, что надо делать.
Остались вопросы? Пишите в комментарии. :)
Макс спасибо очень полезная статья !
ОтветитьУдалитьО! Таки коменты работают все-таки. :) Ура!! :)
Удалить