Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Software Design > Inversion of Control


Автор: andyag 17.03.11, 09:46
Привет!

Несколько часов потратил на попытки понять что же такое inversion of control, так и не смог :-)

Цитата
In computer programming, Inversion of control (IoC) is an abstract principle describing an aspect of some software architecture designs in which the flow of control of a system is inverted in comparison to procedural programming.
Читаю так: IoC - это абстрактный принцип, описывающий особенность некоторых дизайнов архитектуры ПО, в которых "порядок передачи управления" инвертирован относительно оного в процедурном программировании. Не понимаю.

1. Начнём с того, что IoC - это приинцип. Принцип, в моём понимании, это утверждение вида "в ситуации X надо (можно, следует) делать Y". Возьмём, например, LSP:
"It states that, in a computer program if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substitutes for objects of type T), without altering any of the desirable properties of that program (correctness, task performed, etc.).". Читаю как: он утверждает, что если S - это подтип T, тогда объекты типа T могут быть заменены объектами S. Т.е. к LSP моё понимание "принципа" подходит. В случае LSP можно сказать, выполняется он (принцип) или нет. Как определить, выполняется ли IoC?
2. Известно, что у IoC формального определения нет. Известно, что IoC определяется как разница между фреймворком и библиотекой. Разницу между фреймворком и библиотекой я понимаю: абстрактное решение для всей задачи vs. конкретные решения для её частей.

Помогите, пожалуйста, разобраться :-)

Автор: Машина 17.03.11, 11:22
По-моему это то же разделение на интерфейс и имплементацию, токо сказанное по новому :)

В процедуральном подходе вызывающий блок знает, какой блок он вызывает, а здесь нет, т.к. вызывает интерфейс, а имплементация подключается "вне кода".

Автор: ss 17.03.11, 11:35
Вот наглядный пример для DI.

Добавлено
Ну или более скучное, но тоже понятное непонятное описание. Как его не найти было?.. :unsure:

Автор: andyag 17.03.11, 12:32
Спасибо за ответы!

Машина,
Т.е. если я использую указатели на функции в коде на Си (например, предикат для сортировки), это уже получается "удовлетворение принципа IoC"?

ss,
Огромное спасибо за наглядный пример :-) Википедию, конечно, тоже читал и русскую и английскую.


Попытаюсь уточнить проблему. Я не понимаю общего смысла IoC. Нужна формулировка, а не описание свойств. Насколько будет верно, если я скажу так:

IoC - это принцип построения дизайна таким образом, что любая функциональная единица (модуль, класс, метод) принимает только те решения по поводу своего поведения, которые она не может не принимать.

?

Примеры:
1. Думаю, мой пример с сортировкой и предикатом - правильный. Сортировка отвечает только за перестановку элементов в случае, когда она знает что их нужно переставить, а решения по поводу правильности перестановки принимает какой-то внешний код, про который сортировка ничего не знает.
2. Если про класс можно сказать, что он читает данные из базы и выводит их на консоль, база для него должна быть не базой, а "штукой, которая умеет интерпретировать sql и возвращать результаты запросов". Класс должен отвечать только за построение запросов и за получение/отображение результатов. Что там за база на самом деле он знать не должен, т.к. вполне может без этого обойтись.

Автор: Rififi 17.03.11, 13:21
andyag

Несколько часов потратил на попытки понять что же такое inversion of control, так и не смог :-)

http://blog.byndyu.ru/2009/12/blog-post.html

Автор: ss 17.03.11, 13:38
Ну вот же! Стоило чуть копнуть - и всё становится понятно.

Читай дальше:
Цитата
In traditional programming the flow of the business logic is controlled by a central piece of code, which calls reusable subroutines that perform specific functions. Using Inversion of Control this "central control" design principle is abandoned. The caller's code deals with the program's execution order, but the business knowledge is encapsulated by the called subroutines.
In practice, Inversion of Control is a style of software construction where reusable generic code controls the execution of problem-specific code. It carries the strong connotation that the reusable code and the problem-specific code are developed independently, which often results in a single integrated application.

IoC, как ты уже заметил, это принцип. Он заключается в том, что вызывающая сторона знает только порядок вызова методов, но как работает конкретный метод она не знает, ибо обращается через интерфейс.
А Dependency Injection - это один из способов следования IoC, способ отвязать caller'а от конкретной реализации.

Некоторые статьи в русской википедии пишут дебилы. Ибо IoC > DI, а не как там преподносят.

Автор: andyag 17.03.11, 14:06
ss,
У меня вообще складывается впечатление, что в инженерии ПО набралось слишком много слов типа IoC и DI, которые мало кто понимает, и поэтому каша такая. Тут теперь второй вопрос - где граница между IoC и SRP (single responsibility) :-)

Rififi,
Спасибо за интересную статью, но не стоит меня так цитировать, будто я идиот и не смог эту статью найти :-) Статья, несмотря на всю свою интересность, никак не связана с моей проблемой :-) Внимательнее читай вопрос. В статье нет ни определения, ни более-менее внятной теории, а как переписывать хреновый нетестируемый код в более-менее вменяемый - это другой вопрос, тут дело привычки.

Автор: ss 17.03.11, 14:22
andyag, да они какбэ не в одной плоскости находятся... Ты запросто можешь наплевать на SRP и яро следовать IoC, или наоборот, или оба сразу...
Что тебе, например, мешает зафигачить интерфейс навроде кухонного комбайна? Или ДеревяннаяНога + КоричневаяПластмассоваяСтолешница, в которой будет четыре указателя на ДеревяннуюНогу?

Автор: andyag 17.03.11, 14:48
ss,
Ну вот смотри. Пишем чудовищный класс, который делает какие-то манипуляции с базой данных:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class DBManipulator // должен делать несколько запросов в несколько таблиц
    {
      private DBConnection connection;
      public DBManipulator(string connectionString, DBConnectorEnum dbConnectorType) // внимание, говнокод :-)
      {
        if(dbConnectorType == MSSQL) { connection = new MsSqlConnection(connectionString); }
        else if(dbConnectorType == MYSQL) { connection = new MySqlConnection(connectionString); }
        else throw new WhatTheFuckIsYourDB();
      }
     
      void doSomethingWithDB() { ... }
    }


Вопросы:
1. Нарушается ли тут IoC? Почему?
2. Нарушается ли тут SRP? Почему?

Мои ответы:
IoC нарушается - мы принимаем решение по поводу того, через что подключаться к базе, хотя вообще говоря не наше это дело, наше дело - делать запросы. Для запросов нам достаточно просто иметь подключение к базе. Нам его надо иметь? Мы его будем иметь. Пусть кто-нибудь даст. Если бы в конструктор передавалось существующее подключение, это решило бы проблему. (это исходя из моего определения - всё ещё не уверен, насколько оно верное)
SRP нарушается - на классе 2 ответственности - "подключиться к базе", что-то "делать с базой". Избавляемся от "подключиться к базе" (т.е. берём решение для пункта 1), и... SRP тоже не нарушается.

В чём я неправ?

Автор: ss 17.03.11, 15:01
Да нет тут нарушения ИоКа - ты ж не обращаешься напрямую к методам конкретных коннекшнов, через интерфейс работаешь. Тут только СРП нарушен.

Автор: andyag 17.03.11, 15:03
Цитата ss @
не обращаешься напрямую к методам

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
          class DBManipulator // должен делать несколько запросов в несколько таблиц
          {
            private DBConnection connection;
            public DBManipulator(string connectionString, DBConnectorEnum dbConnectorType) // внимание, говнокод :-)
            {
              //////////////////////////////////////////////////////////////////////////////////////////////////////
              if(dbConnectorType == MSSQL) { connection = MsSqlConnection.fromConnectionString(connectionString); } // ну вот такой он, этот MsSqlConnection
              //////////////////////////////////////////////////////////////////////////////////////////////////////
              else if(dbConnectorType == MYSQL) { connection = new MySqlConnection(connectionString); }
              else throw new WhatTheFuckIsYourDB();
            }
          
            void doSomethingWithDB() { ... }
          }

А так? Вообще говоря, я с тобой и по поводу старого примера не могу согласиться, любой конструктор - ни разу не интерфейсный метод, это деталь реализации.

Автор: ss 17.03.11, 17:58
Ну ок, вынесешь ты создание коннекшнов из конструктора, будешь указатель передавать... А где ты их создашь, коннекшны-то? Втам не будет нарушения ИоКа?
Жизнь - она, сука, не по книжке... Всегда во все парадигмы и идиомы уложиться не получится. Главное, когда на это напорешься, сильно не переживать.

Автор: andyag 18.03.11, 09:25
У нас в скоупе был 1 этот класс, так что дальнейшая реализация не интересна :-) Будет там что-то типа читалки конфигов + предоставлялка подключений к базе.
По поводу теории - полностью согласен. Деньги в большинстве случаев вообще только за практику платят. Но это не повод не знать теорию :-)

Автор: deil 22.03.11, 11:48
ИМХО IoC определяется намного проще - это принцип, согласно которому мы пишем ПО так, что объекты "не падают с неба". Т.е. минимизируем кол-во new XXX().
Если объекту А нужна ссылка на объект Б, это не в его компетенции знать, где взять нужную ему ссылку и уж тем более создать объект Б руками (в реальной жизни же ничего само по себе не материализуется). Всё остальное уже логично вытекает из этого утверждения.

А интерфейсы и сокрытие реализации методов, в которые вы ударились - это ребята ООП :) Инкапсуляция.. IoC и интерфейсы никак не взаимосвязаны, вообще никак.

Автор: Машина 22.03.11, 12:44
Цитата deil @
А интерфейсы и сокрытие реализации методов, в которые вы ударились - это ребята ООП :) Инкапсуляция.. IoC и интерфейсы никак не взаимосвязаны, вообще никак.

Я бы сказал, что инкапсуляция (сокрытие внутренних полей и методов) и интерфейсы - это разные вещи :rolleyes:

Автор: andyag 22.03.11, 13:00
Цитата deil @
ИМХО IoC определяется намного проще - это принцип, согласно которому мы пишем ПО так, что объекты "не падают с неба". Т.е. минимизируем кол-во new XXX().
Ура, у нас есть новое слово - "компетенция". Твоё определение одновременно и уже, и размытее того, что я написал выше :-)
Уже, потому что в моё определение включены любые решения, которые кто-то должен принять: буквально утверждается, что если ты пишешь какой-то класс и у него в каком-то методе принимается какое-то решение, принималку этого решения нужно вынести в виртуальный метод или вообще сделать абстрактный метод, чтоб юзер потом сам думал что ему нужно. Ну вот буквально паттерн template method. По состоянию на сегодня я такое проектирование отношу к соблюдению IoC. Ошибаюсь?
Теперь про размытость - для конструирования N объектов, new, в любом случае, будет использован N раз. А вот интересные вопросы - где, когда, кем?
Цитата deil @
Если объекту А нужна ссылка на объект Б, это не в его компетенции знать, где взять нужную ему ссылку и уж тем более создать объект Б руками (в реальной жизни же ничего само по себе не материализуется). Всё остальное уже логично вытекает из этого утверждения.
Гхм.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class A
    {
      public B TheB { get; set; } // A нужна ссылка на B
    }
     
    class B {}
    ...
    A a = new A();
    a.TheB = new B(); // объект класса А не принимает решения по поводу "где взять B", при этом имеем ужасный процедурный говнокод. Здесь IoC выполняется по-твоему?


Цитата
А интерфейсы и сокрытие реализации методов, в которые вы ударились - это ребята ООП :) Инкапсуляция.. IoC и интерфейсы никак не взаимосвязаны, вообще никак.

Ты меня извини, но по-моему весь смысл проектирования ПО только и крутится вокруг интерфейса и реализации. Все паттерны, все принципы - говорят только о том кто кого должен видеть и как это должно происходить, а больше ничего и нет :-)

Автор: deil 23.03.11, 14:22
Цитата andyag @
Ура, у нас есть новое слово - "компетенция". Твоё определение одновременно и уже, и размытее того, что я написал выше :-)
Уже, потому что в моё определение включены любые решения, которые кто-то должен принять: буквально утверждается, что если ты пишешь какой-то класс и у него в каком-то методе принимается какое-то решение, принималку этого решения нужно вынести в виртуальный метод или вообще сделать абстрактный метод, чтоб юзер потом сам думал что ему нужно. Ну вот буквально паттерн template method. По состоянию на сегодня я такое проектирование отношу к соблюдению IoC. Ошибаюсь?

Считаю, что да, ошибаешься.

Вернемся к примеру с БД.
Задача: подсчитать кол-во пользователей в системе, у которых возвраст = ХХХ (select count(*) from users where age = ?)
Супер-подробный абстрактный алгоритм:
1. взять конфиг подключения к БД
2. подключиться к БД
3. узнать, в какой табличке лежат юзеры
4. узнать, в каком поле лежит возраст
5. создать запрос select count(*) from [users_table] where [age_column] = ?
6. передать ему параметр
7. выполнить запрос, получить ответ
8. распарсить полученный ответ

Следуя твоей логике, нам необходим некто, кто выдаст строку подключения к БД, некто кто сможет к ней подключиться (причемпущай нам пофиг если это MySQL, PostgreSQL, SQLite), объект\метод, который хранит информацию о метаданных, абстрактный объект SqlCommand, абстрактный объект SqlParameter (у нас же хз какая БД может быть! тут либо MySQLCommand, SQLiteCommand, etc) и парсилка ответов (ну вдруг нам вернут число в виде строки :)). Получим метод с 666 параметрами-интерфейсами на вход. Это не IoC, это overengineer'инг :))) Точнее, это шаблон template method, query, builder, называй как хочешь, но не сабж.

Если мой код отвечает за подключение к БД и поиск всех пользователей с возврастом = 42, он за это и должен отвечать. Но все, что ему необходимо для решения своей задачи (обычно ресурсы и знания) он не сам возьмёт (рассчитает, спросит у кого-то а-ля SqlConnection.CreateCommand() и т.д.), а ему дадут их на вход.

Добавлено
Цитата andyag @
Теперь про размытость - для конструирования N объектов, new, в любом случае, будет использован N раз. А вот интересные вопросы - где, когда, кем?

Тут ты и ответил на вопрос данного треда (SRP vs IoC): IoC отвечает на вопросы где, когда, кем так - вне моего кода, до вызова, не мной.
Паттерн template method отвечает по-другому: прямо тут, по требованию, мной. Но просто реализация этих действий (в данном случае - new XXX()) сокрыта от моих глаз через абстракции и наследование. И хотя коннешн создается специально отведенным объектом, который принимает кучу сложных решений, но финальное решение "получить коннекшн" всё-равно лежит на твоём коде. IoC оперирует именно такими решениями, из области control flow, а не из области инкапсуляции\полиморфизма.

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

Автор: andyag 23.03.11, 16:57
Спасибо за развёрнутый ответ :-)

Цитата deil @
Считаю, что да, ошибаешься.

Вернемся к примеру с БД.
Задача: подсчитать кол-во пользователей в системе, у которых возвраст = ХХХ (select count(*) from users where age = ?)
Супер-подробный абстрактный алгоритм:
1. взять конфиг подключения к БД
2. подключиться к БД
3. узнать, в какой табличке лежат юзеры
4. узнать, в каком поле лежит возраст
5. создать запрос select count(*) from [users_table] where [age_column] = ?
6. передать ему параметр
7. выполнить запрос, получить ответ
8. распарсить полученный ответ

Следуя твоей логике, нам необходим некто, кто выдаст строку подключения к БД, некто кто сможет к ней подключиться (причемпущай нам пофиг если это MySQL, PostgreSQL, SQLite), объект\метод, который хранит информацию о метаданных, абстрактный объект SqlCommand, абстрактный объект SqlParameter (у нас же хз какая БД может быть! тут либо MySQLCommand, SQLiteCommand, etc) и парсилка ответов (ну вдруг нам вернут число в виде строки :)). Получим метод с 666 параметрами-интерфейсами на вход. Это не IoC, это overengineer'инг :))) Точнее, это шаблон template method, query, builder, называй как хочешь, но не сабж.

Если мой код отвечает за подключение к БД и поиск всех пользователей с возврастом = 42, он за это и должен отвечать. Но все, что ему необходимо для решения своей задачи (обычно ресурсы и знания) он не сам возьмёт (рассчитает, спросит у кого-то а-ля SqlConnection.CreateCommand() и т.д.), а ему дадут их на вход.
Давай для начала сформулируем задачу.
Требуется написать программу, которая будет подключаться к одной из нескольких баз (базы с разными движками) брать оттуда некоторую статистику и выводить на консоль. Такая формулировка пойдёт?

Построение дизайна определяется:
1. Множеством тех баз (источников) с которыми нам нужно работать
2. Ожидаемыми перспективами проекта

По поводу первого пункта. Можем ли мы утверждать, что работать придётся только с SQL-базами? Можем ли мы утверждать, что вообще речь идёт только о базах? Может текстовые файлы какого-нибудь экзотического формата тоже нужны? Можем ли мы утверждать, что в каждой базе есть понятие таблицы или чего-то очень близкого по значению? Ответив на все эти вопросы (возможно, ещё несколько), мы можем определиться с абстракцией. Есть 2 предельных случая:
а. У нас все базы (источники) совершенно одинаковые, разница у них только в брендинге :-)
б. Базы абсолютно разные, есть и SQL, и текстовые файлы, и нечто наше проприетарное.
В случае а. мы можем начать строить абстракцию для алгоритма где-то на уровне "выполнить запрос и получить результат". Алгоритм при этом будет "идеально абстрактен" :-)
В случае б. мы вообще ничего не можем обобщить. Абсолютно. Мы можем написать по конкретной реализации алгоритма на каждый тип источника данных.

Второй пункт. Нам это нужно сопровождать? Это вообще программа для одного запуска или ей будут пользоваться? Насколько сильно планируется расширять? Здесь опять 2 предельных случая:
а. Нужно написать, 1 раз запустить и забыть.
б. Планируется развивать до чёрт знает чего.
В случае а. у нас есть требования и о будущем думать не нужно. Если какая-то абстракция идеально подходит прямо сейчас, мы так и сделаем.
В случае б. сразу нужно думать о будущем. Нужно быть осторожнее с абстракциями, т.к. сегодня у нас может быть только sql, а завтра появится что-то ещё.

К какому совокупному предельному случаю ближе наша задача?
аа. Пишем "в лоб", пишем так, чтоб просто хорошо писалось. Чтоб избежать глюков сегодня и чтоб не запутаться в течение 1 дня.
аб. Нужно думать о будущем. Сейчас у нас только SQL. Завтра добавится какая-нибудь SQL-база, или текстовый файл?
ба. Пишем "в лоб" - 10 реализаций алгоритма для разных баз.
бб. Нужно думать о будущем, причём очень много и очень осторожно.

В нашей задаче какая ситуация? :-) Ты говоришь о 666 параметрах у метода, но умалчиваешь о самом "проекте".

666 параметров у метода говорят о том, что в системе есть место, где взаимодействую 666 элементов, причём никакие 2(или больше) элемента(ов) из этих 666 не олицетворяют какую-то подсистему нашей системы.

Что я сказать-то пытаюсь.

Всегда можно добиться того, чтобы у нашего метода было всего 2 параметра:
"штука, которая инкапсулирует работу с конкретной базой" и "возраст"

Если у нас базы только SQL, и SQL у всех одинаковый, то для нас SQL запрос может быть вообще строкой текста. И это будет нормально. Как только появляется какая-то разница между SQL у MsSql и SQL у MySql, пора вводить абстракцию. И абстракция эта должна быть достаточной, для того, чтобы скрыть разницу между разными базами для нашего алгоритма. Поэтому в случае, если у нас все базы sql, и если мы точно получаем информацию только через запрос select, и точно знаем, что во всех базах есть таблица users, и точно знаем, что в ней есть поле age, и точно знаем, что блаблабла, у нас "штука, которая инкапсулирует работу с конкретной базой" будет называться SqlQueryExecutor, единственное что от неё требуется - выполнять sql запрос (sql - в виде строки) и возвращать результат в каком-то унифицированном виде (например, DataTable).

А если у нас противоположная ситуация, и мы в общем случае ни в чём не уверены, "штука, которая инкапсулирует работу с конкретной базой" будет называться UserStatisticsAnalyzer, которая будет давать нам метод Count(Condition where), который мы благополучно будем дёргать, передавая ему объект нашей реализации Condition. Т.е. буквально:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class WhereAgeIsXCondition : Condition
    {
      // тут реализация условия "возраст равен"
    };
     
    // а вот наш метод:
    int countUsersWithAge(UserStatisticsAnalyzer analyzer, int age) // мне пофигу, база там или не база, пофигу кеширует кто-то что-то или нет, вообще всё пофигу :-)
    {
      return analyzer.Count(new WhereAgeIsXCondition(age)); // а может тут вообще лямбдой можно обойтись
    }


Как конструируется объект analyzer - не моё дело. В методе countUsersWithAge() проблем нет. Всё остальное тоже можно расписать таким образом. И НИГДЕ не будет 666 параметров. Я это гарантирую :-) :-) :-)

SRP - выполняется, у countUsersWithAge() всего одна обязанность.
IOC - выполняется, countUsersWithAge() принимает всего 1 решение - указывает методу Count какой предикат использовать.

Автор: deil 23.03.11, 18:50
Сорри за мой ответ, но - и? :lol:

Да, верю, SRP / IoC выполняются. Но код в 1 строчку не показателен всё же.

Автор: andyag 23.03.11, 22:00
Цитата deil @
Сорри за мой ответ, но - и? :lol:

Согласен, я не прорезюмировал :-)

Речь идёт о твоих 666 аргументах. Если я правильно понял, ты имел в виду что-то вроде:
DBConnectionConfig
DBConnectionFactory
DBTableResolver
DBFieldResolver
...тут ещё 661...
int age // ура :-)

Я пытался сказать, что при вменяемом дизайне не может существовать такого куска кода (особенно в пределах одного метода), где друг с другом будут взаимодействовать 666 объектов :-) Если ты подумал, что я имел в виду такой подход - спешу тебя разуверить, я совсем не об этом говорил :-) Речь шла о том, чтобы всю функциональность разбросать по классам таким образом, чтобы каждый класс (да и метод тоже) знал только то, что ему нужно знать, а предоставлял только ту информацию, ради которой он изначально задумывался.

Возьмём не 666 аргументов, а 4:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    a b c d   // вот эти 4 аргумента
    \ / \ /
     e   f    // задумываемся о том, можно ли из групп элементов построить какие-то сущности, строим (повторяем, пока сущности выходят вменяемыми
      \ /     // как только доходим до первого ЖопоЗефирногоСокета, останавливаемся).
       g      // вот результат (если повезло и голова работает правильно)


Простой пример, если тебе нужно сделать запрос к базе и получить результат, тебе вообще говоря не нужно соединение с базой (DBConnection). Тебе нужна штука, которая сможет выполнять твой запрос и вернуть результат (QueryExecutor - а вот у неё внутри уже может быть DBConnection). Теперь QueryExecutor. Ему нужна штука, которая может дать ему подключение к базе (DBConnectionResolver). Отлично. DBConnectionResolver'у нужна какая-то штука, которая знает настройки подключения к базе (DBConnectionConfigurationResolver). Тут остановимся (банально надоело :-) ). В результате имеем:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // ...где-то, не факт, что исключительно ради вызова нашего метода...
    DBConnectionConfigurationResolver dbConnectionConfigResolver = DBConnectionConfigurationResolver.Instance; // ...
    DBConnectionResolver dbConnectionResolver = new DBConnectionResolver(dbConnectionConfigResolver); // ему нужен только конфиг, умеет подключаться куда надо
    QueryExecutor queryExecutor = new QueryExecutor(dbConnectionResolver); // ему нужно только подключение, умеет выполнять запросы
    // (ты веришь, что я смогу в итоге получить объект со сколь угодно кастомным поведением, при это сам объект об этом никогда не догадается?)
    //
     
    // вызов нашего волшебного метода
    // ему нужны только "исполнитель запроса" и "возраст"
    int personCount = countPersonsWithAge(queryExecutor, 42); // только их и даём
    // никакой магии - годы тренировок и ловкость рук :-)
    // queryExecutor - это объект с ОЧЕНЬ сложным поведением, при этом класс QueryExecutor (да и все остальные)
    // просты, как семейные трусы.

Далее в очередной раз цитирую себя любимого:
IoC - это принцип построения дизайна таким образом, что любая функциональная единица (модуль, класс, метод) принимает только те решения по поводу своего поведения, которые она не может не принимать.

Сопоставляем эту формулировку и пример выше. IoC выполняется, т.к. отдельные классы отвечают только за себя, никакими 666 аргументами тут и не пахнет, более того, явно видно, что при сопровождении кода с таким же подходом код будет усложняться только количественно (больше сущностей, больше строчек код), а качественная сложность будет оставаться постоянной (любой класс зависит от пары интерфейсов и не более того).

Тут резюме заканчивается :-)

Цитата deil @
код в 1 строчку не показателен всё же.

Сейчас вспомнил свои сложности с "рисованием треугольника общего вида" (всё время то равнобедренный, то прямоугольный получаются), задумался на тему того, как должен выглядеть показательный код (код общего вида :D ).

Автор: deil 24.03.11, 12:11
Начнем с того, что ты 666 параметров заменил 666-ю абстрактными классами\интерфейсами :yes:
И все-равно написал вот этот код:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    DBConnectionConfigurationResolver dbConnectionConfigResolver = DBConnectionConfigurationResolver.Instance; // ...
    DBConnectionResolver dbConnectionResolver = new DBConnectionResolver(dbConnectionConfigResolver); // ему нужен только конфиг, умеет подключаться куда надо
    QueryExecutor queryExecutor = new QueryExecutor(dbConnectionResolver);


Шило на мыло, в данном конкретном примере :)

Но неважно.

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    def factorial(n)
      return 1 if n == 1
     
      if n > 666
        puts "warning: calculation will be slow"
      end
     
      return n * factorial(n-1)
    end


Считаем факториал. Решение "вывести на экран предупреждение, если входное число больше 666" - мы "не можем его не принимать"? Для рассчета факториала данный if - явно лишний и не жизненно необходимый. Но он есть, потому что я так захотел.
Всё, мы нарушили IoC?

Добавлено
А теперь?
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class SixSixSixWarningCondition
      def show_warning?(n)
        return (n > 666)
      end
    end
     
    def factorial(n)
      return 1 if n == 1
      return n * factorial(n-1)
    end
     
    def factorial_with_warning(n)
      cond = SixSixSixWarningCondition.new()
      
      if cond.show_warning?(n)
        puts "warning: calculation will be slow"
      end
     
      return factorial(n)
    end


Добавлено
Во :yes:

Автор: ss 24.03.11, 12:35
deil, первый код от второго принципиально не отличается, ибо оба написаны "в процедурном стиле"

Автор: deil 24.03.11, 12:36
Ну давайте ещё "процедурный стиль" обмусолим :wub:
Итак, что не так с кодом? Почему к нему нельзя применять определение "IoC"?

Автор: andyag 24.03.11, 12:38
Цитата deil @
Шило на мыло, в данном конкретном примере :)

Да тут же принципиальная разница:
В твоём случае речь идёт о взаимодействии 666 объектов в одной точке, в моём случае - во всём проекте.
В твоём случае речь о том, что на поведение единицы влияет 666 факторов, в моём случае на поведение единицы влияют 1-2-3 фактора.

Цитата deil @
Считаем факториал. Решение "вывести на экран предупреждение, если входное число больше 666" - мы "не можем его не принимать"? Для рассчета факториала данный if - явно лишний и не жизненно необходимый. Но он есть, потому что я так захотел.
Всё, мы нарушили IoC?
Твой пример показателен. Это реально хреновый код :-) Сколько ворнингов выведет factorial(1000)? А всё потому что "ты так захотел" :-)
Правильное решение - написать отдельно вменяемый факториал без принта и отдельно враппер, который выводит ворнинг в нужном случае и в зависимости от контекста использовать то или другое. Врапперов с принтом при этом можно сделать несколько штук - для вывода на консоль, для пиканья спикером и т.д. Твоя реализация - идеально нерасширяемая.

И да, IoC тут нарушается из-за того, что алгоритм вычисления факториала знает что такое "консоль" (если я правильно трактую Руби, который я не знаю).

Добавлено
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    using System;
    using System.Collections.Generic;
    using System.Text;
     
    namespace cs_ioc2
    {
        class Math
        {
            public static int Factorial(int i)
            {
                if (i == 0)
                {
                    return 1;
                }
     
                if (i == 1)
                {
                    return 1;
                }
     
                return i * Factorial(i - 1);
            }
        }
     
        interface FactorialCalculator
        {
            int calculateFactorial(int i);
        }
     
        class SimpleFactorialCalculator : FactorialCalculator
        {
            public int calculateFactorial(int i)
            {
                return Math.Factorial(i);
            }
        }
     
        class ConsoleWarningFactorialCalculator : FactorialCalculator
        {
            public int calculateFactorial(int i)
            {
                if (i > 10)
                {
                    Console.WriteLine("may be slow");
                }
     
                return Math.Factorial(i);
            }
        }
     
        class Program
        {
            // мне пофигу, мне нужно вычислить факториал
            // кто, как и куда - мне ПОФИГУ
            static int myAlgorithm(FactorialCalculator calc)
            {
                return 123 + calc.calculateFactorial(11);
            }
     
            static void Main(string[] args)
            {
                // вычислим с ворнингом
                FactorialCalculator calc = new ConsoleWarningFactorialCalculator();
                Console.WriteLine("{0}", myAlgorithm(calc));
     
                // вычислим без ворнинга
                FactorialCalculator calc2 = new SimpleFactorialCalculator();
                Console.WriteLine("{0}", myAlgorithm(calc2));
            }
        }
    }

Автор: deil 24.03.11, 13:19
И с этой стороны код говно, и с другой =) Ужас, стыдно :rolleyes:

Мы ушли от темы и в этом болоте стало сложнее выражать точку зрения.

Итак, чуть назад:
Цитата
Уже, потому что в моё определение включены любые решения, которые кто-то должен принять: буквально утверждается, что если ты пишешь какой-то класс и у него в каком-то методе принимается какое-то решение, принималку этого решения нужно вынести в виртуальный метод или вообще сделать абстрактный метод, чтоб юзер потом сам думал что ему нужно. Ну вот буквально паттерн template method. По состоянию на сегодня я такое проектирование отношу к соблюдению IoC. Ошибаюсь?


Давай про факториал, какие решения могут приниматься?
1. куда выводить ворнинг - консоль, файл, етц; Реализация принятия данного решения - в интерфейсе с методом Write и выборе правильной реализации.
2. в каких случаях выводить ворнинг. Реализация принятия данного решения - тупой if (ну, в коде а-ля-говнокод-от-deil'а)

По твоей трактовке, метод рассчета факториала должен выглядеть так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    int Calculate(int n, IOutputStream output, Condition condition) {}

Я прав?

Именно здесь я и не соглашаюсь. Принимаемые решения могут быть двух типов: одни связаны с поиском и разрешением зависимостей (ресурсы, входные параметры, етц), другие - на уровне бизнес-логики.

Условие 2 (в каких случаях выводить ворнинг) - это бизнес-логика.
Я не хочу давать пользователю моего класса поле для маневров, мне не нужно вообще заморачиваться с его расширяемостью и прочим - моё ПО вообще для варки кофе! Поэтому я не делаю абстрактный класс Condition, не реализую специфичный My666Condition, который наследуется от и уточняет PropertyCondition. И не трачу время на враппер для своего метода, который подсовывает конкретный объект Condition. И параллельно не нарушаю IoC.

Автор: andyag 24.03.11, 14:18
Цитата deil @
По твоей трактовке, метод рассчета факториала должен выглядеть так:

Посмотри, пожалуйста, в моё предыдущее сообщение, я там дописал код, который иллюстрирует мою точку зрения :-)

Цитата deil @
Я прав?

Ды нет же :-)


Цитата deil @
Именно здесь я и не соглашаюсь. Принимаемые решения могут быть двух типов: одни связаны с поиском и разрешением зависимостей (ресурсы, входные параметры, етц), другие - на уровне бизнес-логики.

Условие 2 (в каких случаях выводить ворнинг) - это бизнес-логика.
Да, это бизнес логика. И эта бизнес логика может быть реализована сотней способов в сотне разных мест. И что самое интересное, все 100 способов будут работать. Только вот в большинстве случаев сопровождать это будет тяжело.


Цитата deil @
Я не хочу давать пользователю моего класса поле для маневров, мне не нужно вообще заморачиваться с его расширяемостью и прочим - моё ПО вообще для варки кофе!
Судя по коду, ты и себе не хочешь оставлять поле для манёвров :-) Про кофе - нонсенс. Программы пишутся одинаково, независимо от кофе, чая или кефира. Есть domain-specific сущности, есть бизнес логика, которая эти сущности связывает - ЭТО решение задачи. А всё что ниже НУ НИКАК не может зависеть от кофе :-)


Цитата deil @
Поэтому я не делаю абстрактный класс Condition, не реализую специфичный My666Condition, который наследуется от и уточняет PropertyCondition. И не трачу время на враппер для своего метода, который подсовывает конкретный объект Condition. И параллельно не нарушаю IoC.
Твой код не показателен, не хватает клиента, которому нужно вычислить факториал (например, алгоритма, которому этот факториал нужен + вызова самого алгоритма).

Автор: deil 24.03.11, 15:04
Цитата
Судя по коду, ты и себе не хочешь оставлять поле для манёвров :-) Про кофе - нонсенс. Программы пишутся одинаково, независимо от кофе, чая или кефира. Есть domain-specific сущности, есть бизнес логика, которая эти сущности связывает - ЭТО решение задачи. А всё что ниже НУ НИКАК не может зависеть от кофе :-)

Мы ведь о другом сейчас беседуем, разве нет? Не вижу смысла задр-ивать на код и пытаться сделать его похожим на умные книжки.

Принцип IoC отличается от твоего определения - ты слишком усложнил и сузил. ПО по твоим критериям несомненно будет отвечать принципу IoC, но окажется слишком замороченным и утонет в количестве никому не нужных сущностей (см. первый абзац). На самом деле критерии более слабые (на самом деле критериев нет, это твоя терминология. Принципу IoC нельзя удовлетворять, потому что это просто подход к проектированию для уменьшения зависимостей между компонентами, а не какое-то свойство системы да\нет). И упомянаются в них (критериях) не все принимаемые объектом решения, а лишь решения по разрешению зависимостей. Логика - сбоку. IoC не заставляет тебя выносить всё подряд в интерфейсы и предусматривать кучу частных случаев, лепить миллионы точек расширения. А ты давишь именно на это зачем-то.

Автор: andyag 24.03.11, 16:43
Цитата deil @
Мы ведь о другом сейчас беседуем, разве нет? Не вижу смысла задр-ивать на код и пытаться сделать его похожим на умные книжки.

Не знаю. К чему ты тогда привёл аргумент:
Цитата deil @
мне не нужно вообще заморачиваться с его расширяемостью и прочим - моё ПО вообще для варки кофе!

?

Цитата deil @
На самом деле критерии более слабые (на самом деле критериев нет, это твоя терминология. Принципу IoC нельзя удовлетворять, потому что это просто

Т.е. как "нет критериев"? А что тогда вообще есть? И что значит "нельзя удовлетворять"? Что в твоём понимании вообще описывает IoC?

Цитата deil @
И упомянаются в них (критериях) не все принимаемые объектом решения, а лишь решения по разрешению зависимостей. Логика - сбоку.

Вот, пошла конкретика. Начнём с того, что ты только что обвинил меня в зауживании, но при этом сам дал ввёл на порядок более узкий критерий. Покажи где ты читаешь этот список критериев? Вот, читаем Фаулера:
Цитата
Inversion of Control is a key part of what makes a framework different to a library. A library is essentially a set of functions that you can call, these days usually organized into classes. Each call does some work and returns control to the client.

A framework embodies some abstract design, with more behavior built in. In order to use it you need to insert your behavior into various places in the framework either by subclassing or by plugging in your own classes. The framework's code then calls your code at these points
Ты обвиняешь меня в "зауживании", при этом Фаулер, расмматривая чисто "библиотеку" и "фреймворк" умудряется утверждать, что речь, вообще говоря, идёт о любых аспектах поведения - я не вижу здесь той конкретики, которую приводишь ты. Откуда она?

Вот Мартина читаем:
Цитата
A. HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW
LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS.
B. ABSTRACTIONS SHOULD NOT DEPEND UPON DETAILS. DETAILS
SHOULD DEPEND UPON ABSTRACTIONS.

Опять не вижу того, что говоришь ты.

Цитата deil @
IoC не заставляет тебя выносить всё подряд в интерфейсы и предусматривать кучу частных случаев, лепить миллионы точек расширения. А ты давишь именно на это зачем-то.
Я ж и не спорю про миллион точек расширения. И тот код, который я тебе показал - это явный оверинжиниринг. Смысл того кода, просто показать суть сказанного. В реальной жизни, естественно, так только педанты пишут.

Автор: deil 25.03.11, 08:08
Более узкий (строгий) критерий - критерий, которому удовлетворяет меньшее число объектов. Перечитай ещё раз свое определение :) Оно более строгое (чуть лишний if и всё).

Про Фаулера: зри в корень! Ты мне цитируешь framework vs library. Да, здесь ни слова о том, о чем я говорю :)

И, соответственно, парой абзацев выше там есть понятное определение IoC (и даже пример кода, который написано в процедурном стиле, нерасширяем никак и вообще не использует ни одной абстракции, а завязан на конкретную реализацию). Я с ним (определением) на 100% согласен.

Автор: andyag 25.03.11, 10:44
Цитата deil @
Более узкий (строгий) критерий - критерий, которому удовлетворяет меньшее число объектов. Перечитай ещё раз свое определение :) Оно более строгое (чуть лишний if и всё).
Теория всегда более строга, чем практика. Если учесть, что любую систему можно поделить на подсистемы огромным числом способов, а при этом каждая подсистема является системой сама по себе, никто не запрещает (и не требует!) применять "принципы" ко всей системе сразу. У меня может быть чудовищная программа, ужасный говнокод, но если её GUI написан на .NET, один из аспектов моей программы будет удовлетворять IoC на все 100%!

Цитата deil @
Про Фаулера: зри в корень! Ты мне цитируешь framework vs library. Да, здесь ни слова о том, о чем я говорю :)
И, соответственно, парой абзацев выше там есть понятное определение IoC
http://martinfowler.com/bliki/InversionOfControl.html
Не увидел определения :-)

Цитата deil @
(и даже пример кода, который написано в процедурном стиле, нерасширяем никак и вообще не использует ни одной абстракции, а завязан на конкретную реализацию).

Хорошо, давай рассмотрим его иллюстрацию:

Типа тут нет IoC:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    puts 'What is your name?'
    name = gets
    process_name(name)
    puts 'What is your quest?'
    quest = gets
    process_quest(quest)

1. Принимается решение о тексте вопросов
2. Принимается решение об использовании консоли для ввода и вывода
3. Принимается решение о том, кто должен обработать ввод пользователя
4. Принимается решение о последовательности вопросов

А тут типа есть:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    require 'tk'
    root = TkRoot.new()
    name_label = TkLabel.new() {text "What is Your Name?"}
    name_label.pack
    name = TkEntry.new(root).pack
    name.bind("FocusOut") {process_name(name)}
    quest_label = TkLabel.new() {text "What is Your Quest?"}
    quest_label.pack
    quest = TkEntry.new(root).pack
    quest.bind("FocusOut") {process_quest(quest)}
    Tk.mainloop()

1. Принимается решение о тексте вопросов
2. Принимается решение об использовании Tk для ввода и вывода
3. Принимается решение о том, кто должен обработать ввод пользователя
4. Не принимается решение о последовательности вопросов

Пункт 1 и в первом, и во втором случае вычёркиваем - одинаково.
Пункт 2 тоже одинаковый, т.к. в обоих случаях речь идёт о специфике UI.
Пункт 3 вычёркиваем - одинаково.
Пункт 4 остаётся: в первом случае у нас вопросы задаются последовательно (есть последовательность, выполнение этого куска кода завершится или если есть 2 ответа, или аварийно), во втором случае всего этого нет - юзер "может" сделать то, о чём его просят, но он не обязан это делать (нет последовательности, кусок кода можно завершить аварийно и неаварийно, гарантии результата нет).

Вот именно 4ый пункт он позиционирует как IoC. Что произошло? В процедурном коде было 4 решения, здесь - 3. Избавились от одного решения, получили IoC (его позиция). Думаю пример не очень хороший, т.к. программы ведут себя абсолютно по-разному - предоставляют пользователю разные возможности (провокация, короче). Но даже в этом случае:
Цитата andyag @
IoC - это принцип построения дизайна таким образом, что любая функциональная единица (модуль, класс, метод) принимает только те решения по поводу своего поведения, которые она не может не принимать.

Если мы сейчас вспомним, что живём в реальном мире, то на 3 одинаковых пункта можно смело закрыть глаза (ну говнокод, хрен с ним). Рассматриваем только 4ый пункт, и (о чудо!) осознаём, что во втором случае код не принимает то решение, которое он не обязан был принимать (решение о последовательности задавания вопросов).

Если рассматривать аспект "порядок задавания вопросов" второго куска кода, IoC в нём выполняется. Если рассмотреть другие аспекты - не выполняется. Он пофиксил всего 1 аспект, а остальные как были говнокодом, так им и остались :-) Если для задачи какой-то аспект - это 90% всей задачи, на остальные аспекты можно закрыть глаза (привязка к конкретному движку UI, захардкоженый текст, и т.д.) - тупо правило 20-80.

Тут просто фишка в том, что изначально он не сказал "вопросы могуть быть заданы/отвечены в любом порядке", а потом вывернул так, будто это подразумевалось. Т.е. речь идёт о том, что первая реализация брала на себя не только принятие решений за какой-то там другой код, но и за самого ПОЛЬЗОВАТЕЛЯ - она "выбрала" за него порядок вопросов.

Цитата deil @
Я с ним (определением) на 100% согласен.

Ну покажи определение-то :-) Ну не вижу я его в этой статье.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)