Страницы

Поиск по вопросам

Показаны сообщения с ярлыком inversion-of-control. Показать все сообщения
Показаны сообщения с ярлыком inversion-of-control. Показать все сообщения

воскресенье, 8 марта 2020 г.

Ninject не может создать обьект

#c_sharp #net #aspnet_mvc #inversion_of_control #ninject


При попытке создать обьект через контейнер Ninject вылетает exception : Object reference
not set to an instance of an object. Подскажите, пожалуйста, в чем может быть проблема?

Метод с класса NinjectDependencyResolver

private void AddBindings()
        {
            var mapperConfiguration = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile(new TagProfile());
            });
            var mapper = mapperConfiguration.CreateMapper();
            _kernel.Bind().ToSelf().InRequestScope();
            _kernel.Bind, Repository>();
            _kernel.Bind().ToConstant(mapper);

            var repository = _kernel.Get>();// вылетает exception
            _kernel.Bind();
        }


Класс Repository

public class Repository : IRepository where T : class
{
    private readonly BlogDbContext _db;

    public Repository(BlogDbContext db)
    {
        _db = db;
    }
}


P.S. Не знаю важно ли это, но репозиторий с его интерфейсом находится в одной сборке,
а регистрация Ninject в другой.
    


Ответы

Ответ 1



Ошибка говорит о том, что в экземпляре объекта не задана ссылка на объект. Object reference not set to an instance of an object. В этих двух строчках _kernel.Bind, Repository>(); _kernel.Bind(); Вы "говорите" IoC-контейнеру, что при запросе IRepository или Repository, необходимо вернуть реализацию, но не указываете ее. Правильным будет следующий вызов. _kernel.Bind>().To>(); _kernel.Bind().To(); Вы сообщаете IoC-контейнеру, что при запросе IRepository создать экземпляр класса Repository. Для второй строки аналогично. И ответ на вопрос из комментария: для контекста делаю InRequestScope(), нужно ли мне это делать и для репозитория и для сервиса? Для репозитория и Сервиса не нужно делать InRequestScope(), только для контекста BlogDbContext UPD Не знаю важно ли это, но репозиторий с его интерфейсом находится в одной сборке, а регистрация Ninject в другой. Не важно, главное указать ссылка на сборку в которой лежит репозиторий и интерфейсы

понедельник, 6 января 2020 г.

В чем отличие service locator от ioc-container?

#java #service #inversion_of_control


Хотелось бы примеры реализации на java,  примеров реализации service locators полно,
а вот ioc-containers не нашёл. Все ведет на готове реализации ioc, как правило Spring.
    


Ответы

Ответ 1



ServiceLocator и Dependency Injection можно рассматривать как две формы паттерна IoC. Только в первом случае зависимости запрашиваются у локатора(получается некоторый global state, что не очень хорошо), а во втором контейнер сам устанавливает зависимости классу. Есть старенькая, но очень хорошая статья от Фаулера - см. тут (ее вольный перевод есть тут и тут). Еще советую почитать статью от skipy Кроме Spring-а есть и другие реализации IoC - контейнеров: guice, weld-cdi,dagger, pico container, silkdi

понедельник, 30 декабря 2019 г.

Роль interface в Java

#java #dependencies #inversion_of_control


Здравствуйте, давно читаю разные туториалы и много где встречаю должно быть мало
зависимостей, как я понял это все достигается при помощи interface и IoC. Нашел много
примеров но так сути и не понял как правильно это достигать... К примеру у меня есть
проект с моделями(таблицы из БД) и сервисы(классы для выполнения каких=либо действий
над ними), и что получается мне для каждого сервиса писать интерфейс его действий?
Я понимаю что если добавится другой класс мы просто имплементим его и ничего менять
не нужно практически. Но с другой стороны много лишнего кода и классов. Может быть
мне кто-нибудь сможет подробно объяснить как достигнуть минимизации зависимостей на
примерах кода Java или просто на словах, чтобы я уловил смысл?
    


Ответы

Ответ 1



Для чего вообще нужно минимизировать зависимости? Фредерик Брукс, автор статьи «Серебряной пули нет» утверждал, что производство программного обеспечения дело трудное, и в ближайшее время не появится ничего, что сделает его проще. Это было в середине 80-х. Через 10 лет Брукс изучил, изменилось ли что-нибудь кардинально в индустрии. Оказалось, что нет. Правда, один из подходов выглядел многообещающе — повторное использование кода. Речь о том, что, написав большую систему, и приступая к написанию другой, мы могли бы взять готовые куски кода и использовать их повторно. Фактически, мы могли бы сократить работу минимум на 30%. Этому мешает то, что части системы сильно сцеплены (couple) друг с другом. Для борьбы со сцепленностью придуманы без малого десятки техник. Часть из них дублируют друг друга на разных уровнях детализации. Вначале программы были не очень большими, и программисты делали независимыми отдельные функции. Скажем, предпочитали чистые функции, значение которых зависело только от аргументов, следовательно, их можно было безболезненно перенести в другой проект. Предпочитали модули с высокой связностью, но малой сцепленностью. В современных ОО языках предпочитают классы с единственной ответственностью. Когда программы стали слишком большими, модули объединили в слои (уровни) и задали чёткое правило направления зависимостей: нижние уровни не могут зависеть от верхних. Никогда. В классическом трёхзвенном приложении уровень представления зависит от уровня предметной области, а тот, в свою очередь, от уровня доступа к данным. Presentation → Domain → Data Access Для примера возьмём интернет-магазин. Одной из сущностей магазина является Заказ (Order). Этот класс принадлежит к уровню предметной области, потому что вся работа интернет-магазина строится вокруг Заказов. Если вы используете паттерн MVC в построении веб-приложения, то заведовать заказами будет Контроллер Заказов (OrderController). Этот класс принадлежит к уровню представления. Значит, класс OrderController может знать и использовать Order, а Order ничего про OrderController знать не может. В этом есть глубокий смысл. Предположим, у вас работает не только веб-приложение, но фоновые сервисы, которые, по сути являются консольными приложениями. Они запускаются с заданным интервалом и что-то делают с заказами. Очень правильно в этом случае повторно использовать весь код, реализующий Заказы и их сохранение в БД. Но чтобы это сделать, надо чётко понимать, что является представлением, а что нет. OrderController реагирует за запросы HTTP, и у него есть такие штуки, как URI запроса, текущий пользователь и прочее. Но ничего этого нет у самого Заказа. Эта ошибка встречается часто: в классах предметной области хранятся дескрипторы окна, или данные, специфичные для веб-приложений. На самом деле класс Order не может строить никаких предположений о том, в какой среде его будут использовать. Вопрос, трудно ли будет создать фоновые сервисы или оконные приложения, если приложение спроектировано правильно? Не очень трудно. Нам, конечно, придётся написать новый слой, но нам точно не нужно будет переписывать классы нижних уровней, в частности Order. Теперь опускаемся на уровень ниже, к данным. Предположим, все данные лежат в MySQL и мы для доступа к ним используем JDBC. SQL-запрос для постраничного списка заказов мы пишем сами, он в виде строковой константы находится в Java-коде: SELECT Orders.* FROM Orders ORDER BY Orders.CreatedAt LIMIT ? OFFSET ? На уровне доступа к данным у нас находятся классы Connection, ResultSet, Statement из пространства имён java.sql. Классы с верхних уровней могут к ним обращаться, то есть Order мог бы уметь создавать себя из ResultSet, а OrderController мог бы выполнять запросы с помощью Statement. Снова всё хорошо. Вопрос, трудно ли будет изменить способ хранения с MySQL на Oracle, или даже на что-нибудь вроде MongoDB? На этот раз гораздо труднее, чем раньше. Нам придётся вносить изменения не только в классы нижнего уровня, но и в Order, и в OrderController. В Oracle постраничный доступ требует другого синтаксиса, а для загрузки данных из MongoDB уже нельзя использовать ResultSet. Решение заключается в том, чтобы в данном месте инвертировать зависимость. Мы говорим, что не знаем заранее, как будем обращаться к данным. Вместо конкретных классов ResultSet и MongoCollection мы скажем: у нас точно будет какое-то внешнее хранилище из которого мы захотим постранично получать данные. Наверное, нам придётся узнавать и общее количество страниц. public interface OrderRepository { List ReadAll(int oneBasedPageNumber); int ReadTotalPages(); } Интерфейс Хранилище Заказов (OrderRepository) находится на уровне предметной области, и OrderController может использовать его. Реализация интерфейса для MySQL находится на уровне доступа к данным, но теперь зависимость инвертирована: доступ к данным зависит от предметной области. public MysqlOrderRepository implements OrderRepository { @Override public List ReadAll(int oneBasedPageNumber) { . . . } } Реализация MysqlOrderRepository видит Order и может создавать объекты Заказа. Зависимости теперь выглядят так: Presentation → Domain ← Data Access Из всего изложенного становятся понятно, что использовать интерфейсы для доступа к объектам предметной области не нужно, если этого не требуют специальные условия. Скажем так, Заказ в интернет-магазине — одна из базовых сущностей, и вряд ли вам потребуют две конкурирующие реализации Заказов. Точно также и сервисы предметной области интерфейсов не требуют. Бизнес-процесс оформления заказа фиксирован, поэтому его сразу можно реализовать в виде конкретного класса Сервис Оформления Заказов (OrderService). Использовать интерфейсы для объектов уровня представления также не нужно, потому что они находятся на самом верху и от них ничего не зависит. Интерфейсами имеет смысл закрывать только объекты и сервисы уровней ниже предметной области. Мы обозначили их как уровень Data Access, а Эрик Эванс, создатель DDD, предлагает называть их инфраструктурными. Соответственно, у нас может быть сервис рассылки электронных сообщений NotificationService. Это интерфейс уровня предметной области, чья реализация TwilioNotificationService находится на инфраструктурном уровне. При такой организации проекта мы получаем возможность быстро клонировать проект и дописать новый кусок. Хотим сделать консольное приложение? Берём готовые уровни предметной области и инфраструктуры, дописываем обвязку консольного приложения. Хотим перенести на СУБД Postgres? Берём готовые уровни предметной области и представления, дописываем реализацию хранилищ на PostreSQL. Зависимости остаются, но они упорядочены и позволяют изымать и подменять целые уровни приложения. Есть несколько случаев, когда интерфейсы могут появиться на высоких уровнях. Скажем, если в интернет-магазине могут быть разные способы начисления скидок, их удобно реализовать в виде паттерна Стратегия. По сути это будет иерархия однотипных классов, каждый из которых рассчитывает скидку на основании своих данных. Общие методы этих классов можно вынести в базовый интерфейс. В этом случае необходимость в интерфейсе диктуется уже не зависимостями, а способом реализации конкретного паттерна. Резюмирую: мы в действительности можем говорить о минимизации зависимостей, как об управлениями зависимостями. Мы не можем от них избавиться совсем, но мы можем их ограничивать. Для этого мы разбиваем приложение на слои, и вводим правило: все зависимости идут в одну сторону. Использовать можно только свои классы, либо классы слоя, от которого мы зависим. Ядром приложения, его солью является слой предметной области. Все слои, которые выше него, оставляем как есть, они и так от него зависят. Для слоёв, которые ниже него, инвертируем зависимости. На практике это означает, что мы вводим интерфейсы в слой предметной области, которые реализуем в нижних слоях.

Ответ 2



В Java интерфейсы имеет смысл рассматривать в совокупности с абстрактными классами. Они похожи, но имеют существенные отличия. Что-то мне подсказывает, что вопрос связан не именно с интерфейсами, а с обобщением кода в принципе. Интерфейсы не могут минимизировать код, а могут только помочь в его структурировании, тогда как абстрактные классы могут взять на себя часть общих методов, присущих их дочерним элементам, таким образом сократив общее количество кода. Интерфейс служит для объединения схожих элементов - для того, чтобы задать им обязательство имплементировать определенные методы. Например для ваших сервисов это могут быть методы CRUD. Маркерный интерфейс не имеет методов вообще. Наличие такого интерфейса у класса, и соответствующее его поведение, определяется из кода. Абстрактный класс выгодно отличается от интерфейса тем, что общие для всех дочерних элементов методы можно один раз описать в нем, а из дочерних элементов просто вызывать. Пример: Сайт написан на чистых сервлетах. Есть абстрактный класс-сервлет, от которого наследуются все остальные страницы-сервлеты сайта. В абстрактном классе есть два метода: Реализованный doResponse - общий для всех страниц сайта. Абстрактный preparePage - должен быть реализован индивидуально для каждой страницы. Index.java @WebServlet(name = "index", urlPatterns = {"/index.html"}) public class Index extends AbstractServlet { protected static char[] page; @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doResponse(request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doResponse(request, response); } @Override protected void preparePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { StringBuilder stringBuilder = new StringBuilder(); ... creating page from template ... setPage(stringBuilder.toString().toCharArray()); } } AbstractServlet.java public abstract class AbstractServlet extends HttpServlet { protected char[] getPage() throws ServletException, IOException { return (char[]) this.getClass().getField("page").get(this.getClass()); } protected void setPage(char[] page) { this.getClass().getField("page").set(this.getClass(), page); } protected void doResponse(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter pw = response.getWriter(); if (this.getPage() == null) { this.preparePage(request, response); } pw.write(this.getPage()); } protected abstract void preparePage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; }

Ответ 3



Есть Очень классное видео ТУТ где объясняют лямбда выражения используя интерфейсы. Смотреть на скорости х2. После просмотра ты очнешься, как Нео из матрицы, со словами - "Я знаю лямбда и интерфейсы"

воскресенье, 1 декабря 2019 г.

Чем инверсия управления (Inversion of Control, IoC) отличается от инверсии зависимостей (Dependency Inversion Principle, DIP)?

#ооп #шаблоны_проектирования #dependency_injection #inversion_of_control


В литературе встречаются два разных понятия Инверсия управления и Принцип инверсии
зависимостей, которые сформулированы одинакаво:


Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей
должны зависеть от абстракций.
Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.


В чем состоит разница?
    


Ответы

Ответ 1



Inversion of Control -- это общий термин, который характеризует архитектуру ПО. Применяется обычно к фреймворкам -- инверсия управления является одной из характеристик. Фреймворки предоставляют точки подключения, в которых вы можете писать свой код. Но при этом общим выполнением программы по-прежнему управляет фреймворк, время от времени вызывая ваш код. Инверсия заключается в том, что в отличии от традиционного подхода не вы вызываете библиотечный код, а библиотечный код вызывает вас. Примерами могут являться ASP.NET и WPF с их событиями. При этом принцип инверсии управления может использоваться и в более мелком масштабе. Например, есть слой логики (BLL) и доступа к данным (DAL). Общеизвестно, что DAL не может обращаться к BLL. Но иногда возникает потребность на уровне DAL выполнить некий код, который должен принадлежать к BLL. В таком случае в DAL заводится некоторый интерфейс, этот интерфейс реализуется на уровне BLL, и экземпляр конкретной реализации передается из BLL в DAL. Такой пример, в частности, согласуется с DIP. Dependency Inversion Principle дает рекомендации о том, какими должны быть зависимости. Это как раз то, что вы процитировали: Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Вы также могли слышать о Dependency Injection. Внедрение зависимостей является связующим звеном между IoC и DIP. Это один из способов реализации инверсии управления. Лучше почитайте английскую Вики про IoC и DIP, там намного лучше все объяснено. Думаю, даже с помощью Гугл транслейта это будет полезнее, чем статьи на русской Вики. В качестве русскоязычного источника могу порекомендовать статью DI vs. DIP vs. IoC от Сергея Теплякова. Возможно он даже зайдет в этот вопрос и объяснит все как следует :).

суббота, 15 июня 2019 г.

Не понятен смысл DI

Мне не до конца понятен смысл использование DI (например autofac). Возможно, я не правильно трактую его смысл из-за неверной области применения.
Контекст задачи следующий: необходимо внедрить autofac модуль в систему тестирования. Собственно проблема, зачем мне писать код с разрешением зависимостей через тот же autofac если могу явно создавать объекты и передовать их в конструктор.
К примеру, есть ApiController, у него в конструкторе куча аргументов типа пользовательского контекста, менеджера регистрации, менеджера для отправки смс и т.д. Все эти объекты в свою очередь тоже достаточно сложно инициализируются. К примеру:
Вариант с Autofac
ISomeClassInterface someClass; var builder = new ContainerBuilder(); builder.RegisterType(); var container = builder.Build(); someClass = new SomeClass(container.Resolve();
Вариант без Autofac'a
ISomeClassInterface someClass; ISomeService service = new SomeService(some arguments...); someClass = new SomeClass(service);


Ответ

Вы сами ответили на свой вопрос: "Все эти объекты в свою очередь тоже достаточно сложно инициализируются". Чем сложнее, тем проще ошибиться. Безусловно, можно не использовать никакой DI. Но, во-первых, DI упрощает инициализацию объектов. Во-вторых, DI упрощает подмену объектов. Например, вы захотите использовать другой менеджер регистрации. Без DI придется менять код в нескольких местах, с DI - в одном.
Сравните:
A a = new A(new B(new C()), new D(new E())...);
И как-то условно
A a = container.Resolve();

воскресенье, 26 мая 2019 г.

Ninject не может создать обьект

При попытке создать обьект через контейнер Ninject вылетает exception : Object reference not set to an instance of an object. Подскажите, пожалуйста, в чем может быть проблема?
Метод с класса NinjectDependencyResolver
private void AddBindings() { var mapperConfiguration = new MapperConfiguration(cfg => { cfg.AddProfile(new TagProfile()); }); var mapper = mapperConfiguration.CreateMapper(); _kernel.Bind().ToSelf().InRequestScope(); _kernel.Bind, Repository>(); _kernel.Bind().ToConstant(mapper);
var repository = _kernel.Get>();// вылетает exception _kernel.Bind(); }
Класс Repository
public class Repository : IRepository where T : class { private readonly BlogDbContext _db;
public Repository(BlogDbContext db) { _db = db; } }
P.S. Не знаю важно ли это, но репозиторий с его интерфейсом находится в одной сборке, а регистрация Ninject в другой.


Ответ

Ошибка говорит о том, что в экземпляре объекта не задана ссылка на объект.
Object reference not set to an instance of an object.
В этих двух строчках
_kernel.Bind, Repository>(); _kernel.Bind();
Вы "говорите" IoC-контейнеру, что при запросе IRepository или Repository, необходимо вернуть реализацию, но не указываете ее.
Правильным будет следующий вызов.
_kernel.Bind>().To>(); _kernel.Bind().To();
Вы сообщаете IoC-контейнеру, что при запросе IRepository создать экземпляр класса Repository. Для второй строки аналогично.
И ответ на вопрос из комментария:
для контекста делаю InRequestScope(), нужно ли мне это делать и для репозитория и для сервиса?
Для репозитория и Сервиса не нужно делать InRequestScope(), только для контекста BlogDbContext

UPD
Не знаю важно ли это, но репозиторий с его интерфейсом находится в одной сборке, а регистрация Ninject в другой.
Не важно, главное указать ссылка на сборку в которой лежит репозиторий и интерфейсы

пятница, 14 декабря 2018 г.

Роль interface в Java

Здравствуйте, давно читаю разные туториалы и много где встречаю должно быть мало зависимостей, как я понял это все достигается при помощи interface и IoC. Нашел много примеров но так сути и не понял как правильно это достигать... К примеру у меня есть проект с моделями(таблицы из БД) и сервисы(классы для выполнения каких=либо действий над ними), и что получается мне для каждого сервиса писать интерфейс его действий? Я понимаю что если добавится другой класс мы просто имплементим его и ничего менять не нужно практически. Но с другой стороны много лишнего кода и классов. Может быть мне кто-нибудь сможет подробно объяснить как достигнуть минимизации зависимостей на примерах кода Java или просто на словах, чтобы я уловил смысл?


Ответ

Для чего вообще нужно минимизировать зависимости? Фредерик Брукс, автор статьи «Серебряной пули нет» утверждал, что производство программного обеспечения дело трудное, и в ближайшее время не появится ничего, что сделает его проще.
Это было в середине 80-х. Через 10 лет Брукс изучил, изменилось ли что-нибудь кардинально в индустрии. Оказалось, что нет. Правда, один из подходов выглядел многообещающе — повторное использование кода
Речь о том, что, написав большую систему, и приступая к написанию другой, мы могли бы взять готовые куски кода и использовать их повторно. Фактически, мы могли бы сократить работу минимум на 30%. Этому мешает то, что части системы сильно сцеплены (couple) друг с другом.
Для борьбы со сцепленностью придуманы без малого десятки техник. Часть из них дублируют друг друга на разных уровнях детализации. Вначале программы были не очень большими, и программисты делали независимыми отдельные функции. Скажем, предпочитали чистые функции, значение которых зависело только от аргументов, следовательно, их можно было безболезненно перенести в другой проект. Предпочитали модули с высокой связностью, но малой сцепленностью. В современных ОО языках предпочитают классы с единственной ответственностью.
Когда программы стали слишком большими, модули объединили в слои (уровни) и задали чёткое правило направления зависимостей: нижние уровни не могут зависеть от верхних. Никогда.
В классическом трёхзвенном приложении уровень представления зависит от уровня предметной области, а тот, в свою очередь, от уровня доступа к данным.
Presentation → Domain → Data Access
Для примера возьмём интернет-магазин. Одной из сущностей магазина является Заказ (Order). Этот класс принадлежит к уровню предметной области, потому что вся работа интернет-магазина строится вокруг Заказов. Если вы используете паттерн MVC в построении веб-приложения, то заведовать заказами будет Контроллер Заказов (OrderController). Этот класс принадлежит к уровню представления. Значит, класс OrderController может знать и использовать Order, а Order ничего про OrderController знать не может.
В этом есть глубокий смысл. Предположим, у вас работает не только веб-приложение, но фоновые сервисы, которые, по сути являются консольными приложениями. Они запускаются с заданным интервалом и что-то делают с заказами. Очень правильно в этом случае повторно использовать весь код, реализующий Заказы и их сохранение в БД. Но чтобы это сделать, надо чётко понимать, что является представлением, а что нет. OrderController реагирует за запросы HTTP, и у него есть такие штуки, как URI запроса, текущий пользователь и прочее. Но ничего этого нет у самого Заказа. Эта ошибка встречается часто: в классах предметной области хранятся дескрипторы окна, или данные, специфичные для веб-приложений. На самом деле класс Order не может строить никаких предположений о том, в какой среде его будут использовать.
Вопрос, трудно ли будет создать фоновые сервисы или оконные приложения, если приложение спроектировано правильно? Не очень трудно. Нам, конечно, придётся написать новый слой, но нам точно не нужно будет переписывать классы нижних уровней, в частности Order
Теперь опускаемся на уровень ниже, к данным. Предположим, все данные лежат в MySQL и мы для доступа к ним используем JDBC. SQL-запрос для постраничного списка заказов мы пишем сами, он в виде строковой константы находится в Java-коде:
SELECT Orders.* FROM Orders ORDER BY Orders.CreatedAt LIMIT ? OFFSET ?
На уровне доступа к данным у нас находятся классы Connection, ResultSet, Statement из пространства имён java.sql. Классы с верхних уровней могут к ним обращаться, то есть Order мог бы уметь создавать себя из ResultSet, а OrderController мог бы выполнять запросы с помощью Statement
Снова всё хорошо. Вопрос, трудно ли будет изменить способ хранения с MySQL на Oracle, или даже на что-нибудь вроде MongoDB? На этот раз гораздо труднее, чем раньше.
Нам придётся вносить изменения не только в классы нижнего уровня, но и в Order, и в OrderController. В Oracle постраничный доступ требует другого синтаксиса, а для загрузки данных из MongoDB уже нельзя использовать ResultSet
Решение заключается в том, чтобы в данном месте инвертировать зависимость. Мы говорим, что не знаем заранее, как будем обращаться к данным. Вместо конкретных классов ResultSet и MongoCollection мы скажем: у нас точно будет какое-то внешнее хранилище из которого мы захотим постранично получать данные. Наверное, нам придётся узнавать и общее количество страниц.
public interface OrderRepository { List ReadAll(int oneBasedPageNumber); int ReadTotalPages(); }
Интерфейс Хранилище Заказов (OrderRepository) находится на уровне предметной области, и OrderController может использовать его. Реализация интерфейса для MySQL находится на уровне доступа к данным, но теперь зависимость инвертирована: доступ к данным зависит от предметной области.
public MysqlOrderRepository implements OrderRepository { @Override public List ReadAll(int oneBasedPageNumber) { . . . } }
Реализация MysqlOrderRepository видит Order и может создавать объекты Заказа. Зависимости теперь выглядят так:
Presentation → Domain ← Data Access
Из всего изложенного становятся понятно, что использовать интерфейсы для доступа к объектам предметной области не нужно, если этого не требуют специальные условия. Скажем так, Заказ в интернет-магазине — одна из базовых сущностей, и вряд ли вам потребуют две конкурирующие реализации Заказов. Точно также и сервисы предметной области интерфейсов не требуют. Бизнес-процесс оформления заказа фиксирован, поэтому его сразу можно реализовать в виде конкретного класса Сервис Оформления Заказов (OrderService).
Использовать интерфейсы для объектов уровня представления также не нужно, потому что они находятся на самом верху и от них ничего не зависит. Интерфейсами имеет смысл закрывать только объекты и сервисы уровней ниже предметной области. Мы обозначили их как уровень Data Access, а Эрик Эванс, создатель DDD, предлагает называть их инфраструктурными. Соответственно, у нас может быть сервис рассылки электронных сообщений NotificationService. Это интерфейс уровня предметной области, чья реализация TwilioNotificationService находится на инфраструктурном уровне.
При такой организации проекта мы получаем возможность быстро клонировать проект и дописать новый кусок. Хотим сделать консольное приложение? Берём готовые уровни предметной области и инфраструктуры, дописываем обвязку консольного приложения. Хотим перенести на СУБД Postgres? Берём готовые уровни предметной области и представления, дописываем реализацию хранилищ на PostreSQL. Зависимости остаются, но они упорядочены и позволяют изымать и подменять целые уровни приложения.
Есть несколько случаев, когда интерфейсы могут появиться на высоких уровнях. Скажем, если в интернет-магазине могут быть разные способы начисления скидок, их удобно реализовать в виде паттерна Стратегия. По сути это будет иерархия однотипных классов, каждый из которых рассчитывает скидку на основании своих данных. Общие методы этих классов можно вынести в базовый интерфейс. В этом случае необходимость в интерфейсе диктуется уже не зависимостями, а способом реализации конкретного паттерна.
Резюмирую: мы в действительности можем говорить о минимизации зависимостей, как об управлениями зависимостями. Мы не можем от них избавиться совсем, но мы можем их ограничивать. Для этого мы разбиваем приложение на слои, и вводим правило: все зависимости идут в одну сторону. Использовать можно только свои классы, либо классы слоя, от которого мы зависим. Ядром приложения, его солью является слой предметной области. Все слои, которые выше него, оставляем как есть, они и так от него зависят. Для слоёв, которые ниже него, инвертируем зависимости. На практике это означает, что мы вводим интерфейсы в слой предметной области, которые реализуем в нижних слоях.

понедельник, 8 октября 2018 г.

Чем инверсия управления (Inversion of Control, IoC) отличается от инверсии зависимостей (Dependency Inversion Principle, DIP)?

В литературе встречаются два разных понятия Инверсия управления и Принцип инверсии зависимостей, которые сформулированы одинакаво:
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
В чем состоит разница?


Ответ

Inversion of Control -- это общий термин, который характеризует архитектуру ПО. Применяется обычно к фреймворкам -- инверсия управления является одной из характеристик.
Фреймворки предоставляют точки подключения, в которых вы можете писать свой код. Но при этом общим выполнением программы по-прежнему управляет фреймворк, время от времени вызывая ваш код. Инверсия заключается в том, что в отличии от традиционного подхода не вы вызываете библиотечный код, а библиотечный код вызывает вас. Примерами могут являться ASP.NET и WPF с их событиями.
При этом принцип инверсии управления может использоваться и в более мелком масштабе. Например, есть слой логики (BLL) и доступа к данным (DAL). Общеизвестно, что DAL не может обращаться к BLL. Но иногда возникает потребность на уровне DAL выполнить некий код, который должен принадлежать к BLL. В таком случае в DAL заводится некоторый интерфейс, этот интерфейс реализуется на уровне BLL, и экземпляр конкретной реализации передается из BLL в DAL. Такой пример, в частности, согласуется с DIP.
Dependency Inversion Principle дает рекомендации о том, какими должны быть зависимости. Это как раз то, что вы процитировали:
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Вы также могли слышать о Dependency Injection. Внедрение зависимостей является связующим звеном между IoC и DIP. Это один из способов реализации инверсии управления.

Лучше почитайте английскую Вики про IoC и DIP, там намного лучше все объяснено. Думаю, даже с помощью Гугл транслейта это будет полезнее, чем статьи на русской Вики.
В качестве русскоязычного источника могу порекомендовать статью DI vs. DIP vs. IoC от Сергея Теплякова. Возможно он даже зайдет в этот вопрос и объяснит все как следует :).