Осваивая рецепты эффективного развития программного проекта, постарался для себя найти причины, делающие полезным использование принципов развития архитектуры SOLID (статья Как не понимать принципы развития архитектуры SOLID).


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


Стало интересно выполнить анализ применимости этих понятий для общепринятых парадигм программирования, например для ООП. Хорошо, если результат этой работы будет полезен и Вам.


image


На сегодняшний день существует множество подходов для проектирования и последующей реализации программных проектов. Наиболее востребованными в работе с большими программными проектами являются: структурное программирование, функциональное программирование, объектно-ориентированное программирование.


Для меня стал интересен анализ причин возникновения этих подходов проектирования. И в процессе анализа неожиданным открытием стал факт, что все они неявно основываются на следующей предпосылке:


Необходимость планомерного развития проекта 
с минимизацией затрат на повторное написание кода, 
тождественного или сходного с уже существующим.

Развитие программного проекта


Что такое проект без необходимости развития? Такие проекты изредка встречаются и в основном характеризуются быстрой сдельной оплатой без возникновения последующих обязательств со стороны программиста, например:


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

В подобных ситуациях усилия программиста, направленные на поддержание, например, объектно-ориентированного подхода, тратятся впустую. Часто бывает, что я обнаруживаю себя в таком бессмысленном занятии во время разработки одноразовой консольной утилиты, когда вдруг понимаю, что написание текста 4-ого класса в этом проекте задержало меня на 15 минут и к результату не приблизило. Самое печальное, что все классы, с трудом написанные в таких проектах, забываются и не используются повторно, то есть не облегчают нам работу в дальнейшем.


Во всех остальных ситуациях программист, минимизируя свой труд, должен развивать структурно-сложный проект, то есть:


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

Если поискать аналогии развитию программного проекта, то можно вспомнить эволюцию биологического вида.


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

Труд программиста не легок, но у программиста есть "помощник". Этот помощник спрятан где-то глубоко в устройстве нашего мира, в котором есть две особенности:


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

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


Очевидно, что для создания алгоритма требуется выделить признаки, которые обеспечивают применимость алгоритма. Эти признаки необходимо искать во входных данных и в описании начальной ситуации (контекста). Для создания универсального алгоритма необходимо в каждой предметной области, имеющей свои наборы признаков данных и ситуаций, выделить тождественные для всех областей признаки применимости. Все остальные признаки, не обеспечивающие применимость, универсальным алгоритмом игнорируются. Формализуя универсальный алгоритм, мы пришли к необходимости использования абстрагирования — одного из самых важных принципов ООП. При этом для ООП характерен акцент лишь на абстракции данных.


Здесь попробую выписать примеры использования абстрагирования из разных областей.


Абстракция Алгоритмы Область применения
Натуральные числа Алгоритмы количественных расчетов Задачи учёта хозяйственных ценностей
Масса-характеристика материального тела Алгоритмы-операций сравнения количества вещества Задачи сравнения ценности товара, не поддающегося счету
Интерфейс с операциями для коллекции элементов: полный обход, сравнение и обмен позиций Алгоритмы сортировки коллекции Программирование
Интерфейс одинаковых операций для "концевого узла" и "узла ветвления" в дереве Алгоритмы на основе шаблона проектирования "Компоновщик" Разработка сложного программного проекта
Ключевое понятие "Работник" Формулировки в разделе "Трудовой договор" Трудовой кодекс

Строительный блок программного проекта


Используя разные приемы абстракции, программист выполняет реализацию алгоритма в виде участка кода, который представляет собой обособленный и законченный элемент его труда. Этот элемент в зависимости от используемого языка программирования может представлять собой и функцию, и объект, и последовательность инструкций. Назовем для удобства дальнейшего изложения этот фрагмент кода словом "компонент".


Компонент — участок кода (процедура, класс, deployment component и т.д.):


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

Закономерности в развитии программного проекта


С использованием термина компонент появляется возможность сформулировать совокупность простых закономерностей, существующих в разработке программного проекта. Эти закономерности представлю в виде следующих утверждений, разбитых на 3 категории.


  1. Утверждения, описывающие свойства компонента.
    1.1. Корректно написанный компонент обязательно используется и чаще несколько раз.
    1.2. В каждом месте использования компонента от него ожидается неизменное поведение, приводящее к повторяемому результату.
    1.3. При использовании компонента в нескольких местах результат должен удовлетворять каждому месту использования.
    1.4. Поведение, заложенное в компоненте, формирует ограничения для мест использования этого компонента.
    1.5. В каждом месте использования компонента могут быть задействованы все его ограничения.
    1.6. Любое изменение компонента изменяет его ограничения и требует проверки всех мест его использования, что порождает затраты времени программиста.
    1.7. Компонент целесообразно записывать в виде кода в одном экземпляре, то есть необходимо устранять дублирование одинакового кода. Это уменьшит количество правок при внесения изменения в компонент.
  2. Утверждения, описывающие закономерности в реализации программистом новой задачи.
    2.1 Вариант реализации новой задачи целесообразно выбирать минимизируя затраты времени программиста.
    2.2. Для реализации новой задачи программист может добавить новые компоненты или изменить поведения старых компонентов.
    2.3. Добавление компонента в основном требует проверки только в месте нового использования, и порождает минимальные затраты времени программиста.
    2.4. Обусловленное новой задачей изменение поведения компонента, согласно утверждению [1.6], требует проверки в месте нового использования и во всех местах старого использования, что порождает дополнительные затраты времени программиста по сравнению с ситуацией в утверждении [2.3]. В случае опубликованного компонента это требует работы всех программистов, использовавших измененный компонент.
  3. Утверждения, описывающие закономерности во взаимодействии универсальных алгоритмов и их специализаций:
    3.1. Существует возможность написать базовый компонент (название вводится по аналогии с базовым классом и далее для краткости будем использовать слово "база"). База выполняет только самые главные черты некоторого универсального алгоритма.
    3.2. Существует возможность написать компонент-специализацию (далее для краткости будем использовать слово "специализация"). Специализация дополняет универсальный алгоритм базы, делая его применимым в конкретной области использования.
    3.3. База, как следует из утверждений [3.1], [3.2], имеет меньшую сложность и меньше ограничений в применении, чем специализация.
    3.4. Согласно утверждению [1.7] специализацию целесообразно разрабатывать без дублирования кода универсального алгоритма из базы.
    3.5. Места использования базы не требуют проверки после внесения изменений в корректно сформированную специализацию.

Понятия объектно-ориентированного программирования


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


Класс, Объект


Данные понятия ООП закрепляют целесообразность использования специального вида компонента, описываемого совокупностью некоторых внутренних данных и методов работы с этими данными. Все утверждения группы [1] и [2] транслируются в ООП, для которого термин компонент заменяется понятием класс.


При этом, на первый взгляд, отношения класса и объекта исчерпываются группой утверждений [3], в которой база заменяется понятием класс, а реализация — понятием объект. Причем реализация получается динамическая, то есть изменяемая в процессе исполнения программы.


Инкапсуляция


Понятие "инкапсуляция" можно рассмотреть с двух "сторон".


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


Вторая сторона понятия "инкапсуляция" — это сокрытие внутренней реализации компонента. Это сокрытие возможно с использованием понятий база и реализация, описанных в группе утверждений [3]. Для этого публичные методы класса отождествляются с базой, а приватные и защищенные методы класса — с реализацией. В местах использования используются ограничения, формируемые базой, и потому появляется возможность производить изменения в реализации, не касающиеся базовых ограничений. И эти изменения реализации не нужно проверять в местах использования базы [3.5], что обеспечивает минимизацию трудозатрат программиста.


Примечательно, что понятие "инкапсуляция" имеет аналогию в биологии. Этот процесс первой своей стороной схож с биологическими функциями "Клеточной мембраны".


Наследование


Понятия "наследование" продолжает закреплять важность использования связки база + реализация. Для этого в группе утверждений [3] необходимо методы родительского класса отождествить с базой, а методы класса-наследника отождествить с реализацией.


В своей реализации понятие "наследование" позволяет использовать утверждение [2.3], то есть использовать дополнение кода вместо его изменения и дублирования. При этом необходимо исключить дублирование базового алгоритма. Однако, у подхода, использующего наследование для специализации универсального алгоритма, есть существенный минус. Этот недостаток — наличие двух сильно-связных компонентов, которые тяжело изменять независимо. Эти связи-зависимости порождаются отношением родитель-наследник.


Существует множество альтернативных способов использовать связку база + реализация. Приведу далее примеры таких способов.


База Реализация Область применения
Публичные методы класса Приватные методы класса Инкапсуляция
Защищенные методы родительского класса Методы класса-наследника Наследование
Интерфейс динамической библиотеки Функционал динамической библиотеки Компонент=динамическая библиотека
Шаблонные (обобщенные) методы и классы (template, generic) Инстанцирование шаблона с указываемыми аргументами Обобщенное программирование
Универсальные методы, принимающие делегаты Специализация методов указанием конкретных процедур обработки Процедуры сортировки или формирования дерева, с указанием метода оценки порядка элементов
Классы, предусматривающие взаимодействие с шаблоном "Посетитель" Формирование "Посетителя" с требуемым функционалом Шаблон проектирования "Посетитель"
Панель управления АЭС Совокупность автоматики и оборудования АЭС Сокрытие сложности системы от оператора АЭС

Параллельно, подмечаю, что для понятия "наследование" из ООП, так же можно найти аналогию в процессах биологической эволюции. В биологии для этого используется термин "Наследственность".


Полиморфизм


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


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


И на этот раз почти без удивления, нашел аналогию для понятия "полиморфизм" в биологии. Название этого биологического термина полностью совпадает с понятием из ООП. "Полиморфизм" — способность одного организма существовать в состояниях с различной внутренней структурой или в разных внешних формах.


Заключение


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


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


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


Спасибо Вам за внимание.


Отзывы


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


Ссылки



Под редакцией Борисовой М.В.

Комментарии (62)


  1. epishman
    14.04.2019 14:20

    Программирование — это моделирование реальной жизни с помощью IT-абстракций. Если в самой предметной области встречаются высокие абстракции (животное — млекопитающее — собачие — енот), то нужно использовать ООП. Но бывают области, где абстракций особо не выделишь, там ООП применяется достаточно редко, например учетные системы типа 1C, где его до сих пор нет.


    1. t-nick
      14.04.2019 15:31

      Построение абстракций ООП на основе сущностей реального мира — одно из самых частых заблуждений в ООП (вроде бы его привнес кто-то из авторов какого-то учебника). Чтобы развеять это заблуждение, были сформулированы наборы шаблонов (GoF&co) и принципы «правильного» проектирования (SOLID и GRASP).
      ООП создавалось для управления сложностью, разбиения программы на переиспользуемые «кирпичики». Объект в ООП — это набор операций над неким общим состоянием.


      1. Tanner
        14.04.2019 16:01

        Не могли бы вы поподробнее остановиться на

        Построение абстракций ООП на основе сущностей реального мира — одно из самых частых заблуждений в ООП

        Почему это ? заблуждение, кто ещё приходил к этому выводу, что можно почитать на эту тему?

        Я, например, ни разу не видел учебника, который не использовал бы метафор из реального мира, чтобы объяснить принципы ООП. Википедия говорит о “things found in the real world”. В общем, если это и заблуждение, то оно слишком распространено, чтобы отметать его так запросто, вы не думаете?


        1. InterceptorTSK
          14.04.2019 17:03

          Человек что повыше — простой мысли не понимает похоже))

          Построение абстракций ООП на основе сущностей реального мира
          Сущности реального мира и позволяют строить ооп-абстракции. Более того, ооп — это всего лишь подмножество какой-либо логики высшего порядка, а точнее — реализация логики. Но сама логика ооп из ниоткуда не берется же. Кто придумал и исходя из чего полезла логика ооп? Неужели с неба упала?)) Или полезла из математики что ли?

          И он действительно не понимает следующего, на пальцах оно вот так:
          Есть множество X и Y. И есть элемент, принадлежащий множествам X и Y одновременно. Как формализовать сие? Самое простое решение, прямолинейное и тупое «на кванторах и предикатах» — это два равноправных множества X и Y, включающие элемент А оба, и тут говорят конечно же, что эти множества X и Y пересекаются элементом А. Но какому-то чудаку пришло в голову следующее. А что если организовать все это дело по-другому? Если взять Y и сделать подмножеством X, да еще и так, что бы А попадало в оба множества, то получается же совсем другая логика) Элемент А принадлежит Y, но Y — подмножество X, и тогда и получается решение, но другое. Пытливый читатель сразу же увидит тут то, что X и Y можно поменять местами, т.е. X сделать подмножеством Y. А это уже третье решение)

          ТАК КАКАЯ ФОРМАЛИЗАЦИЯ ВЕРНА?

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

          И это всего лишь вершинка айсберга. Маленькая снежинка на макушке. На самом деле все настолько грандиозно, что несведущие «читанув» ооп, и совершенно не поняв откуда что лезет, например то же ооп — и плетут в общем-то всякую фигню… Не обращайте внимания, это нормально. А вы же задали вполне корректный вопрос, по поводу ооп и реального мира. Ответ тут прост. ООП — это всего лишь один из бесконечного кол-ва способов описания реального мира. На самом деле ответ вот такой: ООП — это один из способов реализации подмножества логики высшего порядка.


        1. VolCh
          14.04.2019 18:16
          +1

          Тут не то, что заблуждение, а когнитивное искажение, склонность людей обобщать. Единичные примеры с метафорами из реального мира воспринимаются как универсальное руководство к действию: «проектируй классы и отношения между ними на основе объектов и отношений реального мира».



        1. LAutour
          14.04.2019 21:06

          Я, например, ни разу не видел учебника, который не использовал бы метафор из реального мира, чтобы объяснить принципы ООП

          Я например в таком виде совсем не воспринимаю ООП (возникают вопросы: почему реализуется так, а не иначе). Мне ближе «железный» подход: формы, кнопки, реализация в машинном коде.


      1. muhaa
        14.04.2019 17:05

        Попробую уточнить. Например, если нужно моделировать электрическую сеть (мой привычный объект), то вы никуда не денетесь от иерархий наследования классов типа «проводник->коммутационный аппарат->выключатель» и других подобных или более абстрактных.
        Т.е. мы здесь описываем сущности предметной области как пассивные (не имеющие поведения) наследуемые объекты и снабжаем их функциями, которые позволяют проще получать информацию об этих объектах.
        С другой стороны, не представляю ситуации, в которой эти объекты имело бы смысл снабжать некими функциями, определяющими их поведение. Потому что поведение в разных задачах может быть очень разным и потому что поведение системы сразу станет не предсказуемым и сложным. Это как заставить клеточный автомат сделать нечто задавая правила его работы.
        Для меня в этом случае проще реализовать все требуемые алгоритмы в нескольких крупных объектах, каждый из которых умеет решать некую задачу поверх описанной выше предметной модели объектной области и инкапсулирует требуемые попутно данные. Когда постоянно существующего состояния почти нет, тогда вообще проще — написать все это опираясь на функциональный подход где это просто и скатываясь к процедурному, где с чистыми функциями все слишком сложно.


        1. VolCh
          14.04.2019 18:30

          Т.е. мы здесь описываем сущности предметной области как пассивные (не имеющие поведения) наследуемые объекты

          Тогда вы не используете ООП, оно о данных инкапсулированных с поведением.


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

          Ну как же?! А передаточные функции?


          1. shm-vadim
            14.04.2019 19:41

            ООП о данных инкапсулированных с поведением


            Антисолид. Данные от логики управления ими лучше отделять.


            1. VolCh
              15.04.2019 11:07

              Антисолид — разделять тесно связанные вещи. У объекта с функциями без данных будет две отвественности: собственно функции и контроль данных. На любой чих с данными, нужно менять два объекта.


            1. rjhdby
              15.04.2019 12:55

              Это где же в SOLID такое сказано?

              А вот GRASP, например, прямо совсем с вами не согласен.

              Ответственность должна быть назначена тому, кто владеет максимумом необходимой информации для исполнения


              1. shm-vadim
                15.04.2019 14:51

                Принцип единственной ответственности (The Single Responsibility Principle)
                Каждый класс выполняет лишь одну задачу.


                Т.е. должна быть сущность, хранящее состояние и сервис, который ею манипулирует.


                1. rjhdby
                  15.04.2019 14:59

                  Хранение собственных свойств — это не ответственность, а неотъемлемая суть объекта, характеризующая его состояние.

                  Таким образом, с сильной натяжкой, можно рассуждать лишь от DTO, да и то это скорее искусственный конструкт нежели полноценный объект в понятиях ООП.


                  1. shm-vadim
                    15.04.2019 16:40
                    -2

                    А чего ему не хватает до «полноценности»? Общение с БД, файловой системой и отправка http запросов? Как бы это, в принципе, могло выглядеть, не нарушая принцип единственной ответственности?


                    1. VolCh
                      15.04.2019 22:57

                      Поведения, зависимого от состояния. DTO по сути просто структура данных, без поведения, ну иногда простейшие чистые преобразования типа форматирования.


          1. muhaa
            14.04.2019 22:14

            Боюсь, что не понимаю что значит здесь «передаточные функции». Возможно речь идет о ситуации, когда доменного объекта, выполняется некая функция, которая соответствует реальному воздействию на реальный объект. В ответ объект меняет свое состояние.
            Для меня это не работает.
            Во-первых, скорее всего это будет не сам доменный объект, а некое его расширение, заточенное под конкретный способ моделирования (физически существующие объекты можно моделировать множеством способов).
            Во-вторых, в общем случае моделируется система, состоящая из множества доменных объектов в комплексе и моделирование не сводится просто к имитации поведения объектов (могут быть настройки, несколько итераций и т.д.).
            Поэтому удобно извлечь информацию из доменных объектов, преобразовать ее в более абстрактный вид (матрицы, векторы), и обрабатывать воздействие алгоритмом, который получает на входе абстрактное описание всей системы, заточенное под конкретный алгоритм.
            Бывают ситуации, когда доменный объект — это не настоящий физический объект а нечто витруальное — намерение кого-то сделать что-то, акт получения информации и др. С такими объектами я сталкивался реже, но по-моему все-равно проще написать алгоритм, который получая на входе доменную модель и воздействие возвращает новую доменную модель, чем упаковать то же самое в алгоритмы внутри доменных классов.
            В общем, кто знает другие примеры из реальной жизни, где в доменные объекты действительно удобно инкапсулировать поведение, интересно было бы узнать.
            Кстати, выше сказанное не относится к объектам в играх или UI. С ними ООП работает получше.


            1. VolCh
              15.04.2019 11:15
              -1

              Сейчас не хочется лезть в закоулки памяти, где что-то от ТОЭ осталось, но грубо, передаточные функции:
              — идеального проводника: что на входе, то и на выходе
              — резистора: напряжение на выходе ниже напряжения на входе
              — конденсатора: сдвиг по фазе
              — индуктивности: сдвиг по фазе в другую сторону

              В общем же случае, если у вас в объекте только данные (ну или данные и тупые гетеры/сеттеры), то это и не объект, а просто структура данных (struct в C, record в Pascal и т. д.)


          1. mayorovp
            15.04.2019 07:59

            Передаточные функции определены для четырехполюсников, а не для индивидуальных элементов.


            1. muhaa
              15.04.2019 09:50
              -1

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


        1. mayorovp
          15.04.2019 07:58

          Наследование у вас странное. Не может выключатель наследоваться от проводника, это будет нарушать LSP.


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


          1. muhaa
            15.04.2019 10:15
            -1

            Наследование у вас странное. Не может выключатель наследоваться от проводника, это будет нарушать LSP.
            Это не я придумал и это даже стандарт. Они там наверно много сломали копий. Пусть это будет не проводник, а некий элемент оборудования, способный нести нагрузку в электрической системе. В любом случае, пока эти объекты доменной модели используются только для того чтобы описывать электрическую сеть как она есть, никаких проблем не возникает (хотя это еще не совсем ООП в его классическом понимании).
            Однако, задачи построения этой системы или задача визуализации схемы вполне решаются методами ООП.
            Что касается визуального представление объекта электрической сети, то да, там получается почки классический ООП, хотя это уже не доменные объекты. Что касается построения, не знаю. Когда делал графические редакторы для моделей мне было проще выносить поведение в отдельные объекты-контроллеры, по одному контроллеру на каждый аспект поведения класса объектов. Как можно изящно запихнуть поведение прямо в объекты не представляю.


            1. InterceptorTSK
              15.04.2019 11:09

              Это не я придумал и это даже стандарт. Они там наверно много сломали копий. Пусть это будет не проводник, а некий элемент оборудования, способный нести нагрузку в электрической системе. В любом случае, пока эти объекты доменной модели используются только для того чтобы описывать электрическую сеть как она есть, никаких проблем не возникает (хотя это еще не совсем ООП в его классическом понимании).
              Конечно родительская абстракция у выключателя — это элемент оборудования. Это же логично) А знаете почему? Потому что например интерфейсы этих абстракций будут логичными. Если вы абстракции «завяжете» не логично — у вас интерфейсы логикой потекут сразу же. Что есть абстракция? Это абстракция с абстрактной абстракцией [что называют интерфейсом].

              Наследование у вас странное. Не может выключатель наследоваться от проводника, это будет нарушать LSP.
              Конечно странное. Имеем:

              Проводник: ИнтерфейсАбстракцииПереключения-able
              Выключатель: Проводник
              Имеем явную логическую бредятину)

              Должно быть вот так:
              ЭлементОборудования: ИнтерфейсАбстракцииПереключения-able
              Выключатель: ЭлементОборудования

              В первом случае все потекло, во втором уже лучше. Заметьте, реализацией вообще никто не занимается.
              Суть: На первом этапе проектирования абстракций постройте абстракции так, что бы они максимально отражали реальность.
              Суть2: Наступает сильное понимание того, что абстракции в электрике чрезвычайно не прибраны логикой) И да, в итоге реальных абстракций может не хватить. Это одна из главных проблем ооп.

              А теперь нужны электрики, которые внятно достроят дерево электрических абстракций полностью) Зачем оно нужно? Что бы не париться программистам. Тем более что программисты строя дерево абстракций за электрика — явно занимаются не своей работой… Понимаете масштаб проблемы?

              пс: Если вы не можете на бумашке построить дерево абстракций той системы которую вы описываете — даже и не думайте ее программировать) Смысл же отсутствует тогда совершенно. Вы что программировать собрались-то?
              Следствие: Если вы на бумашке не можете собрать логику абстракций, то любое безумное программирование такой логики заранее обречено на провал.


              1. VolCh
                15.04.2019 11:21

                > Суть: На первом этапе проектирования абстракций постройте абстракции так, что бы они максимально отражали реальность.

                Не так, Нужно строить абстракции так, чтобы они максимально отражали важные для нашей задачи аспекты реальности. Для задачи учёта электрооборудования вообще может быть не важно Переключения-able оно или нет. :), а для моделирования переходных процессов в цепи — неважно, что золото на порядке дороже аллюминия.


                1. muhaa
                  15.04.2019 12:02

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


                  1. VolCh
                    15.04.2019 12:36

                    Есть такая практика, как выделение контекстов и «дублирование» в них моделей сущностей реального мира, моделирование разных аспектов реальности.

                    А возникает скорее не потребность, а соблазн. :)


                    1. muhaa
                      15.04.2019 12:51

                      Примерно так оно и бывает. При изменениях одни и те же объекты вносятся отдельно в ГИС, расчетную модель, модель для диспетчерских задач, базу паспортизации оборудования разными людьми. Используются сложные процедуры конвертации и сравнения данных между разными моделями. Получается куча работы для дата-инженеров, нестыковки в данных и так далее. Из всего что я видел, в меру монолитная модель выглядела совсем не худшим решением.


                1. InterceptorTSK
                  15.04.2019 13:07

                  Что важно а что нет? Сегодня важно одно, завтра — другое. Вы себя искусственно ограничиваете некоей важностью. При этом — разве электрические абстракции имеют сей интерфейс важности?) Если имеют — так прикрутите и его))) Логично? А как вы его прикрутите?

                  важные для нашей задачи аспекты реальности
                  Реальность — это реальность. В реальности или абстракции есть — или их нет. И выходит так, что если вы описываете абстракции реальности — то вы их описываете, и они у вас описаны. Другого не будет. Другое — это от лукавого. Всякая непонятная «важность» и т.д.
                  Ваша мысль про важность приводит еще и к следующему. Недоописав максимально полное дерево абстракций — вы получите не полную логику абстракций. Надеетесь на везение? Ну надейтесь…

                  Попробуем дописать то что выше
                  ЭлементОборудования: ИнтерфейсАбстракцииПереключения-able
                  Выключатель: ЭлементОборудования

                  И допишем вот так:
                  Проводник: ИнтерфейсАбстракцииПереключенияПроводника-able
                  ЭлементОборудования: Проводник, ИнтерфейсАбстракцииПереключенияОборудования-able
                  Выключатель: ЭлементОборудования
                  При этом, абстракция проводника должна быть в собственной логической либе, абстракция элемента обрудования в другой — это например позволит построить принципиальную схему проводников и их переключений, без гориллы и африки впридачу. Перепишем:
                  Либа1.Проводник: ИнтерфейсАбстракцииПереключенияПроводника-able
                  Либа2.ЭлементОборудования: Проводник, ИнтерфейсАбстракцииПереключенияОборудования-able
                  Либа2.Выключатель: ЭлементОборудования
                  И т.д.
                  Естественно не нужно оное воспринимать как готовое) Ибо я над этим думал аж целых 30 секунд… Такие вещи как архитектура абстракций разрабатываются чуть ли не месяцами, а иногда и годами. Берите и дописывайте оное. И проверяйте на логику. Чем больше допишете — тем меньше логических дыр вы получите в будущем. Максимальное из возможных описаний системы приведет к полному отсутствию логических дыр. При этом, т.к. логика абстракций позволяет выпилить ненужное абсолютно безболезненно, не участвующее в логике ПО, то выпилите оное) Или оставьте пустым, потом допишете реализацию «выпиленных» абстракций.
                  Пример переписанного выше позволяет это. Несколько абстракций я могу точно оставить недописанными. Но на общую логику оно не повлияет никак, и в будущем может быть дописано. Недописанное повлияет на реализацию, но не на логику. И это самое главное. Смысл ооп в этом и заключается. Напишите так, что бы ваши абстракции были максимально логичными. Если вы напишете по-другому — ну так ваши абстракции развалятся, и получите вы в итоге что-то другое, но не ооп.


                  1. VolCh
                    16.04.2019 09:32

                    > Вы себя искусственно ограничиваете некоей важностью.

                    А вы не ограничиваете? Классическую квантовую механику применяете или теорию струн? Или всё же, субатомные взаимодействия вам не важны и вы ограничивается атомарным, а то и молекулярным уровнем?


                    1. InterceptorTSK
                      16.04.2019 12:46

                      А вы не ограничиваете?
                      Нет) Еще раз написать? Хорошо, напишу. На абстракции — абстракция важности не распространяется никак. С информационной точки зрения — нет) А если и распространяется — то абстракция важности должна быть прикручена. Другого нет, не было и не будет.

                      Квантовые вещи тоже можно прикрутить, абстрактно на бумашке. Почему нет-то? Другое дело что это вряд ли возможно, т.к. физические абстракции мне не доводилось сводить в кучу. Но если потребуется — я и физиков из-под земли достану, раздам задачи и в итоге получу то что нужно.


                      1. VolCh
                        16.04.2019 15:00

                        > Абстрагирование — отвлечение в процессе познания от несущественных сторон, свойств, связей объекта (предмета или явления) с целью выделения их существенных, закономерных признаков.

                        У вас какое-то другое понимание слов «абстракция», «абстрагирование»?


              1. muhaa
                15.04.2019 11:42

                Понимаете масштаб проблемы?
                Да, и наблюдаю как эта проблема решается с 1995. Работает это примерно так. Электрики, которые немного программисты, описывают модель как она есть, чтобы ее можно было как-то вменяемо представить дата-инженерам предприятий и стараясь далеко не уходить от UML-ок, нарисованных в стандартах International Electrotechnical Commission. Потом программисты, которые немного электрики устраняют в модели явные косяки. Потом программист, который решает очередную прикладную задачу формирует по этой модели некие структуры данных, которые ему нужны для решения этой задачи. Или в простом случае работает прямо по модели. Или чаще — работает по своим структурам, обращаясь к модели для получения каких-то деталей. В обоих случаях доменная модель — это по сути статичное описание электрической сети с непосредственным доступом к каждому атрибуту без лишних абстракций.


            1. mayorovp
              15.04.2019 11:53

              Стандарт описывает онтологию электросхем, то есть набор терминов, а не иерархию классов. Когда вы выстраиваете иерархию классов в соответствии со стандартом онтологии, вы как раз и попадаете в описанную выше ловушку:


              Построение абстракций ООП на основе сущностей реального мира — одно из самых частых заблуждений в ООП


              1. muhaa
                15.04.2019 12:22

                Это стандарт Common Information Model. Он рисуется в UML и может быть прямо конвертирован в классы. Некоторые части этой модели неплохо продуманы создателями стандарта во всех отношениях, некоторые довольно невнятны. Вообще говоря, этот стандарт скорее предназначен для обмена данными между системами разных разработчиков, чем для описания доменной модели. Но учитывая, что какие именно задачи будут решаться с использованием этой модели никто толком не знает, как-то получается, что ничего умнее разработчики которых я видел не придумывали.


                1. mayorovp
                  15.04.2019 12:50

                  Если он может быть прямо конвертирован в классы — это ещё не означает что его нужно прямо конвертировать в классы.


      1. epishman
        15.04.2019 11:28

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


        1. t-nick
          15.04.2019 11:56

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


          1. epishman
            15.04.2019 13:50

            Неприятие ООП для меня загадка. Это так же странно, как отказ от исключений в Go. Похоже, у человечества бывают временами помутнения, но в итоге совершенные паттерны вроде наследования не могут умереть.


            1. VolCh
              16.04.2019 09:34

              Есть задачи, которые хорошо на ООП ложатся, а есть которые плохо. Да и факт того, что в ООП-языки вводятся элементы того же ФП говорит о том, что и создатели языков это понимают.


  1. InterceptorTSK
    14.04.2019 15:07

    Но бывают области, где абстракций особо не выделишь
    Брехня. Значит никто не стремится их выделять. Классификацию Линнея кто придумал до Линнея?)) Вот ровоно так же и с 1с — что бы появилось ооп — нужно придумать абстракции пригодные для логики ооп, в рамках решаемых задач 1с, но т.к. в 1с нет и не было вменяемых архитекторов абстракций — задача проектирования абстракций и не была реализована, а впрочем она даже и не ставилась.


    1. epishman
      15.04.2019 11:31

      Согласен, ведь в западных ERP наследование используется в полный рост.


  1. Tzimie
    14.04.2019 15:52

    В современном мире с большим количеством средних программистов объектно ориентированный код с фабриками фабрик фабрик фабрик не менее write-only, чем код на Perl.



  1. AN3333
    14.04.2019 16:17

    Все утверждения группы [1] и [2] транслируются в ООП, для которого термин компонент заменяется понятием класс.


    Вот и вы путаете класс и объект.
    Объектно ориентируемое программирование создавалось для моделирования объектов.
    Классы же хоть и были изначально, однако как удобство, деталь реализации. Но зачем читать что пишет автор. Давайте посмотрим на реализацию, и так все понятно.


  1. a-tk
    14.04.2019 16:19

    Шаблоны, внезапно появившиеся к концу статьи в таблице, совершенно ортогональны объектно-ориентированному программированию, на которое делается упор. Они не про наследование, они — одна из форм полиморфизма.
    Вообще про полиморфизм мне в своё время понравилось обсуждение на RSDN, копия поста доступна здесь — gaperton.livejournal.com/15121.html


  1. muhaa
    14.04.2019 17:49
    +2

    Однажды в начале моей карьеры программиста, коллега, имеющий опыт разработки на java взялся написать модуль для решения довольно простой задачи: сбора телемеханической информации с устройств согласно настраиваемому расписанию и по запросам.
    Он сделал это в классическом для java стиле ООП и утверждал, что у него там все очень хорошо продумано и при внесении изменений достаточно будет просто заменить несколько классов при инициализации. И ему это действительно удавалось.
    Позже мне захотелось разобраться как работает этот модуль. Я залез в код и увидел кучу фабрик, визиторов, интерфейсов и прочего. Очень быстро я почувствовал, что сложность всего этого на порядок превосходит возможности моего мозга и бросил эту затею, решив что работа с чужим кодом мне не по зубам.
    Позже в другой компании я снова столкнулся с кодом других программистов. Этот код был почти процедурным с очень крупными объектами. И здесь картина была противоположной: через 5 минут просмотра кода я уже понимал что здесь нужно добавить и что переделать. Дальше я менял код, и если это был не мой компонент, просил у ответственного сделать ревью моих изменений.
    До сих пор не могут понять что такое этот суровый java-ООП с патернами и SOLID — может это коллективное заблуждение, может это попытка установить любые правила разработки (какие-то лучше никаких), может это фильтр, позволяющий не подпускать к проекту недостаточно одаренных и опытных, может я не вижу какого-то очень важного фактора…


    1. Graf54r
      14.04.2019 18:08

      вы до сих пор пишите в процедурном стиле?


      1. muhaa
        14.04.2019 23:07

        Не знаю как это назвать. В последних проектах это статичная доменная модель и объекты, которые развертываются при старте сервиса и отвечают за решение различных задач: подписка и получение событий от внешних источников, обработка запросов, обработка данных модели (необходимая для конечных алгоритмов), сами алгоритмы, написанные в стиле близком к функциональному. Собственно, если бы не требовалось кэширование данных и промежуточных результатов (для ускорения работы) то можно было бы все выполнить на почти чистых static функциях (это C#) и выглядело бы еще проще.
        Как использовать здесь больше объектного программирования и не утонуть в трудностях для меня не ясно.


    1. megahertz
      14.04.2019 20:57
      +1

      Оба подхода имеют право на жизнь. Если второй подход хорошо работает на проекте — такой стиль предпочтительнее. Но зачастую для крупных проектов, особенно с размытыми и изменчивыми требованиями без первого стиля не обойтись. При нарушении SOLID абстракции начинают течь еще до запуска первой версии. Изменения в одной части ломают другие подсистемы, вносить серьезные правки становится очень затратно, ну и прочие радости.


    1. dim2r
      15.04.2019 00:12

      Еще в ООП нет простого копирования экземпляра класса.

      class Animal
      class Cat(Animal)

      Animal a1 = new Cat();
      Animal a2 = copy of a1;

      Казалось бы что может быть проще? Но тут начинается трип в махровый мир ООП. Каких только решений тут не встречал. От нудной реализации Clonable во всех субклассах, до развесистой рефлексии и хитрой сериализации-десериализации.


      1. symbix
        15.04.2019 06:14

        А при чем тут ООП вообще? Это особенности конкретного языка программирования.


        Как будто на обычном С обычные структуры с вложенными указателями на другие структуры было бы проще склонировать.


        1. dim2r
          15.04.2019 09:04
          -1

          Элементарная операция должна быть в стандарте. С помощью её можно сделать более сложные копирования. В языке С можно или просто присваивать струткуры друг другу или memcpy() вызывать. Но в ООП пошли сложным путем потому что создатели концепций просто мало программировали и мало читали чужого кода. У меня в проекте 2ГБ исходников. Самая нечитаемая часть сделана на ООП, в том числе с привлечением COM, DCOM, ActiveScript. Сделано вроде правильно, но работает зачастую неправильно. :(


          1. mayorovp
            15.04.2019 11:58

            В языке С можно или просто присваивать струткуры друг другу или memcpy() вызывать. Но в ООП пошли сложным путем...

            Если вы пишете ООП на Си — вы всё еще можете присваивать объекты друг другу. В чём проблема-то?


      1. VolCh
        15.04.2019 11:23

        $a1 = new Cat();
        $a2 = clone $a1;

        :)


    1. symbix
      15.04.2019 06:19

      Два варианта.
      Первый — вам действительно тогда не хватило мозгов, второй — там было сделано сложнее, чем оно того требует.


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


    1. epishman
      15.04.2019 13:54

      Дело не в ООП, а в использовании паттернов из учебников, половина которых суть банальность, а вторая половина — избыточность.


    1. jetcar
      16.04.2019 08:24

      наверно проблема не в стиле, а в конкретном коде, если понимать принципы построения ООП обьектов то там изменения вносяться так же легко, а если код не понятен то дело не в ООП, а в том кто это всё написал, из процедурного помоему проще налепить макарон и это не раз видел, с ООП тоже никто не мешает, но рефакторить потом это проще


  1. t-nick
    14.04.2019 19:09

    Сущности реального мира проявляются в ООП только в виде ограниченных абстракций — объектов доменной области, которые составляют, условно, 1% от общего количества объектов программы. Так как для работы с объектами доменной области приходится создавать объекты-помощники, которые сами по себе с доменной областью не связаны (см. Pure fabrication), и именно поэтому их легко переиспользовать в других частях программы или вообще в другом проекте.

    Новички в ООП часто прямо описывают все поведение в объекте доменной области и получают god object, который невозможно переиспользовать и сложно сопровождать.

    Ещё одна проблема возникает при включении объекта в окружение. В этом случае часто возникает неопределенность ответственности при описании взаимодействия двух и более объектов. Например, при столкновении двух объектов типа «мяч», у какого из объектов вызвать метод, мяч1 ударить мяч2 или мяч2 ударить мяч1?


    1. funca
      14.04.2019 23:14

      Вы хотите получить банан, но кроме банана в нагрузку получаете гориллу, и все чертовы джунгли!


  1. funca
    14.04.2019 23:33

    Правильное ООП, вместо инкапсуляции наследования полиморфизмов (вы что издеваетесь?), должно заставлять разработчиков производить сразу всем понятные проектные метрики: сроки, прибыль, зряплату и все такое. ;)


  1. panchmp
    15.04.2019 03:38

    а чего три волшебных слова?
    если их 4ре: abstraction, encapsulation, inheritance, and polymorphism


    1. dopusteam
      15.04.2019 07:03

      4етыре