Comments 25
Добавлю что как и с объектами, лучше больше узко специализированных интерфейсов, чем меньше раздутых. Таким образом мы пишем 4 интерфейса, которые разделены по задачам и после объединяем их в один композицией.
Так-же и с классами — вместо того чтобы городить кучу классов, есть прекрасный механизм трейтов, которые мы примешиваем в класс.
Стараюсь сделать так чтобы на каждый интерфейс его реализация лежала в трейте. Таким образом когда любой класс по той или иной причине наследует этот интерфейс, можно легко и просто добавить ему функционал который реализует интерфейс с помощью трейта, и чтобы заменить его — достаточно просто написать другой трейт и поменять одну строку в самом классе.
Трейты плохи тем, что в них не очевиден контекст, в котором они исполняются. При классическом наследовании, не множественном, эта проблема тоже есть, но не так явно выражена.
В общем, композиция предпочтительнее наследования. Как раз композиция естественным путём подталкивает нас использовать интерфейсы между объектами. Наследование классов — нет, оно толкает к связности на основе реализации.
Есть такое, однако если взаимодействовать с классом не через его свойства напрямую а через геттеры/сеттеры которые описаны в классе, а трейте гетеры/сеттеры делать обязательными и абстрактными причем с тайп-хинтингом это немного упрощает дело.
trait SeedTableTrait
{
/**
* @return DataBaseRepositoryInterface
*/
abstract public function getDataRepository() : DataBaseRepositoryInterface;
/**
* @param array $dataset
*/
public function seed(array $dataset)
{
$repository = $this->getRepository();
$repository->create($dataset);
}
}
У интерфейса нет зависимости от контекста, неявного состояния, модификаторов доступа к методам и магии позднего статического связывания — нет и соответствующих проблем.
Это не значит, что нельзя спроектировать плохую систему используя интерфейсы. Но это разные уровни
— косяки дизайна на уровне программных интерфейсов
— и сложность и побочные эффекты появляющиеся из-за реализации опирающейся на «не рекомендуемые» шаблоны проектирования.
И чем больше логики в коде вынесено на интерфейсный уровень, тем может быть проще такая система в поддержке. Если про сферических коней в вакууме.
Большой проблемы нет. И сложность, которую приходиться держать в уме есть всегда.
Тот же LoggerAwareTrait из PSR я использую, и лучшего решения не могу придумать.
Проблемы могут быть когда в трейте размещается реализация какого-нибудь бизнесового интерфейса. Трейт не знает какой интерфейс он реализует. И интерфейс не знает о том какие трейты его реализуют.
Вот эта неявность при рефакторинге или ревью кода может доставлять неудобства. Со всеми вытекающими: Когнитивная нагрузка, риски ошибиться, отсутствие подсказок ide, сложность статического анализа и рефлексии, дополнительные условия или соглашения в подводных тестах трейты же тоже надо тестировать, но стоит решить как...
Среди положительных побочных эффектов — очень резкое уменьшение кода и сложности. Многомиллионный код сворачивается в код из пару сотен строк. Нет нужды читать весь код. А когда нужно добавить новых функционал, всегда известно в каком именно месте его следует разместить.
Есть еще архитектура снизу-вверх. Удобна когда бизнес логики очень много, и малая иерархия. Например для игр-квестов и поиск предметов.
Есть еще и более совершенная архитектура — самоогранизующая эволюционная. Но управлять ей почти невозможно, но зато дает массу неожиданных положительных системных эффектов.
Есть еще и более совершенная архитектура — самоогранизующая эволюционная. Но управлять ей почти невозможно, но зато дает массу неожиданных положительных системных эффектов.
Вы заинтриговали, можно несколько более развернуто?
Тем не менее, благодарю за высказанные мысли. Похожие бродят и в моей голове при анализе собственных творений.
Там именно с архитектурной точки зрения рассматривается устройство ядра. И когда ты читаешь и видишь как сложнейшие задачи, которые должна эффективно и правильно решать ОС, оказывается можно описать и логика предложенных решений выглядит тривиальной, элегантной, то начинаешь восхищаться этой архитектурой. Книга объемистая, и некоторые вещи действительно сложны для понимания, но на меня она произвела неизгладимое впечатление в плане архитектурного мышления. Я бы посоветовал именно эту книгу в качестве примера.
у вас будет выбор либо использовать какую-то высокоуровневую систему (быстро, возможно в ущерб гибкости решения), либо “дособрать” из низкоуровневых компонент решение, которое точнее ложится под бизнес потребности
К примеру, у вас может быть высокоуровневая компонента “уведомить пользователя о событии”. Она, исходя из настроек в профиле пользователя выбирает длинный либо короткий вариант уведомления и отсылает его либо смской либо на почту. Такая высокоуровневая компонента использует 2 более низкоуровневые: “отправить смску на номер X с содержанием Y” и “отправить имейл по адресу X с содержанием Y”.Ага, и только автор архитектуры знает, как её использовать. Потом кто-то будет вызывать первый интерфейс, кто-то второй (просто потому что может), добавятся новые параметры, новая логика уведомлений, всё это будет делаться несогласованно между двумя этими интерфейсами и исправляться, в итоге взаимодействие поставщика и потребителя держится на костылях и неявных договорённостях, они становятся связаны, как старые приятели. А потом приходит новый сотрудник, которому дают задачу «сообщить пользователю об отрицательном балансе», он видит всю эту эволюционировавшую «красивую» архитектуру и матерится про себя, думая, что за идиоты это спроектировали.
Архитектуру надо проектировать так, чтобы её нельзя было неправильно использовать. И следить, чтобы её никто всё-таки не заюзал неправильно.
Прежде всего вопросы компетентности программистов и коммуникации внутри команды я оставил за пределами этой статьи.
Полные и абстрактные интерфейсы очень легки в своем понимании. Именно для этого я и привел пример про счетчик использования файла в Линуксе — очень простая для понимания, очень эффективная абстракция. Использовать ее не поназначению будет весьма затруднительным. Большинство правильных (с моей точки зрения) интерфейсов именно так и выглядят — их можно описать 1-2 предложениями и сложно перепутать с каким-то другим интерфейсом, используемым в программе.
Если говорить об этом примере с уведомлениями. То интерфейс большой компоненты будет выглядеть «Уведомить пользователя Х о событии У». Ведь его будет очень сложно спутать с интерфейсом «отправить смс с содержанием Х на номер У». Это 2 очень разные вещи. И мне кажется, что в будущем дополнительно расширять интерфейс что первый что второй уже не будет нужно, они уже полные… т.е. они уже полностью описиывают свою функцию.
Дополнительно, компоненту «отправить смску» можно использовать и для других целей… Допустим, ее можно использовать если что-то упало в инфраструктуре и слать смску сисадмину. Сисадмин не является пользователем системы, поэтому более абстрактная компонента «уведомить пользователя Х о событии У» тут неприменима. Компоненту «отправить имейл с содержанием Х на ящик У» можно еще использовать для отправки каких-то ежедневных отчетов сотрудникам компании.
Я искренне считаю, что эти 2 вещи спутать сложно. И да, их будет проще спутать, когда в каждый из этих интерфейсов еще напихают по полдюжины каких-то флагов/параметров сомнительного смысла и полезности. Для этого я и упомянул в статье, что перегружая интерфейс лишними подробностями мы его делаем менее полными, чем идеально сбалансированный интерфейс.
Видео с конференции по Clojure, я знаю Clojure очень поверхностно, но это не помешало мне насладиться ходом мысли докладчика:
www.youtube.com/watch?v=Tb823aqgX_0
Я тоже скептически отношусь к многоуровневому наследованию. Интересная логика была описана в видео. Я таким образом еще ни разу не мыслил, но я вижу, что она очень применима, особенно в тех местах, где нет расширяемости за счет дополнительных подключаемых модулей. Ведь с появлением новых дополнительных модулей, они тоже захотят где-то хранить свое состояние. И возможно тогда все состояние уже будет проблематично упаковать в одном единственном месте.
Но идея докладчика мне действительно понравилась :) Спасибо =)
У него в примере Картахены их было 3 и на его примере было четко видно, что:
- каждая из функций, которую он придумал, однозначно относилась к одному из 3х слоев
- каждая функция состоит из нескольких строк и активно использует функции на 1 слой ниже.
Архитектура кода