?

Log in

Previous Entry | Next Entry

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

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

Обсуждение – в этой теме.

Comments

( 16 comments — Leave a comment )
donz_ru
Sep. 14th, 2010 07:49 am (UTC)
Эти бы слова, да в нужные уши... :)
Samolisov Pavel [blogspot.com]
Sep. 14th, 2010 03:07 pm (UTC)
Как всегда замечательная статья. Единственный вопрос: Вы пишите, что DI и IoC - не одно и то же, соответственно существует еще какой(ие)-то механизм(ы) реализации IoC помимо DI. Возможно у вас есть какой-то пример?

З.Ы. По-моему запятая в названии статьи лишняя.
skipy_ru
Sep. 15th, 2010 06:00 am (UTC)
Павел, 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, но им не ограничивается.
Samolisov Pavel [blogspot.com]
Sep. 16th, 2010 01:26 am (UTC)
Большое спасибо за такой развернутый ответ. Кажется начало приходить просветление )
skipy_ru
Sep. 15th, 2010 06:00 am (UTC)
P.S. Запятую убрал. :)
magicprinc
Sep. 15th, 2010 11:21 am (UTC)
>существует еще какой(ие)-то механизм(ы) реализации IoC помимо DI

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

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

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

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

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

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

P.P.S. Длина имен функций меня волнует еще меньше. Имя должно описывать функцию, а его длина второстепенна.
kgeorgiy
Sep. 16th, 2010 07:19 pm (UTC)
> Вы их сначала реализуйте до конца, а потом сравните. Ничего похожего не будет.
То что у них одна реализация -- отчетливо видно из определения класса AbstractConfiguration.

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

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

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

Про Log4j: у них больше похоже на мою модель, только ConfigurationReader называется Configurator и вместо выдачи конфигурацию наружу, конфигурирует LoggerRepository.
skipy_ru
Sep. 17th, 2010 06:05 am (UTC)
>> Вы их сначала реализуйте до конца, а потом сравните. Ничего похожего не будет.
>То что у них одна реализация -- отчетливо видно из определения класса AbstractConfiguration.

Если бы все методы AbstractConfiguration были final - можно было бы говорить о том, что реализация определена. А я сейчас возьму и переопределю все методы в DBConfiguration. Надо мне.

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

Объясняю еще раз - я говорю про ОБЪЕКТНО-ОРИЕНТИРОВАННЫЕ языки. Python, OCaml, F# могу позволять делать все, что им заблагорассудится. И иметь все проблемы, которые к этому прилагаются.

>Если чисто объектный и логика сложная, то получаем XXXConfigurationReader.

И чем отличается наличие класса конфигурации и нескольких XXXConfigurationReader от наличия абстрактного класса конфигурации и нескольких реализаций, выполняющих роль XXXConfigurationReader? А я Вам скажу чем. Если я оставлю один класс конфигурации и несколько XXXConfigurationReader-ов - то для возможности расширения этой картины и добавления новых XXXConfigurationReader-ов я вынужден буду высветить все set-методы для конфигурации. В случае наследования все поля - protected, и возможности для изменения нет.

И потому же не рекомендуется использование статических функций, порождающих конфигурации. Для того, чтобы кто-то написал свою функцию - ему нужны все set-методы. Или конструктор со всеми значениями. И зачем мне давать лишние возможности использования, чтобы потом разгребать их последствия?
(Anonymous)
Sep. 20th, 2010 03:42 pm (UTC)
Чувствую, что написано хорошо, но я пока не дорос. А жаль :)
skipy_ru
Sep. 21st, 2010 08:02 am (UTC)
Это не беда. Даже если Вы начнете просто замечать такие архитектурные приемы - будет очень хорошо. А потом незаметно придет стадия, когда Вы посмотрите на код и поймете, что вот тут жесткое связывание, надо бы ввести интерфейсы.

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

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. Ну... разве что я соберусь и напишу. :) Когда время найду.
simplifierman
May. 8th, 2012 08:07 pm (UTC)
Очень круто) Отличная статья; особенно, с точки зрения систематизации знаний. Очень четко расставлены акценты на ключевые мысли. Сами принципы вроде быть чуть ли не сами собой разумеются, но прочтение статьи структурирует их в сознании и позволяет применять более осознанно, избегая явных ошибок проектирования
А вот работа с локаторами описана как-то недостаточно подробно. Хотелось бы иллюстративных примеров. Хотя бы про то, как на создавать и использовать чисто локаторы. Ну, и хорошо б и конкретный пример реализации совместного использования локатора и внедрения параметров.
И статью про остальные 3 базовые принципа хорошего дизайна было бы отлично увидеть =)
Alex Zezekalo
Oct. 28th, 2012 12:44 am (UTC)
Интересная статья... Мне как чуваку, который недавно начал изучать ООП И Джаву в лице Андроид-реализации интересно было читать, хотя охватить все тонкости сразу не получается... Хочется задать вопрос: "Раз у Вас так неплохо получается излогать свои мысли с глубоким погружением в предмет, то может быть сделаете серию статей о паттернах проектирования (а не только и синглтоне)? Был бы очень Вам признателен, тем более "знание паттернов проектирования - это унылое гав..о, но без него никуда" как я прочитал на одном айтишном форуме...
( 16 comments — Leave a comment )