Используемый инструментарий
Для примера, я буду использовать 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. И лишь после этого преступить к написанию настоящих реализаций. Конечно, я не утверждаю, что этот последний этап будет совсем простым. Однако, к этому моменту наша система будет полностью спроектирована. У нас уже не будут возникать философские вопросы - мы будем точно знать, что надо делать.Остались вопросы? Пишите в комментарии. :)
Макс спасибо очень полезная статья !
ОтветитьУдалитьО! Таки коменты работают все-таки. :) Ура!! :)
Удалить