Эволюция понимания ООП

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

ООП прежде всего, инструмент


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

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

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

Если провести некую аналогию, то для меня это сродни кросс-платформенным средам разработки. Например, тот же .NET. Не важно, на каком языке я пишу — C#, VB или какой-то другой, я знаю, что мой код скомпилится в промежуточный. Так вот, представьте, что я, как программист использую C#, а мой заказчик говорит свои требования на VB. В такой ситуации ООП служит тем самым преобразователем, который помогает сойтись мне и заказчику, быть на одной волне (любят они фразу — to be on the same page). И вот тут открывается ключевая особенность, которая была не заметна раньше. ООП — это инструмент. Моя модель, которую я реализую у себя в коде — это инструмент. Она не обязательно будет сохраняться полностью, если мол я создал объект, поиграл с ним, изменяя его внутреннее состояние, получил от него какой-то выхлоп и теперь могу спокойно его выкинуть в мусорку. Она как отвертка, которая помогает в работе, как таблички экселя, которые я открываю, произвожу рассчеты и закрываю без сохранения. Это именно инструмент в работе с данными, а не целостная сохраняемая модель. Очень часто в командах разработчиков разделены люди, занимающиеся базами данных и люди, работающие над непосредственно кодом, моделью и пользовательским интерфейсом. Я вовсе не ратую за то, что эти команды надо разделять, я наоборот приветствую совместную работу. Так вот, если не привязываться к данным, то рефакторинг своей модели выглядит как усовершенствование нашего инструмента для работы с этим набором данных. Это все равно, что выпустить новую улучшенную дрель или новое сверло, чтобы сверлить более толстые стены.

Действие важнее того, кто его совершил


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

Задача рассчетов мыслями объектника


Не удержав себя от сравнения процедурного и объектно-ориентированного подхода, я задался простым вопросом — где бы я мог использовать процедурный подход, вместо объектного? Если честно, то я не знаю такие области, в которых ООП явно проигрывало функциональному подходу по тем или иным параметрам. Одна вещь, которая вертится на языке, все же, есть — это какие-нибудь калькуляции, сложные рассчеты, алгоритмы. Давайте возьмем пример, когда нам необходимо выполнить рассчет величины по каким-то сложным правилам. Я не знаю, как бы действовал человек, который проповедует процедурный стиль, но мысли объектника я понять могу. Пунктом первым, я бы разбил эту калькуляцию на смысловые этапы, например, подсчет вот этого коэффициента, потом этого и потом финальный. Каждый этап калькуляции — это объект, который будет участвовать во всем процессе. Для общения между шагами, чтобы дать им возможность обмениваться данными, создадим еще один вспомогательный объект, который будет хранить в себе все промежуточные рассчеты. Затем объеденим эти объекты в единую цепочку, опишем один единственный метод, который будет возвращать необходимое значение и все. Такой код будет удобно тестировать, его комфортно читать, в нем не трудно разбираться, но есть одно Но. Нужно иметь небольшой сдвиг в своем понимании, что объект — это не обязательно какое-то существительное из реального мира, это может быть еще и действие. А вот это уже становится сложнее и явно не в пользу ООП. Определить грань, где в качестве объектов используются объекты реального мира или какие-то понятия, а где объекты могут быть действиями, весьма непросто. Да и нужно приложить некое усилие, чтобы понять, как будет выглядить метод у такого объекта. Получается, поведение у какого-то действия, а это уже за гранью русского языка. Из такого моего понимания вещей я делаю вывод, что ООП действительно не самый лучший выбор для данной задачи, но лишь потому, что он может быть не всегда прост и прозрачен в понимании.
Поделиться публикацией

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

    +8
    ООП — это инструмент и философия. А еще это музыка программирования. :) Хотя мои ощущения с того времени немного сместились, поскольку я изучил функциональное программирование.

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

    Вы здесь несколько путаете читателей. Функциональный и процедурный подход — разные вещи.
      +2
      Да, спасибо, про путаницу в процедурном и функциональном подходе мне уже объяснили. Дело в том, что я не большой специалист в области функционального программирования, поэтому и спутал. Изначально, наверное, не стоило подходить к сравнению таких вещей.

      Есть в планах задачка, которую хочется попробовать сделать, применив функциональный подход. Тогда, наверное, и к ООП отношение поменяется вновь
        +5
        На самом деле, если код пишется хорошо, в нем функциональщина появляется сама собой. И это возникакет одинаково часто как при процедурном подходе, так и при ООП-подходе. Очень интересны с этой точки зрения шаблоны проектирования. Они оперируют терминами ООП (классы, взаимодействие), а реализуют вполне функциональный подход. Хотя, конечно, не в чистом виде.
      +6
      Все хорошо, но вот тут:
      сравнения процедурного и объектно-ориентированного подхода, я задался простым вопросом — где бы я мог использовать процедурный подход, вместо объектного? Если честно, то я не знаю такие области, в которых ООП явно проигрывало функциональному подходу

      Процедурный (императивный) и фнукциональный — совершенно разные подходы к программированию. Не мешайте их в одну кучу, пожалуйста.
        +2
        Тем неменее, не лишним будет напомнить читателю, что ООП, ФП и императивная парадигмы в некоторых языках вполне себе сосуществуют. Например, в OCaml, F#, Lisp.

        Но я почему-то в некоторой степени пурист в плане парадигм.
          –2
          От себя хочу добавить, что у абстрактных типов данных (АТД) какого-нибудь Haskell больше общего с классами ООП, чем различий. Подробнее об этом можно прочитать здесь.

          И да, забавно слышать рассуждения автора о различиях ФП и ООП, если он путает ФП с процедурным программирование.
            –1
            Learn You a Haskell For Great Good: Type Classes 102:

            Remember that type classes have nothing to do with classes in languages like Java or Python. This confuses many people, so I want you to forget everything you know about classes in imperative languages right now!
              0
              да, классы типов в Haskell и классы в ООП — разные понятия. А кто сказал, что классы типов и АТД — одно и то же?
        +10
        > Каждый этап калькуляции — это объект, который будет участвовать во всем процессе.
        Тут то есть проблема. Объекты и классы придуманы для удобной связи состояния и поведения с поддержкой сокрытия первого. Когда нет состояния (как в случае если объект представляет собой шаг вычислений) все ОО конструкции теряют смысл. Остаётся только ритуал создания объекта не имеющий ничего общего с задачей.

        > Для общения между шагами, чтобы дать им возможность обмениваться данными, создадим еще один вспомогательный объект, который будет хранить в себе все промежуточные рассчеты.
        На самом деле в подавляющем большинстве языков такой объект уже создан. Называется стэк :)
          –1
          Тут то есть проблема. Объекты и классы придуманы для удобной связи состояния и поведения с поддержкой сокрытия первого. Когда нет состояния (как в случае если объект представляет собой шаг вычислений) все ОО конструкции теряют смысл. Остаётся только ритуал создания объекта не имеющий ничего общего с задачей.

          А это уже не ООП, а технология использования языка. Как пример — паттерн DAO.

          На самом деле в подавляющем большинстве языков такой объект уже создан. Называется стэк :)

          Если это про стек вызовов, то использовать его в некоторых ситуациях чудовищно неудобно.
          +2
          Вы очень хорошо в конце написали про объекты-действия. Такие объекты еще известны как сервисы. По сути дела, это обычные процедуры. По моему опыту в них обычно заключается почти вся логика верхнего уровня. И по крайней мере половина, а то и 80, или даже 90 процентов вообще всего проекта (в зависимости от «богатости» домена на сложные концептуальные сущности). Поэтому я стал осторожнее относится к напыщенным заявлениям про объектную ориентированность.
            0
            На самом деле злоупотребление всякими сервисами превращает доменную модель в классы с набором геттеров и сеттеров. Она не несет в себе никакой смысл, ее трудно понимать, напрмер, нельзя сказать, что же можно сделать с этим объектом, а что нет. Есть даже термин такой — anemic domain model. У Фаулера есть интересная статейка об этом
              +1
              Про ADM я в курсе :) Я тут как то писал статью про RDM. Кстати, есть и полностью противоположный изъян: наделение сущностей предметной области поведением, не относящимся на прямую к ним. В итоге увеличивается связанность, уменьшается инкапсуляция. Впрочем, ADM на практике не так страшна, как ее любят описывать. Что бы понять, что можно сделать с сущностью Foo как правило достаточно глянуть на список методов сервиса FooService :)
                0
                Как раз, чтобы программисту развиваться — ему надо совершать ошибки. Пусть лучше добавит лишней ответственности классу и потом на этом научится, чем будет просто писать сервисы.

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

                  Боюсь он не поймет, что шибся ни в первом, ни во втором случае :)

                  >В дополнение к минусам сервисов — неудобно писать тесты. Неизвестно, какие правила создания объектов и можно создать невалидный объект и подпихнуть его сервису.

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

                  0
                  Проблему ADM против «наделение сущностей предметной области поведением, не относящимся на прямую к ним» может решить парадигма DCI (http://en.wikipedia.org/wiki/Data,_Context_and_Interaction).
                  Если кратко, то следует задуматься о том, что кроме самих объектов есть ещё роли, которые они играют и с точки зрения программирования выгодно разделить объект предметной области (не несущий функционала специфичного для юз кейсов программы) и роли (которые уже несут специфичный функционал).Это не единственная и не главная идея DCI, там есть ещё несколько клёвых, например о явном выделении шагов алгоритма (юз кейса), за что часто ругают ооп (вот у нас есть много объектов со многимим методами, а как понять когда и как они используются).
              0
              Сейчас все больше пытаюсь закопаться в объектно-ориентированное программирование, но никак не могу нормально представить, как решается такая простая задача через ООП, как следующая (такие задачи приходиться решать очень часто по ходу работы). Скажите ваше мнение как её элегантно решить через ООП?

              Дано: список Работников, у класса Работник есть свойство Дети, у ребёнка есть поле Возраст и Пол.

              На новый год начальство решило сделать подарки детям до 14 лет. От нас требуется составить список детей работников (группируем по полу, т. к. подарки должны быть разные). Учитывать, что на фирме может работать оба родителя, но подарок должен быть один.

              Очень хотел бы взглянуть на вашу версию реализации данной задачи. Ко мне в голову приходят десятки классов, особенно учитывая принципы SOLID и т. п. Хотя как я узнал — через «функциональщину» это решается парой функций.
                0
                Ну никто не мешает переложить те же пару функций на пару методов сервиса. Вот у меня что получилось: pastie.org/2599906
                  +1
                  При чем тут ООП?

                  Где эти данные хранятся, в памяти всегда (на Smalltolke пописываете)? Если в базе, то посмотрите в сторону en.wikipedia.org/wiki/Active_record_pattern или martinfowler.com/eaaCatalog/dataMapper.html

                  Если вас интересует реализация какого-то языка запросов на объектах, посмотрите как это было реализовано в Hibernate или github.com/rails/arel. Согласен все эти gt, lt выглядят чужеродно, но не стоит забывать, что это надстройки над декларативным SQL (диктовалось требованиями совместимости с интерфесом).
                    +3
                    Это задача проще всего решается на SQL ИМХО.
                      +4
                      Коллекция (список, массив) объектов Ребёнок, объекты Работник имеют свойство Дети — список или массив ссылок на объекты Ребёнок из первой коллекции.

                      Формируем пустые массивы или списки Мальчики и Девочки.
                      Идем по списку Работники, далее по списку Дети конкретного работника, проверяем возраст и, если подходит, то в зависимости от пола помещаем его в список Мальчики или Девочки, если его там ещё нет.

                      Главное использовать агрегацию, а не композицию. Объект Работник должен ссылаться на объект Ребёнок, а не содержать его.
                        0
                        *помещаем ссылку на него в список Мальчики или девочки, если её ещё там нет.
                          0
                          Ваш коммент почему-то наводит на мысли о тяжелом 1Сном детстве :)
                            +4
                            Детство у меня было на ASM-8080 и BASIC
                              +3
                              Вот не пойму за что минусуют. Вроде задачу решил, или нужно приводить работающий код? Или мой алгоритм избыточен или сильно не оптимален?
                                +2
                                Не пытайся понять, проще винду на z80 портировать чем понять за что минусуют… =)
                                  0
                                  Просто все эти Объект, Коллекция, Работники... на русском языке и с большой буквы. Я об этом, а не об алгоритме :)

                                  Самому в юности приходилось НА 1C кое-чего писать. Незабываемо!
                                    0
                                    Коллекция — в начале предложения была, а Работник и Ребёнок скопипащено из «ТЗ».
                              –2
                              Как по мне, здесь достаточно 4 классов: Работник, Ребенок и две колекции для работы с двумя предыдущими классами. Список детей тут будет узнать проще простого: если ребенок есть в базе (он не сирота ведь) — значит родители работают на предприятии. Просто выбираем коллекцией детей всех детей до 14 лет, берем у них свойство родитель и даем родителю подарок для соответствующего пола. Т.е. плящем именно от ребенка. Конкретная реализация всего этого зависит от языка и фреймворка на котором вы будете это писать.
                                +2
                                Можно попробовать взглянуть на проблему используя те идеи, что предлагает автор статьи и попытаться выделить ключевые концепции, помня, что объектами могут быть не только некие существительные, но и процессы/действия.

                                Получим примерно такой список сущностей с которыми нам придётся оперировать:

                                — самое очевидное это: Сотрудник, Ребёнок;

                                — раз мы дарим подарки то нам надо их как-то обозначить, соответсвенно появляется — Подарок;

                                — так как нам надо выбирать подарок для ребёнка основываясь на каких то правилах, которые могут в том числе и поменяться, то надо бы изолировать способ выбора подарка в отдельную сущность, например, ВыбиралкаПодарка. Всё что она делает — получает на вход Ребёнка и возвращает для него Подарок;

                                — так как нам надо как-то связать Подарок и Ребёнка, то появится, например, сущность ПодарочныйСертификат;

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

                                — но так как в чистой теории Подарки выбираются не из воздуха, то появляется некое ХранилищеПодарков из которого этот самый РаспределительПодарков будет таскать Подарки и скорее всего надо бы учесть что Подарки иногда могут и закончиться :(

                                — сам по себе процесс выбора Подарка с помощью ВыбиралкиПодарков и последующее получение Подарка из ХранилищаПодарков с последующей выдачей этого Подарка Ребёнку (т.е. созданием ПодарочногоСертификата) тоже весьма хороший кандидат на выделение в отдельную сущность, назовём его мммм… ВыдавательПодарочныхСертификатов :)

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

                                Понятное дело что результатом работы РаспределителяПодарков будет список ПодарочныхСертификатов. Если надо сгенерировать только список то ХранилищеПодарков можно выкинуть.

                                Вроде всё :)
                                  0
                                  Вы делаете распространенную и грубую ошибку — объектное моделирование далеко не сводится к выписыванию все сколь-нибудь важных существительных и глаголов. Это во-первых. Ну и во вторых вашу модель нужно бы пропустить через принцип YAGNI.
                                    +2
                                    Вот это я никогда не понимал, получается, если выделять таким образом сущности, выделять ответственности конкретные ответственности и т. п. То в итоге мы приходим к тому, что у нас есть множество классов, которые содержат по одному методу (без какого-либо состояния) (например: ВыбиралкаПодарка, ВыдавательПодарочныхСертификатов, РаспределителяПодарков). Но для чего тогда нужен сам класс, почему нельзя просто создать отдельный метод, который назвать ВыбратьПодарок, ВыдатьПодарочныйСертификат, РаспределитьПодарки. Если нужно деление — делите на мелкие функции, внутри этой задачи (метода), зачем их выносить отдельно на тот же уровень абстракции, что и сущности, причем чаще всего такие вспомогательные методы больше никто и никогда не будет использовать (они очень и помечаются как private). Если нужно группировать такие функции, используйте Модули, Namespace'ы и т. п.

                                    Чем такой подход — создание виртуального существительного для реализации глагола, лучше чем создания отдельного глагола? Ведь нам не важно, кто или что раздает подарки, нам важен алгоритм отбора и раздачи подарков, зачем его прятать за виртуальными сущностями?
                                      0
                                      Между методом и ВыбратьПодарок и объектом ВыбиралкаПодарка разница в том, что возможна некоторая иерархия выбиралок. Но применив принцип YAGNI получаем. что нам как раз методов и достаточно. Браво. Мы опять пришли к процедурному программированию.
                                        +2
                                        Вот и выходит, что ходим во круг колодца по одной и той же тропинке, только названия другие.
                                        0
                                        хорошо, допустим мы сделаем так, как вы предлагаете, и оставим всё в одном классе но разобьём всё на кучку методов. Что вы будете делать когда вас попросят раздавать подарки не только на Новый Год но и, допустим, на Рождество и выбирать надо будет по абсолютно разным критериям?

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

                                          0
                                          >Что вы будете делать когда вас попросят раздавать подарки не только на Новый Год но и, допустим, на Рождество и выбирать надо будет по абсолютно разным критериям?

                                          Рефакторинг «extract class», затем «extract interface» и поехали.

                                          >что это часть какой-то большой системы которая претендует пережить один запуск.

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

                                            Но больше всего мне нравиться то, почему используют ООП, обычно эта фраза произноситься с легким пафосом, прям в лицо стоящему напротив человеку: «ООП позволяет реализовать задачу в разы проще!» и обычно добавляется: «Ты что не видишь, что ли?!». А там лес классов, тысячи взаимосвязей, но зато очень низкая связанность сущностей, как будто мы в день делаем тысячи приложений, в которых надо выдавать подарки и обязательно, прям завтра, используем повторно наш тщательно написанный сегодня код.
                                        0
                                        Не соглашусь. Пока просто не соглашусь. Перед тем как вступать в дискуссию было бы очень интересно послушать по каким причинам всё это не удовлетворяет принципу YAGNI. И предлагаю начать с ВыбиралкиПодарка. Также было бы очень интересно если бы вы провели параллели с SOLID.

                                          0
                                          В общем по поводу моего решения.

                                          Если мы действительно решаем одноразовую задачу получить список и ничего более, то формально я соглашусь с тем что предложил VolCh. И на этом всё, делайте потом с этим списком что хотите и как хотите ;)

                                          Если же мы пытаемся решать какую то более сложную задачу являющуюся частью более сложной системы то я всё же использовал бы более обобщенное решение. Хотя тоже проще чем то, что я предложил изначально.

                                          я бы разбил задачу на две части.
                                          1. Выборка детей из списка по некоему критерию. Тут возможно несколько решений, но я всё же вынес бы тот код который принимает решение о том попал ребёнок в список или нет в какую-то внешнюю сущность.
                                          2. Пробежаться по полученному списку и раздать Подарки. Предыдущие оппоненты действительно убедили меня, что я слишком мало знаю о задаче, потому, на самом деле, делайте с этим результирующим списком что угодно :)

                                          Ну и опять же, при желании оба этапа можно объединить :)

                                          Дальнейшие рассуждения можно делать только получив больше информации.

                                          Но, как по мне, единственное о чём это говорит, так это то, что я зря взялся выдавать какие-то решения имея столько общую постановку :) и ни разу не является аргументом в споре «все остальные» vs ООП (это в основном к выводу «Мы опять пришли к процедурному программированию»)
                                            0
                                            Я тоже соглашусь с решением VolCh. Не нужно писать абстрактных коней в вакууме. Код нужно усложнять по мере необходимости, а не про запас. Вот как нам представят систему, где такое решение не кактит, переделаем на новое. Рефакторинг + тесты = залог успешной разработки.
                                        +1
                                        Вот наворотили-то, в задании сказано «список детей до 14 лет, сгруппированных по полу».
                                          –1
                                          ну и ещё одно ограничение вы забыли, как и многие кто предлагает решить это простым sql запросом ;) просили не выдавать два подарка ребёнку если оба его родителя являются сотрудниками. Это конечно скорее всего всё ещё решается в терминах sql, но я бы уже не стал назвать это простыми запросами.
                                            +1
                                            В моём алгоритме выше это учтено. Я к тому, что все эти классы, кроме Работник и Ребёнок (плюс их коллекции) излишни для сформулированной задачи. Есть работники, есть их дети, нужно получить список детей младше 14 лет, сгруппированных по полу без повторений. Никаких подарков и, тем более, сертификатов программа выдавать не должна. Налицо вольная трактовка ТЗ и не затребованная «заказчиком» функциональность.
                                              0
                                              если мы рассматриваем одноразовое решение одноразовой задачи в результате которой получаем список показанный на мониторе, например, то никаких проблем, обходимся процедурным подходом.
                                                +1
                                                Просили показать, как решить конкретную задачу в рамках ООП — я показал. И да, по сути, процедурный подход к решению задачи (алгоритму), только он оперирует объектами (хотя могли быть, например, структуры или ассоциативные массивы).
                                              +1
                                              select * from children where age <= 14 order by sex
                                                –1
                                                distinct забыли
                                                  +2
                                                  ну если у него все дети в одной таблице то они там итак уникальные, по полной аналогии с вашим решением
                                          +2
                                          Cделать один select в две строчки, скопировать результат в письмо, отправить начальству.
                                            0
                                            Если вам нужно решить задачу, пользуйтесь тем инструментом, который больше всего подходит. Тут — SQL. А если действительно понадобится ООП (по причинам, которые у вас не указаны), нужно планировать структуру классов под решаемые задачи (как в комментарии ghostWhite выше), как написано в последней части статьи. Разумеется, скорость и удобство разработки будут ниже, но в некоторых случаях бывает оправданно написать один класс, который решает серию похожих задач, чем кучу однострочников, раскиданных по всей программе.
                                              0
                                              Если данные из базы — используйте SQL, однозначно.
                                              Он как раз предназначен для выборок.

                                              Если же данные из внешнего источника (не БД), то используйте hash set для списка детей и обход всех детей (предлагается использовать итератор — абстрагируемся от структуры хранения).
                                              Насчет обоих родителей. В задаче необходимо уточнить, как идентифицируются дети.
                                                0
                                                ООП здесь будет прослеживаться в:
                                                • Абстрагировании алгоритма составления списка от структуры хранения исходных данных
                                                • Использование объекта, работающего по принципу hash set, независимо от его внутренней реализации
                                                • Работать напрямую с массивами неудобно — придется заранее вычислять размер. Проще сделать оценку сверху и использовать абстракцию списка


                                                Все это можно реализовать и с использованием процедурного подхода, но в ООП оно ложится естественным образом.
                                                  +1
                                                  >Работать напрямую с массивами неудобно — придется заранее вычислять размер.

                                                  Зависит от языка.
                                                    0
                                                    Да, условие немного расплывчато, хотя довольно четко написано. Есть список родителей, у них поле Дети, у Ребёнка есть такие-то свойства. И как мне кажется, не правильно менять условие задачи, чтобы она подходила под реализацию через SQL или hash set'ы. Редко бывает, что мы можем в готовой системе что-то поменять, а тут требовалась именно доработать.

                                                    Мне именно интересен подход ООП, так как после прочтения разных методик написания (SOLID, YAGNI), у меня вышла каша из десятков классов. Хотя задача в том же C#, решается простым LINQ выражением. Каюсь, задачу намеренно усложнил, чтобы было нагляднее видно сложность работы с наборами данных при помощи ООП.

                                                    ГотовыеСписки = Работники
                                                    .SelectMany(работник=>работник.дети)
                                                    .Where(детё=>детё.Возраст <= 12)
                                                    .Distinct()
                                                    .GroupBy(детё=>детё.Пол)

                                                    Console.WriteLine(string.Join("\r\n", ГотовыеСписки[Мальчик].Select(детё=>детё.Имя));
                                                    Console.WriteLine(string.Join("\r\n", ГотовыеСписки[Девочка].Select(детё=>детё.Имя));

                                                    Самое интересное, что это не псевдокод, это вполне рабочий код, который НЕ НУЖНО реиспользовать где-нибудь, т. к. его значительно проще написать там, где нужно ещё раз. Это как раз вариант нарушения всех принципов ООП: для чего нужно писать сложный универсальный код, когда можно написать красивый идеально подходящий именно для этой задачи.

                                                    У LINQ есть ещё один плюс, вы почти (!!!) можете не думать о том, где лежат у вас данные, это может быть база, сервис или объекты.
                                                      +1
                                                      1. Этот код вполне объектен. 2. ООП — это не антипод YAGNI.

                                                      Ваш код как раз пример хорошего решения задачи в стиле ООП. Если такой код понадобится в нескольких местах, просто выделится сервис («объект-действие»).
                                                        0
                                                        С одной стороны да, с другой по ООП (по крайне мере, которому меня учили): я должен был как минимум создать класс фильтр (чтобы его можно было заменять), реализовать класс группировки и т. п. Здесь нет этого — для многих это не ООП.
                                                          0
                                                          Просто за вас уже тут реализованы фильтры и т.д. Только наверняка ленивые и вообще очень фичастые.
                                                            0
                                                            под фильтром понимаю код «детё.Возраст <= 12», по уму это ведь должен был быть отдельный класс с реализацией интерфейса IFilter, либо на худой конец язык запросов (подобие nHibernate). А тут функция — и всё.
                                                              0
                                                              Нужен этот класс зависит вовсе не от того использовать ООП или нет. А от того, требуется ли передавать несколько разных «фильтров». YAGNI опять же. И чем функция не фильтр? Принимали бы ее как аргумент и подставляли бы сразу в .Where. Функция тоже объект. Не нужно думать, что ООП — клепаем класс на каждый чих, да еще этот каждый чих снабжаем интерфейсом.

                                                            0
                                                            ООП не заключается в том, чтобы под каждый чих создавать объект.
                                                            0
                                                            На функциональном языке код будет выглядеть почти так же, только вместо обращения к методам SelectMany,Where и т. п. Будут, что-то типа Seq.Iter, Seq.Filter, Seq.Distinct

                                                            И в ФЯ не будет ниодного объекта или класса :) хотя не знаю последовательности в функциональном программировании считаются объектами или нет? — получается вроде не ООП.
                                                              0
                                                              Функции можно рассматривать как объекты. Про это в SICP написано.
                                                      0
                                                      грязное грязное решение=)
                                                      www.ideone.com/LKz5m
                                                      +1
                                                      Если честно, то я не знаю такие области, в которых ООП явно проигрывало функциональному подходу по тем или иным параметрам.

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

                                                        Почему объектно-ориентированное программирование провалилось?
                                                          0
                                                          да-да, знакомые материалы.
                                                          В ответ на это есть статья с противоположным названием Objects have not failed
                                                          0
                                                          > Вы никогда не будете писать класс, а потом думать, чем бы ему заняться. То есть, цепочка всегда начинается от поведения.

                                                          В Common Lisp это понять проще, т.к. методы (глаголы) внешние по отношению к классам (существительным) и им не принадлежат. В большинстве случаев вы прямо в коде будете сначала писать методы, а потом классы с полями.
                                                            +8
                                                            >Вы никогда не будете писать класс, а потом думать, чем бы ему заняться. То есть, цепочка всегда начинается от поведения.

                                                            Извините, не удержался :):

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

                                                            (с) Степанов (автор STL)

                                                              +2
                                                              Абзац про мое несогласие с тем, что в ООП начинают с классов как раз и вызван этим отрывком
                                                                +3
                                                                Ещё один прекрасный пример того, что любой человек может ошибаться.
                                                                  +5
                                                                  Только когда найден набор подходящих доказательств, лишь тогда на этой основе выводится аксиома.

                                                                  А как же вот это:
                                                                  Аксио́ма (др.-греч. ἀξίωμα — утверждение, положение) — утверждение, принимаемое истинным без доказательств, и которое в последующем служит «фундаментом» для построения доказательств в рамках какой-либо теории, дисциплины и т.д.
                                                                    0
                                                                    Это то, что получается в итоге. Сам процесс нахождения аксиом имеет экспериментальный характер. К примеру вот что написано в вики про аксиоматику евклидовой геометрии: «В 1899 году Гильберт предложил первую достаточно строгую аксиоматику евклидовой геометрии». Т.е. понадобилось 2200 лет, что бы найти набор аксиом, который действительно является фундаментом евклидовой геометрии.
                                                                      0
                                                                      Разве этот процесс называют доказательством?
                                                                        0
                                                                        Хз как правильно называется — я не математик. Наверное процесс создания теории.
                                                                          0
                                                                          Вы правы, это не доказательство.
                                                                          Тут обратный процесс, дедуктивный.
                                                                    +4
                                                                    Вот многие в комментариях приписывают ООП такие эпитеты как «философия» и.т.д. Проснитесь люди.

                                                                    ООП это лишь очередной уровень программирования.
                                                                    Как от машинных кодов (одномерное, так сказать порядковое исполнение инструкций, только с переходами назад-вперёд) пришло процедурное (так называемое двухмерное) программирование (когда переходы стали делаться в отдельные процедуры которые можно повторно использовать в разных проектах) точно также пришло и ООП. Просто программирование стало ещё на уровень выше. Теперь можно написать процедуру которая вызовет 10 процедур каждая из которых вызовет по 20 процедур и так на любую глубину. Это было возможно и при процедурном программировании, но разница лишь в том что теперь от этого не кипят мозги и это под силу сделать быстро и наделать меньше ошибок… Это просто очередной уровень абстракции… Так сказать очередной шаг в эволюции программирования, позволяющий держать в голове меньше мусора, и сосредоточиться на логике приложения.
                                                                      +1
                                                                      Честно говоря на один целый уровень в ООП маловато концепций. Настоящим рывком в программировании было осознавание вреда стихийного программирования и переход к модульному подходу. ООП, да, усовершенствовало такой подход введя такие полезные вещи, как инкапсуляция состояния. Стоит помнить и об альтернативном способе повышения надежности — отказ от изменяемого состояния вообще (функциональное программирование).
                                                                        0
                                                                        Почему инкапсуляция состояния всегда приписывается ООП, разве в модулях нельзя было создать скрытые переменные, которые доступны только этому модулю?
                                                                        Вообще к ООП обычно приписывают горы дополнительных возможностей:
                                                                        — абстракция — как будто функция не может абстрагировать (или модуль);
                                                                        — инкапсуляция — попробуйте получить доступ к локальным переменным функции;
                                                                        — полиморфизм — почему я не могу написать функцию использующую более общую функцию, а реализацию подставить во время выполнения;

                                                                        Единственное, по моему мнению (где-то читал, но к сожалению источник указать не смогу, вроде у Вирта, про его Оберон): ООП дало нам наследование, но при этом в каждой умной книжке, которая учит нас писать идеальный код, обязательно будет упоминание об ограниченном использовании наследования (обычно сводиться к тому, что не более двух уровней, иногда — только реализация интерфейсов). Так как глубокое наследование это ад для отлова ошибок (особенно если не соблюдать разных принципов, типа Лискоу(в)).
                                                                          0
                                                                          Повторное использование кода, и прочие фишки приписываемые сугубо ООП можно было делать и до его появления. Просто некоторые из них с появлением ООП стало делать проще и быстрее. Не это ли естественный шаг программирования?
                                                                            +1
                                                                            А стало ли? Было бы интересно сравнить… Я перешел с Pascal/Delphi на C#, и не очень понял, чем стало удобнее. C# идеальный язык, мне он нравится на много больше, но не ООП, а как раз наоборот: функциональные штучки (LINQ, лямбды, делегаты, анонимные классы), свое библиотекой, где-то синтаксисом и т. п. Но не как не ООП, не понимаю, для чего меня заставляют писать static class, если мне нужна функция?
                                                                              –1
                                                                              Стало проще для тех кто понял фишку… и стало труднее кто забивает гвозди микроскопом.

                                                                              Был код

                                                                              echo "hello world"

                                                                              Для «hello world» больше и не надо (на этом и споры «ООП vs процедурка») однако если мы пишем приложение посложнее можно сделать

                                                                              echo "hello" ? something() : something_else()
                                                                              retrun 0

                                                                              function something() { return "World" }
                                                                              function something_else() { return "Wife" }


                                                                              И можно пойти дальше, и в сложных проектах нужно идти дальше, например

                                                                              echo = new (Russian)
                                                                              echo->hello->world

                                                                              class hello
                                                                              class goodby
                                                                              class fuck_you

                                                                              class world
                                                                              class baby
                                                                              class bitch


                                                                              НО
                                                                              Если ты пишешь «hello world» в классическом его виде, то да. ООП это никому не нужный онанизм…
                                                                            +1
                                                                            Потому что путается ооп с «модульностью объектов с состояниями», потому что банда четырех модно и на каждом собеседовании паттерны спрашивают, а, например, sicp — нет.
                                                                              0
                                                                              В большинстве языков модуль нельзя передать параметром в функцию/процедуру.
                                                                                0
                                                                                Ну это очень редко нужно, передавайте функцию, словарь или список функций. Если ваш язык не позволяет и такого делать, то это недоработка языка.
                                                                                0
                                                                                Вы путаете «классы» с ООП.

                                                                                >
                                                                                "— абстракция — как будто функция не может абстрагировать (или модуль);
                                                                                — инкапсуляция — попробуйте получить доступ к локальным переменным функции;
                                                                                — полиморфизм — почему я не могу написать функцию использующую более общую функцию, а реализацию подставить во время выполнения;"

                                                                                Все правильно, и вы как раз описали объектный подход, реализованный на функциях. В SICP примерно так, на замыканиях, и вводят понятие объектов. В программировании очень сложно порой ответить на вопрос что чем является, так как используя один базис, можно построить другой и рассуждать уже на этом уровне. Можно на функциях сделать объекты, а можно на объектах функции.
                                                                            –1
                                                                            Странный опус и не менее странные комментарии. Извините, но каша в головах полная.

                                                                            Я в 25 лет научился качественно косить сено. Но я не требую, что косили сено другие и чтобы еще косили качественно.

                                                                            Если у Вас не получается или Вы что-то не понимаете это не значит, что у других тоже самое.
                                                                              +1
                                                                              А автор разве написал, что он что-то не понимает?
                                                                                +1
                                                                                А я вроде нигде не говорил о том, что другие подходы не работают. Я пытался поделиться тем опытом, что у меня есть, и если хоть одному человеку он будет полезен, я буду рад
                                                                                –1
                                                                                >>где бы я мог использовать процедурный подход, вместо объектного
                                                                                Там, где важна скорость выполнения, и временные затраты на поиск нужной функции в иерархии классов неприемлимы — системное программирование.

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

                                                                                  ООП != Классы. Например код ядра линукса очень даже объектный. Там широко используется основная концепций ООП — связывание данных и методов.
                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                    +2
                                                                                    ООП головного мозга. Терминальная стадия.
                                                                                      +2
                                                                                      вообще ниочем
                                                                                        +2
                                                                                        коммент был в корень топика, пардон
                                                                                      +2
                                                                                        0
                                                                                        Не надо путать ооп (с наследованием и прочим) и фп (с комбинаторами и прочим)-языки и способы декомпозиции на объекты и потоки соответственно. Если первое описывает язык и способ кодирования, то второе намного важнее с более абстрактной точки зрения и от языка зависит слабее. Например, на С можно закодировать как с имитацией ооп, так и с имитацией датафлоу, хотя язык процедурный (и противопоставляя объектную декомпозицию поцедурному программированию, автор сравнивает теплое с мягким). Для примера советую посмотреть исходники игры на си (quake), с программой, которая активно пользуется библиотеками типа blas. Наличие классов в коде ни о чем не говорит — в stl тоже классы, но это не ооп.
                                                                                        Адепты ооп часто пытаются расчленить предметную область на объекты там, где этого не надо, получая гигантские конструкции где достаточно sql-запроса, как в обсуждении выше.тственно. Если первое описывает язык и способ кодирования, то второе намного важнее с более абстрактной точки зрения и от языка зависит слабее. Например, на С можно закодировать как с имитацией ооп, так и с имитацией датафлоу, хотя язык процедурный (и противопоставляя объектную декомпозицию поцедурному программированию, автор сравнивает теплое с мягким). Для примера советую посмотреть исходники игры на си (quake), с программой, которая активно пользуется библиотеками типа blas. Наличие классов в коде ни о чем не говорит — в stl тоже классы, но это не ооп.
                                                                                        Адепты ооп часто пытаются расчленить предметную область на объекты там, где этого не надо, получая гигантские конструкции где достаточно sql-запроса, как в обсуждении выше.
                                                                                          0
                                                                                          Следующими шагами для вас будет видимо осознание абстрактизации и полиморфизма. По вашему посту не прослеживается ясное понимание этих вещей.
                                                                                            +2
                                                                                            цели показать свое понимание абстракции и полиморфизма не было, поэтому и не прослеживается
                                                                                              0
                                                                                              Но это же основополагающие вещи. Если они не нужны для решения задачи, то действительно иногда проще 15 строк процедурного кода написать чем 3 класса.
                                                                                                0
                                                                                                Конечно, я с этим полностью согласен. Применение ООП только ради ООП может привести к усложнению и самой системы, так еще и взаимоотношения с коллегами. Параноидального отношения вообще стоит избегать к любым вещам.

                                                                                                Но возвращаясь к вопросу о полиморфизме и абстракции, еще раз повторю — цели такой не было. Здесь описано именно то, что менялось в моем понимании. Понимание абстракции как было, так и осталось. С полиморфизмом та же штука
                                                                                            +1
                                                                                            ООП это хорошо… но только бы не «ООП ради ООП»…
                                                                                              0
                                                                                              OMG, где все программисты? )
                                                                                              Читаем внимательно On Understanding Data Abstraction
                                                                                                +1
                                                                                                Ещё одна маленькая задача которую удалось решить без ООП. Да я используя классы коллекций, да внутри много ООП, которое позволяет мне делать такие вещи. Но почему получается, чем меньше ООП снаружи, тем проще мой код?

                                                                                                Regex reg = new Regex(@«src=»"/media/posters/(?[0-9_a-zA-Z\-]*?)\.thumbnail\.jpg""", RegexOptions.Singleline);

                                                                                                Enumerable.Range(1, int.MaxValue)
                                                                                                .Select(page => new WebClient().DownloadString(string.Format(«demotivators.ru/?page={0}», page)))
                                                                                                .Select(html => reg.Matches(html))
                                                                                                .SelectMany(_ => _.Cast())
                                                                                                .Select(_ => _.Groups[«url»].Value)
                                                                                                .AsParallel().WithDegreeOfParallelism(40)
                                                                                                .Select(name => new
                                                                                                {
                                                                                                name,
                                                                                                url = string.Format(«demotivators.ru//media/posters/{0}.jpg», name),
                                                                                                path = string.Format(@«D:\temp\dem\{0}.jpg», name)
                                                                                                })
                                                                                                .Where(image => !File.Exists(image.path))
                                                                                                .Select(image => new { image.name, image.url, image.path, data = new WebClient().DownloadData(image.url) })
                                                                                                .ForAll(image => File.WriteAllBytes(image.path, image.data));

                                                                                                (извиняюсь, нету кармы, поэтому не могу оформить красиво код).

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

                                                                                                (пробовал другие варианты, но обычно очень сложно заставить код скачивать следующую страницу не после того, как на разобранной странице скачены все картинки, а сразу по освобождению выделенного потока)
                                                                                                  0
                                                                                                  сорри не удалось отказаться от Regex, но это только потому что, мне не удалось завести код Regex.Matches(строка, шаблон).
                                                                                                    0
                                                                                                    >Но почему получается, чем меньше ООП снаружи, тем проще мой код?

                                                                                                    Так в том одно из основных свойств ООП и заключается, что инкапсулируя код внутри объектов мы получаем простой код снаружи.
                                                                                                      +1
                                                                                                      Если я перепишу на ООП, то получится примерно тоже самое, что и у вас :) Я введу понятие функций, монад и т.д. ООП — это способ моделирования, который можно использовать и для мета-моделирования, т.е. для реализации других способов моделирования, в том числе и ФП. Не стоит еще забывать и логику исчисления предикатов и программирование в ограничениях. А еще акторную модель…

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

                                                                                                    Самое читаемое