Евгений Матюшкин (skipy_ru) wrote,
Евгений Матюшкин
skipy_ru

Categories:
  • Mood:

Обсуждение статьи «Модульный дизайн, или «что такое DIP, SRP, IoC, DI и т.п.»»

Появилась новая статья. А с ней - и новый раздел. "Архитектура". Давно хотел начать писать в этом направлении, да все никак не складывалось. Была одна статья о синглтонах, но больше про технику реализации.

И вот, наконец, сложилось. Статья о принципах модульного проектирования. Как сделать так, чтобы модуль можно было переиспользовать. Читайте: http://www.skipy.ru/architecture/module_design.html.

Обсуждение – в этой теме.
Tags: article, solid, архитектура, статья
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 16 comments
Эти бы слова, да в нужные уши... :)
Как всегда замечательная статья. Единственный вопрос: Вы пишите, что DI и IoC - не одно и то же, соответственно существует еще какой(ие)-то механизм(ы) реализации IoC помимо DI. Возможно у вас есть какой-то пример?

З.Ы. По-моему запятая в названии статьи лишняя.
Павел, IoC (Inversion of Control) и DI (Dependency Injection) - действительно не одно и то же. IoC - это общий принцип. Он совершенно не связан с разработкой ПО, хотя получил известность именно благодаря ей. Даже в Википедии IoC назван принципом объектно-ориентированного программирования (http://en.wikipedia.org/wiki/Inversion_of_control, http://ru.wikipedia.org/wiki/Обращение_контроля).

В своей статье об IoC Мартин Фаулер привел хорошую аналогию с т.н. «голливудским принципом» – «не звоните нам, мы вам позвоним сами». Саму статью можно прочитать вот тут: http://martinfowler.com/bliki/InversionOfControl.html.

В дополнение приведу еще один пример IoC из Java, не связанный явно с DI.

Возьмем обычную сериализацию. Как Вы знаете, существует два способа реализации - использование Serializable и Externalizable. Начнем со второго.

Что происходит при обработке Externalizable? Объекту дают поток и говорят - на, родной, пишись! И объект сам пишет свои данные. И точно так же при чтении, создается пустой экземпляр, ему дается поток и говорится - на, родной, читайся. И объект сам читает свои данные.

А что происходит в случае Serializable? Объект сам ничего не делает. Он попадает в механизм, который разбирает его по косточкам, вытаскивает из него все данные и записывает в поток. И точно так же при чтении - внешний механизм читает данные из потока и прописывает их в объект. В этом случае сам объект не является активным действующим лицом - все операции производятся над ним кем-то. IoC в чистом виде.

На самом деле, если копнуть глубже - тут наблюдается сходство с DI. И в том, и в другом случаях в определенное поле объекта пишется определенное значение. Это может быть ссылка на объект, это может быть прочитанное число. Не суть принципиально, и то и другое - IoC. Просто когда мы говорим об объектах, мы не думаем о ссылках, - это ниже того уровня абстракции, на котором мы мыслим в данный момент. Скажем так, запись ссылки - это физическая реализация установки зависимости. А на более высоком уровне абстракции мы говорим о DI. То есть можно сказать, что IoC лежит в основе принципа реализации DI, но им не ограничивается.
Большое спасибо за такой развернутый ответ. Кажется начало приходить просветление )
P.S. Запятую убрал. :)
>существует еще какой(ие)-то механизм(ы) реализации IoC помимо DI

Классики также приводят в качестве примера IoC-не-DI оконную систему управляемую событиями, т.е.
не код активно читает getKeypress(), а сама система вызывает методы кода по приходу событий от клавиатуры
А зачем нам нужны классы XMLConfiguration и DBConfiguration, у которых совершенно одинаковое поведение? Почему в место них не использовать ConcreteConfiguration + функции createConfigurationFromXml и createConfigurationFromDB?

Единственная проблема, которую я тут вижу -- где определять createConfigurationFromXXX. Если у нас не чисто объектный язык -- то их логично сделать top-level функциями модуля реализации. Если Java/C# -- придется завести отдельный класс со статическими методами (заодно названия станут короче).
А кто сказал, что у XMLConfiguration и DBConfiguration совершенно одинаковое поведение? Вы их сначала реализуйте до конца, а потом сравните. Ничего похожего не будет.

Насчет статических функций. Я Вам советую посмотреть на классы конфигурации из Log4J. Чтобы объем оценить. Вы действительно ЭТО хотите в одну статическую функцию запихнуть? А как только начнете разбивать - в одном классе окажется куча мелких функций, одни работают со свойствами, другие с XML, третьи с аннотациями (а ведь через месяц Вам аннотации точно понадобятся, в очередном проекте!). И Вы в них мгновенно утонете.

Кроме того, статические функции противоречат принципу OCP - при необходимости добавления новой конфигурации Вам придется менять ConcreteConfiguration. А мне придется всего лишь добавить новый класс. Отдельный, ни на кого больше не влияющий.

P.S. > их логично сделать top-level функциями модуля реализации

Хочу напомнить, что я говорю про объектно-ориентированные языки. Процедурные меня не волнуют. Кстати, "отдельный класс со статическими методами" - это тоже из процедурных языков принесено.

P.P.S. Длина имен функций меня волнует еще меньше. Имя должно описывать функцию, а его длина второстепенна.
> Вы их сначала реализуйте до конца, а потом сравните. Ничего похожего не будет.
То что у них одна реализация -- отчетливо видно из определения класса AbstractConfiguration.

> Вы действительно ЭТО хотите в одну статическую функцию запихнуть?
Нет, конечно, ООП не противоречит функциональной декомпозиции.

> при необходимости добавления новой конфигурации Вам придется менять ConcreteConfiguration
1. А что мешает определять новые методы в других классах?

> Хочу напомнить, что я говорю про объектно-ориентированные языки. Если у нас объектный язык с примесью функционального (Python, OCaml, F#), то -- функции (не обязательно объявленные в одном месте)+ их передача . Если чисто объектный и логика сложная, то получаем XXXConfigurationReader.

Про Log4j: у них больше похоже на мою модель, только ConfigurationReader называется Configurator и вместо выдачи конфигурацию наружу, конфигурирует LoggerRepository.

skipy_ru

9 years ago

Anonymous

September 20 2010, 15:42:32 UTC 9 years ago

Чувствую, что написано хорошо, но я пока не дорос. А жаль :)
Это не беда. Даже если Вы начнете просто замечать такие архитектурные приемы - будет очень хорошо. А потом незаметно придет стадия, когда Вы посмотрите на код и поймете, что вот тут жесткое связывание, надо бы ввести интерфейсы.

P.S. Думаете, я до всего этого дошел сразу? Мне лет 6-7 понадобилось, чтобы только начать чувствовать такие вещи. И еще года 4 - чтобы быть способным внятно сформулировать.
Здравствуйте Евгений, благодарю вас за статью. Очень хорошо написана. Жаль что в сети мало вот таких вот статей про архитектуру. У меня есть несколько вопросов относительно локатора сервисов:
1)Кто отвечает за конфигурацию и заполнение локатора? Конфигурацию можно сделать в xml или аннотациях. Локатор должен их прочитать и сам себя заполнить. Так или нет?
2)В локаторе должны находиться только экземпляры зависимостей и тех кто от них зависит или что то еще?
Если делать конфигурацию аннотациями как вы написали в статье то:
3)какие параметры должны быть указаны в @Provider и @Injection?
4)как эффективно искать классы помеченные именно этими аннотациями? Не переберать же все классы из cp?
5)Где можно посмотреть пример такого локатора с аннотациями?
Изменяюсь, за такое большое кол-во вопросов, но надеюсь что вы ответите.
Добрый день, анонимный товарищ! :)

1. Ну, в принципе так. Лучше на XML, аннотации все-таки надо искать.

2. Да, локатор предназначен для создания дерева зависимостей. Все остальное там лишнее.

3. Дело в следующем. Если мы будем, например, ориентироваться только по имени класса - мы получим по одному экземпляру каждого типа. А если их нужно несколько? Например, DataDestination - на его базе можно сделать лог. Т.е. в модуле мы будем иметь не один экземпляр DataDestination, а два. Работа с ними идентичная, что нам и надо. А вот получение - в одном случае я могу написать, скажем, @Injection(Type.business), в другом - @Injection(Type.log). Считаем, что enum Type определен. Ну и две реализации - @Provider(DataDestination.class, Type.business) и @Provider(DataDestination.class, Type.log). Соответственно, при внедрении зависимости по классу и конкретному типу мы можем найти нужный экземпляр.

4. При старте - не надо, это долго. А вот при сборке - да. Других вариантов нет. Сканируется наш jar (только наш, а не весь classpath, что уже существенно лучше), всё, что найдено с аннотациями - собирается. Потом пишется xml, на который и будет ориентироваться локатор.

5. Ну... разве что я соберусь и напишу. :) Когда время найду.
Очень круто) Отличная статья; особенно, с точки зрения систематизации знаний. Очень четко расставлены акценты на ключевые мысли. Сами принципы вроде быть чуть ли не сами собой разумеются, но прочтение статьи структурирует их в сознании и позволяет применять более осознанно, избегая явных ошибок проектирования
А вот работа с локаторами описана как-то недостаточно подробно. Хотелось бы иллюстративных примеров. Хотя бы про то, как на создавать и использовать чисто локаторы. Ну, и хорошо б и конкретный пример реализации совместного использования локатора и внедрения параметров.
И статью про остальные 3 базовые принципа хорошего дизайна было бы отлично увидеть =)
Интересная статья... Мне как чуваку, который недавно начал изучать ООП И Джаву в лице Андроид-реализации интересно было читать, хотя охватить все тонкости сразу не получается... Хочется задать вопрос: "Раз у Вас так неплохо получается излогать свои мысли с глубоким погружением в предмет, то может быть сделаете серию статей о паттернах проектирования (а не только и синглтоне)? Был бы очень Вам признателен, тем более "знание паттернов проектирования - это унылое гав..о, но без него никуда" как я прочитал на одном айтишном форуме...