Страницы

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

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

среда, 22 января 2020 г.

Зависимости по умолчанию в Android-проекте

#android_studio #gradle #dependencies


При работе с Android Studio я использую сборку Gradle. Есть ли какой-то способ добавить
dependencies по-умолчанию? Например, я постоянно использую библиотеку ButterKnife и
ещё несколько. Как перестать постоянно их прописывать? Чтобы при создании проекта они
сами добавлялись.
    


Ответы

Ответ 1



Например, вы можете заменить дефолтный шаблон из Android Studio\plugins\android\lib\templates\gradle-projects\NewAndroidProject\root\build.gradle.ftl.

воскресенье, 12 января 2020 г.

Менеджмент front-end зависимостей на Ruby

#ruby #gems #dependencies


Здравствуйте. Пишу программу с выводом в браузер. Для нее (помимо rubygems) нужно
несколько зависимостей: CSS, шрифты, JavaScript библиотека. Надо организовать управление
зависимостей, в первую очередь хотя бы скачивание по команде.
Варианты, которые я придумал:


Это может организовать Bower, но, так как программа распространяемая, не хотелось
бы заставлять пользователей устанавливать помимо Ruby еще и Node.js только чтобы загрузить
зависимости.
Можно распространять прямо с программой, но программа будет в Open Source на GitHub,
а засовывать чужие проекты в свой репозиторий, пусть даже и с дисклеймером, как-то
неудобно. Плюс самому придется следить за версиями каждой библиотеки и делать отдельный
commit под каждую версию каждой зависимости.
Можно сделать это через Rake + GitHub + Net::HTTP + File.write, но это как-то ненадежно
и займет дополнительную кучу кода, который надо поддерживать
Gem обертки, как известно, почти никогда никем не обновляются


Есть еще пара идей, но они на данный момент не кажутся реализуемыми:


Bower, увы, не предоставляет публичного Web API
CDN не вариант, так как программа, опять же, распространяемая и должна работать и
без подключения к интернету
Gem под данную задачу не нашел (подробнее ниже)


Пробовал также искать gem, но результаты плачевны:


giternal и rip позволяли загружать зависимости c git репозиториев, но они давно никем
не поддерживаются и не проходят большинство тестов
rails-assets и torba только для sprockets, а мне не хочется переписывать весь проект
под гем, который, слишком мягко говоря, мне совершенно не нравится.

    


Ответы

Ответ 1



В фронтэнде Node.js крепко укоренился для сборки и преобразования всего и вся. Поэтому требовать установленный Node.js для модификации ассетов это дело нынче вполне обычное. Посему, для полной среды разработки под ваш проект Node.js всё равно понадобится. Бежать от него бесполезно — догонит. Для вас в основном стоит задача "не заставлять конечных пользователей ставить Node.js, т. к. во время выполнения он уже не нужен". Но конечные пользователи скорее возьмут готовую сборку, чем будут делать её сами, и если в готовых сборках ассеты уже собраны, то и Node.js им не понадобится. Следовательно, задача уже решена. git clone не предназначен для распространения сборок. Вот совсем. Если вас нервирует необходимость заставлять даже ради разработки исключительно серверной части ставить зависимости ассетов, то можно сделать ход конём и вынести их в отдельный гем. А при генерации конфигов для вебсервера (или настройке встроенного) можно узнать, куда гем с ассетами установлен (на примере activesupport): # они работают немножко по-разному, вас интересует скорее первое Bundler.rubygems.find_name('activesupport').first.full_gem_path Gem::Specification.find_by_name('activesupport').gem_dir Это может иметь смысл где ассеты и серверная часть очень слабо связаны друг с дружкой, если ассеты образуют что-то SPA-подобное, например. И, соответственно, работает прежнее решение: вы пакуете релизы ассетов заранее, а кому надо заняться разработкой, по желанию может поставить уже упакованные.

суббота, 11 января 2020 г.

Разрешение зависимостей для орграфа с циклами

#графы #зависимости #dependencies #компиляция


Балуюсь, хочу сделать транслятор из BNF в код-грамматику для LEPL. Парсер написал,
код для отдельных правил генерируется, но дальше подзастрял с порядком правил.

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



Нужно разрешить зависимости относительно заданной вершины, получив список, в котором
следует записывать правила так, чтобы последующие правила уже «видели» все требуемые
им предыдущие. Если возникает цикл — нужно перед первым употреблением поместить «предварительную
декларацию.» Т.е., для примера графа выше, если, упрощая, считать, что вершины содержат
просто строки, получить что-то в духе:

>>> deps_dict = {
...    "A": {"B", "C"},
...    "B": {"D", "E"},
...    "C": {"A", "E"},
...    "E": {"E", "D"}
... }
>>> resolve_deps(deps_dict, "A")
[
    "D"              # {}
    Predeclare("E"), # ref'd by {E, B}
    "E"              # {D, E}
    "B"              # {D, E}
    Predeclare("A"), # ref'd by {C}
    "C"              # {A, E}
    "A"              # {B, C}
]


(То, что это не единственный вариант удовлетворяющего условиям результата — понятно.)

Положение в списке предварительных деклараций не очень важно — в худшем случае, можно
их вытащить наверх, хотя лично мне хочется размещать их не раньше самого необходимого
(т.е. перед первой отсылкой).

Если бы циклов не было — все было бы, вроде как, понятно — про топологическую сортировку
я нашел и почитал. Но как справиться с циклами — честно говоря, пока не соображу.
    


Ответы

Ответ 1



Поскольку БНФ оперирует контекстно-свободными грамматиками, то самое простое - это использовать соответствующий раздел теории синтаксического анализа. Самое неприятное, что может быть в произвольно взятой КС-грамматике G - это левая рекурсия, которая, нестрого говоря, делает грамматику G недетерминированной. При этом шаги алгоритма избавления от левой рекурсии включают в себя избавление от циклов. Поскольку вы, судя по всему, пытаетесь достичь детерминированности соответствующей грамматики, то лично я рекомендовал бы сразу же заимплементировать алгоритм избавления от левой рекурсии. Простое описание подхода избавления от левой рекурсии можно найти здесь, более же обзорная публикация по разным алгоритмам избавления от левой рекурсии расположена тут. Если вы действительно знаете, что делаете и хотите только избавиться от циклов, то воспользуйтесь только шагами (1) и (2) из публикации Eliminating left-recursion: three steps. Из дополнительных референсов могу порекомендовать пост Эрика Липперта по поводу CFG и циклов в них вообще, а также набор хороших презентаций по теории формальных языков.

понедельник, 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. После просмотра ты очнешься, как Нео из матрицы, со словами - "Я знаю лямбда и интерфейсы"

пятница, 13 декабря 2019 г.

Dependency Injection в WPF

#c_sharp #wpf #dependencies #dependency_injection


Добрый вечер! У меня появился такой вопрос. Как правильно реализовать внедрение зависимости
в WPF? 
Проблема вот в чем. Скажем у меня есть приложение которое включает в себя несколько
окон в каждом из которых нужно привязывать например интерфейс IFoo к классу Foo. Я
имогу сделать привязку конкретных типов к интерфейсам для каждого окна по отдельности
но проблема в том что разные окна используют одинаковые интерфейсы и если делать привязку
для каждого окна по отдельности то получится дублирование кода - дл\я каждого окна
будут выполняться одинаковые привязки, и при этом я не смогу изменить привязку только
из одного места в программе. Это нужно будет делать для каждого окна по отдельности.
Тогда рушится весь Dependency Injection.  А вот как это сделать так чтобы привязка
осуществлялась для всего приложения один раз и была известна ото всюду я не знаю.     


Ответы

Ответ 1



Прежде всего вам надо использовать паттерн Model-View-ViewModel в своем WPF-приложении, если вы еще не делаете этого. В приложении, реализованном с применением MVVM, окна содержат только XAML-разметку, а вся логика находится в моделях представлений. Именно в моделях представлений вам придется использовать внедрение конструктора для получения зависимостей. Вот так может выглядеть конструктор модели представления окна, который принимает в качестве зависимости IFoo: public class SomeWindowViewModel { public SomeWindowViewModel(IFoo foo) { } } Далее. Корневым объектом в вашем графе зависимостей будет модель представления главного окна приложения. Это будет первый объект, который создаст контейнер внедрения зависимоcтей и он должен быть создан раньше чем будет показано главное окно. Модель представления главного окна может принимать в качестве зависимостей IFoo и любые другие необходимые объекты: public class MainWindowViewModel { public MainWindowViewModel(IFoo foo, ISomeService service /*и т.д.*/) { } } Корень компоновки, то место где вы единственный раз определите все привязки, и соберете ваш граф объектов, должен находится в точке входа приложения. В случае с WPF-приложением точкой входа является класс App, и здесь есть небольшая проблема. По умолчанию файл App.xaml выглядит вот так: Здесь строчка StartupUri="MainWindow.xaml" декларативно указывает фреймворку, что при запуске приложения должно быть создано и отображено окно MainWindow.xaml. Чтобы взять контроль в свои руки вы должны удалить строку StartupUri="MainWindow.xaml" и создать окно сами в методе OnStartup класса App. Теперь вы контролируете процесс создания окна и можете привязать к нему его ViewModel. При этом не следует создавать модель представления главного окна самостоятельно. Вместо этого надо получить ее из контейнера внедрения зависимоcтей, позволив тем самым контейнеру самостоятельно построить граф объектов. На примере CastleWindsor это будет выглядеть так: public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // создаем контейнер внедрения зависимостей var container = new WindsorContainer() // подключаем модуль, в котором прописаны привязки // (этот модуль - то самое место, где мы прописываем привязки // единственный раз) container.Install(new MainInstaller()) // теперь, когда контейнер осведомлен о привязках, // мы можем запросить у него ViewModel, и контейнер вернет // ее нам, самостоятельно разрешив все зависимости var viewModel = container.Resolve(); // теперь можно показывать главное окно var mainWindow = new MainWindow(); mainWindow.DataContext = viewModel; mainWindow.Show(); } }

четверг, 28 февраля 2019 г.

Разрешение зависимостей для орграфа с циклами

Балуюсь, хочу сделать транслятор из BNF в код-грамматику для LEPL. Парсер написал, код для отдельных правил генерируется, но дальше подзастрял с порядком правил.
Есть набор правил, ссылающихся друг на друга. Зависимости можно просто представить в виде орграфа, в котором есть циклы:

Нужно разрешить зависимости относительно заданной вершины, получив список, в котором следует записывать правила так, чтобы последующие правила уже «видели» все требуемые им предыдущие. Если возникает цикл — нужно перед первым употреблением поместить «предварительную декларацию.» Т.е., для примера графа выше, если, упрощая, считать, что вершины содержат просто строки, получить что-то в духе:
>>> deps_dict = { ... "A": {"B", "C"}, ... "B": {"D", "E"}, ... "C": {"A", "E"}, ... "E": {"E", "D"} ... } >>> resolve_deps(deps_dict, "A") [ "D" # {} Predeclare("E"), # ref'd by {E, B} "E" # {D, E} "B" # {D, E} Predeclare("A"), # ref'd by {C} "C" # {A, E} "A" # {B, C} ]
(То, что это не единственный вариант удовлетворяющего условиям результата — понятно.)
Положение в списке предварительных деклараций не очень важно — в худшем случае, можно их вытащить наверх, хотя лично мне хочется размещать их не раньше самого необходимого (т.е. перед первой отсылкой).
Если бы циклов не было — все было бы, вроде как, понятно — про топологическую сортировку я нашел и почитал. Но как справиться с циклами — честно говоря, пока не соображу.


Ответ

Поскольку БНФ оперирует контекстно-свободными грамматиками, то самое простое - это использовать соответствующий раздел теории синтаксического анализа. Самое неприятное, что может быть в произвольно взятой КС-грамматике G - это левая рекурсия, которая, нестрого говоря, делает грамматику G недетерминированной.
При этом шаги алгоритма избавления от левой рекурсии включают в себя избавление от циклов. Поскольку вы, судя по всему, пытаетесь достичь детерминированности соответствующей грамматики, то лично я рекомендовал бы сразу же заимплементировать алгоритм избавления от левой рекурсии.
Простое описание подхода избавления от левой рекурсии можно найти здесь, более же обзорная публикация по разным алгоритмам избавления от левой рекурсии расположена тут.
Если вы действительно знаете, что делаете и хотите только избавиться от циклов, то воспользуйтесь только шагами (1) и (2) из публикации Eliminating left-recursion: three steps
Из дополнительных референсов могу порекомендовать пост Эрика Липперта по поводу CFG и циклов в них вообще, а также набор хороших презентаций по теории формальных языков.

пятница, 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. Зависимости остаются, но они упорядочены и позволяют изымать и подменять целые уровни приложения.
Есть несколько случаев, когда интерфейсы могут появиться на высоких уровнях. Скажем, если в интернет-магазине могут быть разные способы начисления скидок, их удобно реализовать в виде паттерна Стратегия. По сути это будет иерархия однотипных классов, каждый из которых рассчитывает скидку на основании своих данных. Общие методы этих классов можно вынести в базовый интерфейс. В этом случае необходимость в интерфейсе диктуется уже не зависимостями, а способом реализации конкретного паттерна.
Резюмирую: мы в действительности можем говорить о минимизации зависимостей, как об управлениями зависимостями. Мы не можем от них избавиться совсем, но мы можем их ограничивать. Для этого мы разбиваем приложение на слои, и вводим правило: все зависимости идут в одну сторону. Использовать можно только свои классы, либо классы слоя, от которого мы зависим. Ядром приложения, его солью является слой предметной области. Все слои, которые выше него, оставляем как есть, они и так от него зависят. Для слоёв, которые ниже него, инвертируем зависимости. На практике это означает, что мы вводим интерфейсы в слой предметной области, которые реализуем в нижних слоях.