Правильный способ внедрения зависимостей в клиентском приложении Windows (WPF) - PullRequest
8 голосов
/ 09 июля 2011

Я привык к IoC / DI в веб-приложениях - в основном Ninject с MVC3.Мой контроллер создан для меня, заполнен всеми имеющимися зависимостями, зависимостями и т. Д.

Однако в толстом клиентском приложении все по-другому.Я должен создать свои собственные объекты, или я должен вернуться к подходу стиля локатора службы, где я прошу ядро ​​(возможно, через некоторый интерфейс, чтобы позволить тестируемость) дать мне объект, полный зависимостей.

Тем не менее, я видел несколько мест, где Service Locator был описан как анти-шаблон.

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

  • Тестируемость
  • Правильный DI / IoC
  • Наименьшее возможное количество соединений

Обратите внимание, что я говорю не только о MVVM здесь и получении моделей представлений в представлениях.Это особенно вызвано необходимостью предоставить объект типа репозитория из ядра, а затем получить объекты, извлеченные из этого репозитория, с функциональностью (данные, конечно, поступают из базы данных, но им также нужны некоторые объекты в качестве параметров в зависимости от состояниямира, и Ninject знает, как это обеспечить).Могу ли я как-то сделать это, не оставляя и репозитории, и сущности в качестве непроверяемых беспорядков?

Если что-то неясно, дайте мне знать.Спасибо!

РЕДАКТИРОВАТЬ 14 ИЮЛЯ

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

Я недостаточно хорошо объяснил это в первоначальном вопросе, но дело в том, что я пишу библиотеку, которая будет использоваться несколькими (сначала 4-5, может быть, позже) клиентскими приложениями WPF.Все эти приложения работают в одной и той же доменной модели и т. Д., Поэтому хранение всего этого в одной библиотеке - единственный способ остаться СУХИМЫМ.Однако есть также шанс, что клиенты этой системы напишут своих собственных клиентов - и я хочу, чтобы у них была простая, чистая библиотека для общения.Я не хочу заставлять их использовать DI в своем корне композиции (используя такой термин, как Марк Симан в своей книге) - потому что это УЖЕ усложняет вещи по сравнению с ними, просто обновляя MyCrazySystemAdapter () и используя его.

Теперь MyCrazySystemAdapter (имя выбрано потому, что я знаю, что люди со мной здесь не согласятся) должен быть составлен подкомпонентами и соединен с использованием DI.Сам MyCrazySystemAdapter не нужно вводить.Это единственный интерфейс, который клиенты должны использовать для общения с системой.Таким образом, клиент, к счастью, должен получить один из них, DI происходит как закулисная магия, и объект состоит из множества различных объектов, используя лучшие практики и принципы.

Я понимаю, что это будет спорнымспособ хотеть делать вещи.Тем не менее, я также знаю людей, которые собираются стать клиентами этого API.Если они увидят, что им нужно изучить и подключить систему DI, и заранее создать всю структуру своих объектов в точке входа своего приложения (Composition Root), вместо того, чтобы обновлять отдельный объект, они дадут мне средний палец ипоэкспериментируйте с базой данных напрямую и облажайтесь так, как вы вряд ли можете себе представить.

TL; DR: Предоставление правильно структурированного API-интерфейса - слишком сложная задача для клиента.Мой API должен доставлять один объект - созданный за сценой с использованием DI и надлежащих методов - который они могут использовать.Реальный мир иногда превосходит желание строить все задом наперед, чтобы оставаться верным шаблонам и практикам.

Ответы [ 2 ]

5 голосов
/ 11 июля 2011

Предлагаю взглянуть на фреймворки MVVM, такие как Caliburn.Они обеспечивают интеграцию с контейнерами IoC.


По сути, вы должны создать полное приложение в своем файле app.xaml.Если некоторые детали необходимо создать позже, потому что вы еще не знаете всего, чтобы создать их при запуске, введите фабрику в виде интерфейса (см. Ниже) или Func (см. Поддерживает ли Ninject Func (автоматически сгенерированная фабрика)?*) в класс, который должен создать этот экземпляр.Оба будут изначально поддерживаться в следующей версии Ninject.

например,

public interface IFooFactory { IFoo CreateFoo(); }
public class FooFactory : IFooFactory
{
    private IKernel kernel;
    FooFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public IFoo CreateFoo()
    {
        this.kernel.Get<IFoo>();
    }
}

Обратите внимание, что реализация фабрики логически относится к конфигурации контейнера, а не к реализации ваших бизнес-классов.

1 голос
/ 09 июля 2011

Я ничего не знаю о WPF или MVVM, но ваш вопрос в основном о том, как вытащить вещи из контейнера, не используя Service Locator (или контейнер напрямую) повсюду, верно?
Еслида, я могу показать вам пример.

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

Примечание: я буду использовать пример с WinForms, а не привязанный к конкретному контейнеру (потому что, как я уже сказал, я не знаю WPF ... и я используюCastle Windsor вместо NInject), но поскольку ваш основной вопрос не связан конкретно с WPF / NInject, вам будет легко "перенести" мой ответ на WFP / NInject.

Фабрика выглядитнапример:

public class Factory : IFactory
{
    private readonly IContainer container;

    public Factory(IContainer container)
    {
        this.container = container;
    }

    public T GetStuff<T>()
    {
        return (T)container.Resolve<T>();
    }
}

Основная форма вашего приложения получает эту фабрику с помощью инжектора конструктора:

public partial class MainForm : Form
{
    private readonly IFactory factory;

    public MainForm(IFactory factory)
    {
        this.factory = factory;
        InitializeComponent();  // or whatever needs to be done in a WPF form
    }
}

Контейнер инициализируется при запуске приложения и разрешается основная форма (поэтому он получает фабрику через инжектор конструктора).

static class Program
{
    static void Main()
    {
        var container = new Container();
        container.Register<MainForm>();
        container.Register<IFactory, Factory>();
        container.Register<IYourRepository, YourRepository>();

        Application.Run(container.Resolve<MainForm>());
    }
}

Теперь основная форма может использовать фабрику для извлечения таких вещей, как ваше хранилище, из контейнера:

var repo = this.factory.GetStuff<IYourRepository>();
repo.DoStuff();

Если у вас есть больше форм и вы хотите использовать фабрику оттудаКроме того, вам просто нужно ввести фабрику в эти формы, как в основную форму, зарегистрировать дополнительные формы при запуске, а также открыть их из основной формы с фабрикой.

Это то, что вы хотелизнаете?


РЕДАКТИРОВАТЬ:
Рубен, конечно, вы правы.Моя ошибка.
Все, что было в моем ответе, было старым примером того, что я где-то лежал, но спешил, когда опубликовал свой ответ, и недостаточно внимательно прочитал контекст моего старого примера.

Мой старый пример включал в себя основную форму, из которой вы можете открыть любую другую форму приложения. Это для чего была фабрика, поэтому вам не нужно вводить каждую другую форму с помощью инжектора конструктора в основную форму.
Вместо этого вы можете использовать фабрику, чтобы открытьлюбая новая форма:

var form = this.factory.GetStuff<IAnotherForm>();
form.Show();

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

public partial class MainForm : Form
{
    private readonly IAnotherForm form;

    // pass AnotherForm via constructor injection
    public MainForm(IAnotherForm form)
    {
        this.form = form;
        InitializeComponent();  // or whatever needs to be done in a WPF form
    }

    // open AnotherForm
    private void Button1_Click(object sender, EventArgs e)
    {
        this.form.Show();
    }
}

public partial class AnotherForm : Form
{
    private readonly IRepository repo;

    // pass the repository via constructor injection
    public AnotherForm(IRepository repo)
    {
        this.repo= repo;
        InitializeComponent();  // or whatever needs to be done in a WPF form

        // use the repository
        this.repo.DoStuff();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...