Как стать автором
Обновить

Комментарии 25

Добавлю что как и с объектами, лучше больше узко специализированных интерфейсов, чем меньше раздутых. Таким образом мы пишем 4 интерфейса, которые разделены по задачам и после объединяем их в один композицией.


Так-же и с классами — вместо того чтобы городить кучу классов, есть прекрасный механизм трейтов, которые мы примешиваем в класс.


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

Согласен с вами. Эти «максимизация объектов» и «минимизация связей» не только численно измеряется, но еще и качественно — 4 маленьких интерфейса лучше, чем 2 жирных толстых интерфейса. =)
Вы буквально процитировали Роба Пайка.

Трейты плохи тем, что в них не очевиден контекст, в котором они исполняются. При классическом наследовании, не множественном, эта проблема тоже есть, но не так явно выражена.
В общем, композиция предпочтительнее наследования. Как раз композиция естественным путём подталкивает нас использовать интерфейсы между объектами. Наследование классов — нет, оно толкает к связности на основе реализации.

Есть такое, однако если взаимодействовать с классом не через его свойства напрямую а через геттеры/сеттеры которые описаны в классе, а трейте гетеры/сеттеры делать обязательными и абстрактными причем с тайп-хинтингом это немного упрощает дело.


Осторожно php код!
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, сложность статического анализа и рефлексии, дополнительные условия или соглашения в подводных тестах трейты же тоже надо тестировать, но стоит решить как...

Ну это принципы SOLID + иерархическая декомпозиция + кибернетика. Такая архитектура- самая распространненая и устойчивая. Проектировать нужно именно сверху-вниз, наверху самая высокоуровненая абстракция(это очень удобно с точки зрения управления, аналогия иерархия в армии). Большое количество связей дает комбинаторный взрыв, плюс любая «сложная система» генерирует эмерджетные свойства, побочные эффекты. SOLID даст очень прочный каркас, но из-за этого вытекают и свои минусы(вдобавок проблемы с временем жизни объектов, null-проблемы, с асинхронными процесами, колбеками).
Среди положительных побочных эффектов — очень резкое уменьшение кода и сложности. Многомиллионный код сворачивается в код из пару сотен строк. Нет нужды читать весь код. А когда нужно добавить новых функционал, всегда известно в каком именно месте его следует разместить.
Есть еще архитектура снизу-вверх. Удобна когда бизнес логики очень много, и малая иерархия. Например для игр-квестов и поиск предметов.
Есть еще и более совершенная архитектура — самоогранизующая эволюционная. Но управлять ей почти невозможно, но зато дает массу неожиданных положительных системных эффектов.
Есть еще и более совершенная архитектура — самоогранизующая эволюционная. Но управлять ей почти невозможно, но зато дает массу неожиданных положительных системных эффектов.

Вы заинтриговали, можно несколько более развернуто?
частично это интернет, всемирная паутина, блокчейн, нейросети, клеточные автоматы.
Сейчас как раз занимаюсь разработкой такой архитектуры для игр. Понятие «баг — это фича» как раз возникает в таких системах.
Спасибо за статью, хотя не хватает описания практического применения…
Тем не менее, благодарю за высказанные мысли. Похожие бродят и в моей голове при анализе собственных творений.
Хехе, я попытался включить некоторые примеры в статью, она и так получилось слишком длинной, поэтому я не сильно налегал на примеры. Но я могу посоветовать прочитать одну книгу о внутреннем устройстве Юникса. Я ее читал 4 года назад и плакал как ребенок, которому купили вкусную и красивую шоколадку))) Вот эта книга www.amazon.com/Design-UNIX-Operating-System/dp/0132017997

Там именно с архитектурной точки зрения рассматривается устройство ядра. И когда ты читаешь и видишь как сложнейшие задачи, которые должна эффективно и правильно решать ОС, оказывается можно описать и логика предложенных решений выглядит тривиальной, элегантной, то начинаешь восхищаться этой архитектурой. Книга объемистая, и некоторые вещи действительно сложны для понимания, но на меня она произвела неизгладимое впечатление в плане архитектурного мышления. Я бы посоветовал именно эту книгу в качестве примера.
у вас будет выбор либо использовать какую-то высокоуровневую систему (быстро, возможно в ущерб гибкости решения), либо “дособрать” из низкоуровневых компонент решение, которое точнее ложится под бизнес потребности
К примеру, у вас может быть высокоуровневая компонента “уведомить пользователя о событии”. Она, исходя из настроек в профиле пользователя выбирает длинный либо короткий вариант уведомления и отсылает его либо смской либо на почту. Такая высокоуровневая компонента использует 2 более низкоуровневые: “отправить смску на номер X с содержанием Y” и “отправить имейл по адресу X с содержанием Y”.
Ага, и только автор архитектуры знает, как её использовать. Потом кто-то будет вызывать первый интерфейс, кто-то второй (просто потому что может), добавятся новые параметры, новая логика уведомлений, всё это будет делаться несогласованно между двумя этими интерфейсами и исправляться, в итоге взаимодействие поставщика и потребителя держится на костылях и неявных договорённостях, они становятся связаны, как старые приятели. А потом приходит новый сотрудник, которому дают задачу «сообщить пользователю об отрицательном балансе», он видит всю эту эволюционировавшую «красивую» архитектуру и матерится про себя, думая, что за идиоты это спроектировали.

Архитектуру надо проектировать так, чтобы её нельзя было неправильно использовать. И следить, чтобы её никто всё-таки не заюзал неправильно.
Мммм, какой вы пессимист) Я с вами не согласен.

Прежде всего вопросы компетентности программистов и коммуникации внутри команды я оставил за пределами этой статьи.

Полные и абстрактные интерфейсы очень легки в своем понимании. Именно для этого я и привел пример про счетчик использования файла в Линуксе — очень простая для понимания, очень эффективная абстракция. Использовать ее не поназначению будет весьма затруднительным. Большинство правильных (с моей точки зрения) интерфейсов именно так и выглядят — их можно описать 1-2 предложениями и сложно перепутать с каким-то другим интерфейсом, используемым в программе.

Если говорить об этом примере с уведомлениями. То интерфейс большой компоненты будет выглядеть «Уведомить пользователя Х о событии У». Ведь его будет очень сложно спутать с интерфейсом «отправить смс с содержанием Х на номер У». Это 2 очень разные вещи. И мне кажется, что в будущем дополнительно расширять интерфейс что первый что второй уже не будет нужно, они уже полные… т.е. они уже полностью описиывают свою функцию.

Дополнительно, компоненту «отправить смску» можно использовать и для других целей… Допустим, ее можно использовать если что-то упало в инфраструктуре и слать смску сисадмину. Сисадмин не является пользователем системы, поэтому более абстрактная компонента «уведомить пользователя Х о событии У» тут неприменима. Компоненту «отправить имейл с содержанием Х на ящик У» можно еще использовать для отправки каких-то ежедневных отчетов сотрудникам компании.

Я искренне считаю, что эти 2 вещи спутать сложно. И да, их будет проще спутать, когда в каждый из этих интерфейсов еще напихают по полдюжины каких-то флагов/параметров сомнительного смысла и полезности. Для этого я и упомянул в статье, что перегружая интерфейс лишними подробностями мы его делаем менее полными, чем идеально сбалансированный интерфейс.

Я думаю что все решается дополнительным уровнем абстракции: компонент для уведомления админа о неполадках.
Из этого следует что лучше абстрагироваться даже от низкоуровневых компонентов.

Годная статья! От себя хотелось бы добавить, что не правильно спроэктированая система, особенно сложная, дает менимое ощущение контроля ситуации в проэкте. В какой-то момент такая система начинает диктовать правила написания кода, когда програмист просто не в силах сделать какой-либо рефакторинг потому, что одна часть тянет за собой другую и ситуация превращается в карточный домик из середины которого пытаются выдернуть карту так, чтобы он не упал. Таким образом предстоящий рефакторинг может превратиться в нескончаемый ад. В правильных системах рефакторить код приятно и легко. Обычно в таких системах замена функционала не ведет к краху всей системы.
Полностью согласен =) Как раз один проект, который я собственными руками довел до состояния «карточного домика», и завставил меня вообще сесть и почухать репу на тему, как же можно писать сложные системы и при этом сохранять контроль над ситуацией. Ответ получится у меня таким, каким я его описал в этой статье :)
Не так давно я увидел замечательное видео с практическим примером применения построения архитектуры снизу вверх.
Видео с конференции по Clojure, я знаю Clojure очень поверхностно, но это не помешало мне насладиться ходом мысли докладчика:
www.youtube.com/watch?v=Tb823aqgX_0
Спасибо большое за видео! С удовольствием посмотрел.

Я тоже скептически отношусь к многоуровневому наследованию. Интересная логика была описана в видео. Я таким образом еще ни разу не мыслил, но я вижу, что она очень применима, особенно в тех местах, где нет расширяемости за счет дополнительных подключаемых модулей. Ведь с появлением новых дополнительных модулей, они тоже захотят где-то хранить свое состояние. И возможно тогда все состояние уже будет проблематично упаковать в одном единственном месте.

Но идея докладчика мне действительно понравилась :) Спасибо =)
Вот только что проснулся, и в голове как щелкнуло — в этом видео мужик показал куда нагляднее, чем у меня получилось в статье, понятие нескольких вертикальных слоев АПИ (я в статье про это говорил «несколько уровней детализации»).

У него в примере Картахены их было 3 и на его примере было четко видно, что:
  • каждая из функций, которую он придумал, однозначно относилась к одному из 3х слоев
  • каждая функция состоит из нескольких строк и активно использует функции на 1 слой ниже.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории