Как стать автором
Обновить

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

Более того, объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C# и т.д.
Я не сильно большой теоретик, но в контексте данной фразы, мне кажется, не совсем верно определено понятие инкапсуляции. В случае getters/setters и properties важно отсутствие прямого доступа к полям класса, что оставляет вам свободу менять внутреннюю структуру класса и getter-а как угодно.
Если вы уберёте из класса Person поле name, то оставлять метод getName() тоже не имеет смысла. Да, бывают случаи, когда поле исчезает, а свойство остаётся(например, меняется тип поля — вы хранили дату в поле типа Long, а теперь решили использовать Date), но это единичные случаи. Да и даже в них лучше изменить пользовательский код соответствующим образом, чем держать в объекте свойство для поля, которого нет.
У вас может быть ORM прокси для класса Person, которая кроме сохранения name может делать кучу других вещей (сохранять состояния для возможности последующего обновления в источник данных). Так что метод getName() это уже не поле.
У вас может быть просто прокси класс, который не всегда обращается к реальному (по сути ORM класс и есть прокси) или ограничивает доступ, что то логирует, внутри set\get могут генерироваться события об изменениях, у вас может быть декоратор, который «преобразует» свойство в get методе перед тем, как его отдать (ну не знаю… добавляет обращение Sir перед Name ;). В конце концов у вас уже может быть класс *PrettyMan* из другой сборки, который не очень соответствует текущему описанию *Person* и вы напишете для него адаптер.
В общем и целом, простое свойство Name может быть реализовано ой как сложно (у нас же делать все легко не принято ;). Не смотря на все это — с точки зрения вызывающего кода вы просто меняете или получаете свойство Name, и в реальности, за счет инкапсуляции, вы не знаете что и как реально происходит внутри (сколько реально классов используется внутри, если можно так сказать), оно вам и не нужно, вам нужно только само свойство Name (все остальное может резко измениться после).
Я, в общем-то, и не говорил, что доступ через методы — это плохо. Я говорил о том, что сам факт открытия внутренней структуры противоречит описанным идеям ООП. Все эти примеры с триггерами, ORM и т.д. имеют место быть, но я не о них: вот типичная ситуация — создаём объект для переноски данных, якобы следуя парадигме ООП закрываем все поля методами и тут же даём полный доступ к ним через эти самые методы. Вся структура наружу, все данные наружу. Но при этом всё это якобы не противоречит идеям ООП.
>Но при этом всё это якобы не противоречит идеям ООП.
Да, не противоречит, потому что мы даём доступ к данным не напрямую, а через функции. И, в случае надобности можем поменять внутренности класа, не трогая его интерфейс.
> «создаём объект для переноски данных»

Не путайте хорошо описанную в стиле ООП сущность предметной области (Domain Entity) и объекты для переноски данных (DTO).

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

Бегом читать Марка Симанна про инкапсуляцию, да и вообще всю серию про Poka-yoke Design. Это лучшее описание смысла всех столпов ООП простым языком и с примерами.

Очень крутая мысль насчет того, что на границах, приложения не объектно-ориентированные.

DTO — это всего лишь представление кусочка данных, который был отображен в (mapped into) объектно-ориентированный язык.

DTO не нарушают инкапсуляцию, потому что они попросту не объекты.
Не позволяйте вашим инструментам обманывать вас. .NET Framework очень-очень хочет, чтобы вы думали, что DTO — это объекты.

> .NET Framework очень-очень хочет, чтобы вы думали, что DTO — это объекты.

Ну вот не только .NET, но и многочисленная литература по ООП :)

За ссылки спасибо, чуть позже почитаю.
Прочитал, ещё раз спасибо. В принципе, и Симман, и Бейли, на которого Симман ссылается, описывают те же идеи, которые использую в своей работе и я. Однако, ни моя, ни даже их терминология не является общепринятой в мире ООП. Бейли называет сокрытие информации инкапсуляцией, я использую более общий термин — поддержание согласованности объекта. Мне кажется, это более точный и понятный термин, чем «заключение в капсулу». Симман говорит, что DTO — это не объекты, а значит на них не распространяется инкапсуляция. Мне кажется диким говорить, что некий объект в объктно-ориентированном языке — это не объект. Бейли использует чуть более мягкую формулировку: «there are times when simple data structures – classes that have nothing more than properties to get and set data – are necessary. However, these are not representative of encapsulation». Однако, как вы можете видеть из обсуждения, многие понимают под инкапсуляцией именно getters/setters объектов DTO (читать как: натягивают понятие инкапсуляции туда, где оно не показательно). Это разброс терминологии, вызванный (а может и вызвавший) отсутствием чёткого определения и строгих принципов. О чём, собственно, и статья.
Извините что влезу, но…

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

DTO — не обладают поведением, ток свойствами. Отсюда получается что DTO и объект и нет. Почему нет? Потому что он какой то херовый объект. С другой стороны, используя определенный уровень абстракции, это объект. Т.к. под него вам один хрен придется выделять память и очищать ее потом. Вот если уровень абстракции будет такой, то объект. Т.к. обладает поведением выделить память и очистить. Поведение? Безусловно да. Свойства есть? Да. Объект? Объект.
Согласен со всем, кроме поведения DTO. Под поведением обычно, всё-таки, понимается логическое поведение, а не детали реализации. Поэтому поведением DTO не обладает, только свойствами. С «и объект, и нет» — прекрасный пример недостаточно проработанной терминологии.
А я не соглашусь. Т.к. один из принципов ООП является Абстракция. На разных уровнях абстракции все по разному. Даже свойство объекта может превратится в объект.
Если речь о DTO, то и не нужно. Свойство может быть DTO другого типа.
Просто смотря что вы пишите. Если я пишу подсистему управления DTO объектами, то уже извините, но выделение и очистка памяти есть именно логическое поведение в заданных рамках абстракции.
Можно пример? А то у нас как-то уже слишком абстактно получается :)
Вы знаете, но ведь конструктор тоже можно представить в виде поведения? Особенно не дефолтовый конструктор. Это как рождение чего то. А какая нить фабрика мне рожает мои DTO. Которые для меня это мои любимые коробки с данными. :) Которые можно разрушить вызвав у них деструктор. Ну безусловно есть проблемы, но не зацикливайтесь. Дайте свободу вашим мыслям. :) Только так вы прочувствуете ООП. :) :)
Вы о чем? DTO создается, с помощью ключевого слова new (или частенько AutoMapper-ом) для одной конкретной цели — передать данные на другой уровень/слой приложения. Зачем там фабрики?
Ну этот пример говно, не спорю. :)
Или еще на определенном уровне абстракции есть объекты не обладающие поведением. Допустим письмо. Объект? Да. Использую как DTO? Да. Где проблема? :)
А самой по себе в этом проблемы нет. Проблема в том, что если вы делаете это повсеместно — вы не используете средства самого ООП-языка для предостережения вас от невалидных состояний объекта.
Не понял, если честно, при чем тут это?
Пример с письмом. В рамках моей абстракции это объект, который передает данные между слоями. Он содержит лишь одно свойство текст, на которое не накладывается ограничений, кроме как тип свойства и возможности языка по работе с этим типом. Не содержит поведения. Т.к. с письмом могут работать ток другие объекты, само письмо, в рамках моей абстракции, не умеет ничего. И использую его как исключительно DTO между слоями. Да, вы сейчас начнете говорить что это просто совпадение, что объект предметной области является DTO объектом. Но я вам гарантирую, что это зависит лишь от используемых мною абстракций.
Тут уже зависит от того, какие абстракции и как вы используете. Но DTO у меня обычно имеет только одну роль. Даже просто в соответствии с SRP. И даже если у меня один объект domain-модели выглядит как DTO, то не факт, что он для этого (это, скорее, Value-Object, в более широком смысле, чем DTO).
Я сам отнюдь не всегда следую «правильному» ОО-дизайну, на самом деле. Но когда речь заходит о DTO — то я даже не пытаюсь. Марк Симанн об этом отлично написал.
Более того. DTO — это паттерн, а не конкретная реализация. Так что я не понимаю почему DTO не может являться объектом в рамках терминов ООП.
Именно, DTO — паттерн, роль объекта/структуры. Фишка в том, что он в первую очередь DTO, а уже потом, возможно, объект, созданный средствами ОО-языка.
Отвечу на все тут.

«Именно, DTO — паттерн, роль объекта/структуры. Фишка в том, что он в первую очередь DTO, а уже потом, возможно, объект, созданный средствами ОО-языка.» — в первую очередь это часть разрабатываемой и проектируемой мною системы. И я не могу рассматривать объект моей системы, как нечто чуждое моей системе. Этот объект DTO есть некая абстракция объекта, используемого в моей системе. И если я не использую три столпа ООП, это все равно часть моей абстракции.

«И даже если у меня один объект domain-модели выглядит как DTO, то не факт, что он для этого (это, скорее, Value-Object, в более широком смысле, чем DTO).» — ну один объект может решать множество задач? Разве нет? Он и DTO и Value-Object, в более широком смысле. Все зависит от того, на каком уровне абстракции он сейчас рассматривается.
Есть же сущности, а есть объекты-значения. DTO — это объект-значение. Просто набор данных. Человек — сущность, у него есть… сущность :) identity. Как вам такая терминология?
Ну, я бы Value-Object (в терминах DDD) и DTO не смешивал бы. VO имеет смысл для предметной области (domain), а DTO — этот термин описывает вообще другое — просто объект для передачи данных, вне зависимости от контекста.
Тоже всегда было непонятно, зачем так усложнять доступ к данным. Вот, Вы наглядно объяснили: чтобы отслеживать чтение и (или) запись. Но то же самое можно сделать, введя события onRead, onWrite, onBeforeWrite и тогда формат работы с данными не изменится, и возможность отслеживания сохранится.
Про события вы правы. Сеттеры очень часто используются для того что бы реализовать как раз генерацию события о изменении состояния. :) Если бы языки поддерживали «из коробки» события о изменении свойств, то в некоторых местах жить бы стало проще, а в некоторых невыносимо сложно. :)
Ваш паттерн — Dynamic Proxy ;)

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

Геттеры и сеттеры — это действительно методы. Но в случае DTO нам даже не нужно их определять/переопределять — то есть никакого поведения. И никакой инкапсуляции.
ДАвайте перейдем в ветку с письмом. Ок?
Так в этом и разница, вы не даете полный доступ к полям данных через методы, вы даете полный доступ к результатам работы методов, это могут быть поля данных в одной реализации, обработанные поля данных в наследнике, например, и левая байда из /dev/random в другой реализации, при этом сохраняя общий интерфейс.
Вот как раз чуть выше уже обсудили. Есть 2 типа объектов. Первый можно называть функциональными, смысловыми, сущностями предметной области или как-то так. Второй — data transfer objects (пожалуй, это самой точный термин). Ваш пример показателен для первого типа объектов, т.к. за ними может скрываться какая-то логика. Для DTO этот пример не показателен — они по определению несут данные, и их единственная функция — предоставить к этим данным доступ. Если за вызовом метода стоит обращение к /dev/random, то это уже логика, а значит объект относится к первому типу.
Не облегчит ли инкапсуляция превращение первого типа во второй если это потребуется?

Предположим решили, не хранить name в Person, а хранить историю изменения имени и получать текущее значение на лету?
Если объект несёт в себе какую-то логику, а не только данные, то он по определению не является и не может стать TDO. Он может хранить внутреннее состояние, прикрывая его инкапсуляцией, но объектом данных он не станет.
>>>Предположим решили, не хранить name в Person, а хранить историю изменения имени и получать текущее значение на лету?

Ваше решение проблемы?
Так тут проблемы и нет — вы просто преобразуете функциональный объект так, как вам нужно. Он остаётся функциональным и выполняет нужную логику. Но он не был и не станет DTO. В то же время у вас может быть дополнительны объект, скажем, PersonData, который будет состоять тупо из полей и методов доступа, и который вы будете использовать для передачи данных. Вот он уже будет DTO.
Или я чего то не понял?
тогда нужно какое-то правило, что с DTO может работать только слой функциональных оберток, чтоб абстрагировать зависимость от модели данных.

А зачем это надо?
Да не нужны никакие правила — объекты данных, это просто данные, вы можете обращаться с ними как хотите в своих бизнес объектах.
>>>Предположим решили, не хранить name в Person, а хранить историю изменения имени и получать текущее значение на лету?

Если любое место в коде моет использовать DTO то оно сломается от такого преобразование. Так как вынесение поля в отдельную таблицу обязано изменить DTO
Да с чего бы, есть же ещё data access objects, которые и работают с базой, и если вынести поле в отдельную таблицу, то изменится только этот самый DAO.
Каким образом, если в DAO нет никакой логики, он сможет изолировать своих пользователей от изменений?
В DAO есть логика работы с базой данных и формирования domain objects и data transfer objects. Без логики — это только DTO, ибо просто данные.
Понятно, я запутался в аббревиатурах.
Даже если это логика непротиворечивости данных?
Данные сами по себе не могут быть противоречивыми (конечно, если речь не идёт о противоречивых показаниях свидетелей преступления, например), противоречивыми могут быть объекты. А DTO не являются полноценными объектами в ООП смысле. Поэтому, думаю, не стоит заморачиваться на непротиворечивости объектов-данных.
Так тогда я не понимаю что вас смущает, в объектах первого типа инкапсуляция важна — соот-но нужны геттеры и сеттеры, в объектах второго типа, раз уж они как объекты не используются, ее можно не соблюдать, тобишь для языков которые позволяют прямое обращение к публичным полям данных — использовать их, или например в ди, помимо полноценных объектов, есть структуры — как более легковесный аналог оным, с прямым доступом к данным, и ваши сомнения решаются вообще идеально, нет объекта — нет проблем :)
Да, примерно так. Правда если речь идёт именно об объектах, а не о структурах, то лучше всё-таки оставлять методы доступа — просто потому что некоторые библиотеки или фреймворки могут рассчитывать на них. На инкапсуляцию это не повлияет, а компилятор или VM всё равно транслируют методы доступа в прямое обращение к полям, так что всё нормально.
Проектирование (независимо от того, ООП у нас или какая другая методология, расчитанная на построение иерархий типов) должно идти от интерфейсов к реализации. Если у вас в интерфейсе есть метод, просто предоставляющий некоторое значение, то это нормально и к этому случаю применимы все положительные качества геттеров; если же геттер появляется в интерфейсе вследствие реализации — интерфейса не хватило, дыру в абстракции получилось закрыть прокидыванием значения,- тогда это самое что ни на есть нарушение инкапсуляции.

Если проектирование у вас начало идти от реализации к интерфейсу — это всегда нарушение инкапсуляции. Независимо от того, какую логику и в какой момент времени вы в геттер засунете.
Вы натолкнули меня на мысль, а что если именно в этом ошибка: «Проектирование должно идти от интерфейсов к реализации»?

Может это просто чрезмерная идеализация, и на практике полезней «проектирование должно идти по спирали от интерфейса к реализации, затем к интерфейсу, затем к реализации и т.п.»?
Не возьмусь формализовать свою точку зрения, но сугубо эмпирически такая модель (проектирование по спирали) рано или поздно (если остался хоть один архитектор, ответственный за соответствующий кусок кода) приводит к рефакторингу формата «проще всё выкинуть и переписать с нуля».

Что касается темы обсуждения в общем, то не следует забывать, что нарушение инкапсуляции — это далеко не всегда плохо: горизонтальная инкапсуляция в архитектуре, например, зачастую приводит к over engineering'у и раздуванию кода.
Есть такое понятие — «проксирование». Поля не проксируются, в отличие от методов.
Например:
В гугло-аккаунте есть поле name.
В фэйсбукоаккаунте есть first_name, last_name.
В яхе есть givenName и familyName.
В твиттере есть screen_name а остального может не быть.

Функция getName в ситуации «поздороваться с залогиненым пользователем» — это то, что доктор прописал.
Первое что пришло в голову про Name — свойство FullName. Внутри это может быть как одно поле, так и группа из FirstName, MiddleName, LastName.

Здесь можно еще зацепиться за auto properties, но опять же, глядя со стороны интерфейса класса невозможно сказать auto это или есть там некая логика.

Ну и наконец — property при присвоении или чтении может выполнять дополнительные задачи типа валидации и т.д.

> Да и даже в них лучше изменить пользовательский код соответствующим образом

Расскажите это разработчикам библиотек. Узнайте что с ними сделают пользователи библиотек за такое, когда после очередного update надо будет переделывать свой проект.
НЛО прилетело и опубликовало эту надпись здесь
Да. Просто про auto property я упомянул потому что в исходном сообщении был упор на то, что обращение к property с кодом вида «return this._myValue» является раскрытием реализации.
Расскажите это разработчикам библиотек. Узнайте что с ними сделают пользователи библиотек за такое, когда после очередного update надо будет переделывать свой проект.

Действительно, пусть лучше будет пустой метод, который пользователи библиотеки будут вызывать и получать PropertyNotFoundException. Я уже где-то здесь отвечал, что смысл комментария был следующий: в DTO объектах свойства тесно связаны с полями, на которые они ссылаются. Если убираем поле, то по логике нужно убрать и свойства — всё равно они уже ни на что не ссылаются. Если же удаления поля/свойства ведёт к жёстким проблемам, тем более на стороне клиентского кода, то спрашивается, нафига вообще убирали? Если нужно убрать его из будущих релизов, достаточно объявить его depricated и удалить через год-два. А удалять поле и оставлять пустые методы — значит нарушать принцип инварианта и просто обманывать пользователя библиотеки.
> Если убираем поле, то по логике нужно убрать и свойства —
> всё равно они уже ни на что не ссылаются.

Далеко не всегда. Пример с скрытием того как хранится имя человека (FullName или FistName и отдельно LastName) уже приводили.

Ну и главное: обсуждение идет в рамках статьи или само по себе? Если первое, то в самой статье аббревиатура DTO не встречается. Там как я понимаю речь про ООП вообще. А в комментах вы вцепились в DTO, т.к. сама суть DTO — хранение этих данных. Т.к. в них часто вообще нет логики, то понятно что они более зависимы от этих самых данных.

Если вам так не хочется называть DTO объектами (т.е. вопрос чисто в названии), возьмите терминологию C#+EF — там это сущности (entities) :) Ну или называйте DTO контейнерами.
Во-первых, если у нас есть FullName, FirstName и т.д., то это однозначно объект из предметной области, и к объектам данных он имеет весьма сомнительное отношение.Во-вторых, мне в этом топике почему-то инкременируют желание отказаться от геттеров и сеттеров. Я этого совершенно не предлагаю и никогда не предлагал. Я прекрасно понимаю плюсы опосредованного доступа к полям. Я говорил о том, что это не инкапсуляция в смысле ООП, поскольку другие объекты всё равно получают доступ к полю, и тут уже неважно, как именно (с идеологической точки зрения неважно, с точки зрения реализации и возможного рефакторинга смысл в методах доступа, естественно, есть).

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

Термин «объект» очень сильно перегружен. Поэтому я стараюсь называть объектами (или бизнес объектами) сущности из ООП, а контейнеры данный — DTO или объектами данных. Эта терминология меня вполне устраивает и не вводит в заблуждение.
> Во-первых, если у нас есть FullName, FirstName и т.д., то это однозначно объект
> из предметной области

Не факт. Например, если DTO это промежуточное звено между БД и бизнес-логикой. При этом из БД могут читаться FirstName + LastName, а выдаваться будет FullName.

> Поэтому я стараюсь называть объектами (или бизнес объектами) сущности из ООП,
> а контейнеры данный — DTO или объектами данных. Эта терминология меня вполне
> устраивает и не вводит в заблуждение.

Ну а тогда о чем статья? :) О том, что вы не знали о существовании DTO, Entities и т.д. Извините, но это уже ваши сложности, а не проблемы ООП.
Уфф. Почитайте комментарии: терминология не установлена, отличительные черты ООП чётко не определены, понятие той же инкапуляции не согласовано. Это только то, что получило широкий ризонанс в комментариях. Кроме этого в статье я описал проблемы с отображением в программе потока управления из реальной жизни, неясность типизации ОО языков и проблему значения null, проблемы с эфективностью программ из-за активного потребления памяти и многое другое. Мне кажется, на статью таки тянет.
Может быть, когда-нибудь вы поймёте, что описанные вами в статье «проблемы» — это не проблемы ООП. Там всё в порядке.
Как минимум проблема терминологии остаётся.
getters и setters в Java, properties в C# и т.д. — это часть внешнего интерфейса объекта, наравне с методами. С какого перепугу они нарушают инкапсуляцию?
Это просто вырожденные методы класса.
И они вовсе не позволяют получить беспрепятственный доступ к состоянию объекта.

Настоящее нарушение инкапсуляции это например friendly function в C++.
Или использование рефлексии типов в .net.

Под инкапсуляцией обычно понимают вообще сокрытие деталей реализации а не только состояния объекта.
Например порядок вызова приватных методов в классе тоже может требовать согласованности.
В тоже время поля класса могут быть публичными, открывая к нему доступ. И это не является нарушением инкапсуляции если предусмотрено в архитектуре класса.
Не буду по 100 раз одно и то же описывать, просто дам ссылку на подтред: habrahabr.ru/post/147927/#comment_4989853. Читать: исходное сообщение, статьи по ссылкам и мой комментарий ниже.
Ну и чего там относящегося к вопросу, по ссылке, которую Вы дали?
Прочитал — всякие мысли про DTO и т.д.

Все это не имеет отношения к ООП.

Да, DTO с точки зрения ООП не обьекты, потому что не имеют поведения и не могут поддерживать полиморфизм. В ООП само понятие объекта имеет смысл только с точки зрения выполнения его постулатов.
Присутствие наследования требует наличия у объекта характеристик, которые наследуются. Полиморфизмаповедения.

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

В языке программирования C# у каждого объекта есть внешние интерфейсы доступа.
Property — часть таких интерфейсов. Сокрытие деталей реализации осуществляется приватным модификатором доступа. Вот в рамках этих правил разработчик и добивается внутренней согласованности объекта (его состояния и поведения).
Таковы механизмы языка для реализации инкапсуляции. Ими нужно пользоваться.
Ну да, я со всем согласен. О чём спор?
Спор об истинности утверждения в статье.

Более того, объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C# и т.д.
Почитайте комментарии — далеко не все считают, что DTO — это не объекты.
>>Настоящее нарушение инкапсуляции это например friendly function в C++.
нет, конечно. Это не нарушение, так как класс сам решает кто ему friend, а кто не friend. А вот дот нетовская рефлексия или reinterpret_cast в с++ — это да.
В принципе конечно да, getters/setters могут осуществлять доступ к данным по сколь угодно закрученным схемам, НО:
— Это делается очень редко. Большинство getters/setters являются простым return field; и this.field = field; Тем самым ничем не отличаясь от простого public Field field;
— Это все равно доступ к данным снаружи.
Вчера — просто return field, сегодня — делегирование вызова, завтра — ленивое создание структуры. И при этом интерфейс цел и никого снаружи не волнует, как класс устроен внутри.
И добавим ещё сюда обвешивание триггерами и событиями — которое можно осуществить прозрачно, и уже только за одно это (ну и за вышеперечисленное — тоже) я готов писать все геттеры и сеттеры вручную без всякого синтаксического сахара и IDE.
Это всё, конечно, очень бла-ародно, но какова у вас в работе пропорция между простыми геттерами, и красивым хорошим кодом с триггерами и событиями?
Это не та пропорция, которую нужно считать. Считать нужно соотношение между прямым доступом к членам класса (включая доступ из методов класса) и доступом через геттеры/сеттеры.
100% доступ через геттеры — это 100% возможность добавить триггер/логгер/брейкпойнт в любой момент времени.
Меняющаяся по мере разработки. Этого достаточно?
Фабрики фабрик забыли ещё.
«Осторожно, рекурсия!»
Вы говорите о замене реализации метода в подклассах? Так это подтиповой полиморфизм(не путайте, пожалуйста, с параметрическим). К инкапсуляции не имеет отношения.

Я имею ввиду, что наличие прямых геттеров и сеттеров у класса(про подклассы — это отдельный разговор) прямо нарушает инкапсуляцию, точно так же как и просто открытое поле. А вот с точки зрения подтипового полиморфизма да, наличие методов-оберток позволяет избежать нарушения полиморфизма, в отличие от открытых полей. Но ведь выше речь шла именно об инкапсуляции, а не о полиморфизме, разве нет?
Если бы геттеры были эквивалентны прямому доступу к данным, в C++ я мог бы через любой геттер получить ссылку/указатель на поле.
Ребят, читайте весь тред :) Речь идёт не о том, через что получать доступ к внутренним данным, речь идёт о самом факте получения доступа. Я пытаюсь показать, что идеология (и, как уже видно по обсуждению, терминология) ООП полна дыр и противоречий. В процедурном языке у меня есть структуры с данными и процедуры, их обрабатывающие. В объектно-ориентированном языке эти вещи смешаны. Вопрос, каковы правила работы с такими смешанными сущностями? Должны ли они быть законченными агентами, взаимодействующими друг с другом только по средствам сообщений и не раскрывающими внутреннюю структуру? Или же они должны нести в себе данные, открытые для общего доступа? Или что-то ещё? Я вижу два типа объектов, имеющих два противоположных предназначения, вытекающих как раз из процедурного программирования. Но если это так, то в чём фишка ООП, чем оно отличается от процедурного программирования?
Не должно быть никаких данных открытых для общего доступа.

private name
public getName(): return this.name

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

Если еще и такой же прямой сеттер, то означает — в рамках данного конкретного класса.

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

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

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

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

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

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

Почему я придерживаюсь более широкого толкования, я попытался разъяснить здесь: habrahabr.ru/post/147927/#comment_4993553
Любой метод (сообщение и т. п.) интерфейса объекта обязан выходить на его «внутренне мясо» (состояние). Поскольку назначение методов либо изменить состояние, либо дать информацию о нём. Какие правила трансляции метод <-> состояние используется не клиента нашего объекта ума дело. Это и есть инкапсуляция. Клиенту нужно знать, максимум, что если он не нарушил контракт, то вызов метода, рапортующего о состоянии объекта отрапартует, а изменяющего — изменит. Гетеры/сетеры этому требованию отвечают, а потому соответствуют инкапсуляции. Не соответствуют лишь те из них, которые рапортуют о нюансах состояния, являющегося не клиентского ума делом, или, тем более, его меняющие. Но мы же не о бездумном выставлении акцессоров на каждое поле объекта?
> Гетеры/сетеры этому требованию отвечают, а потому соответствуют инкапсуляции

Ну вот эти:
Большинство getters/setters являются простым return field; и this.field = field;


не отвечают. Соответственно, не все геттеры/сеттеры по умолчанию выполняют инкапсуляцию.
Правила трансляции метод<->состояние могут быть абсолютно любые, в том числе и такие примитивные. То что ни все выполняют инкапсуляцию, согласен, но это не зависит от того как они реализованы. Логика реализации может быть примитивной типа this.field = field; и не нарушать инкапсуляцию, а может быть сложной и нарушать. Грубо говоря, если метод задокументирован как часть публичного интерфейса объекта (не путать с модификатором public в Си-подобных языках), как часть ответственности объекта, то он по определению не нарушает. А вот если мы вводим public сеттер или геттер лишь в целях отладки/тестирования, то нарушает. Нарушение инкапсуляции — это семантика, а не синтаксис. Если в языке есть синтаксис для свойств, аналогичный синтаксису доступа к полям, то даже public поля могут не нарушать инкапсуляцию, потому что мы можем в любой момент заменить прямой доступ к полю на сколь угодно сложную логику свойства, а клиенты объекта этого не заметят.
Уважаемый EvilBlueBeaver хорошо переформулировал ту мысль, которую я не очень удачно пытался выразить. А также привел очень хороший, на мой взгляд, пример. Не хотите продолжить дискуссию в той ветке habrahabr.ru/post/147927/?reply_to=5000125#comment_4999743?
Давайте попробуем прояснить непонятные мне моменты.

1.
Под инкапсуляцией я понимаю просто скрытие реализации.

От кого сокрытие?

2. Представим, что я разработчик ядра, а Вы разработчик модулей. Я даю Вам интерфейс некоторого класса, у которого только два метода: setName($name), getName(). Нарушена ли инкапсуляция?

3. Представим, что Вы разработчик класса, у которого только два метода: setName($name), getName(). Нарушена ли инкапсуляция в следующих случаях, если методы...?
а. просто устанавливают и получают значение защищённого свойства $name.
б. просто устанавливают и получают значение защищённого свойства $value.
в. просто устанавливают и получают значение защищённого свойства $name и выводят его значение на экран.
г. просто устанавливают и получают значение защищённого свойства $name и выводят его значение записывают в лог.
д. просто устанавливают и получают значение защищённого свойства $name, но перед установкой умножают на 100, а получаемое значение увеличивают в 100 раз.
е. просто устанавливают и получают значение защищённого свойства $name, но при этом содержат по 15 строчек комментариев о том как метод будет изменён в будущем.
ё. обфусцированы и просто устанавливают и получают значение защищённого свойства $name.
ж. обфусцированы, но логика в них сложнее, чем в предыдущем варианте.
Давайте.

От кого сокрытие?
От пользователя экземпляра класса.

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

Нарушена ли инкапсуляция?

а. просто устанавливают и получают значение защищённого свойства $name.
Да.

б. просто устанавливают и получают значение защищённого свойства $value.
Да.

в. просто устанавливают и получают значение защищённого свойства $name и выводят его значение на экран.
Да.

г. просто устанавливают и получают значение защищённого свойства $name и выводят его значение записывают в лог.
Да.

д. просто устанавливают и получают значение защищённого свойства $name, но перед установкой умножают на 100, а получаемое значение увеличивают в 100 раз.
Да.

е. просто устанавливают и получают значение защищённого свойства $name, но при этом содержат по 15 строчек комментариев о том как метод будет изменён в будущем.
Да.

ё. обфусцированы и просто устанавливают и получают значение защищённого свойства $name.
Да.

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

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

От кого сокрытие?
От пользователя экземпляра класса.


Если о реализации этих методов ничего не известно


Вот Вы пользователь. Как реализован метод Вы не знаете. Значит реализация от Вас скрыта. Значит всё-таки соблюдается инкапсуляция или нет?

Так же непонятен ответ с обфусцированными методами. Ведь реализация от пользователя скрыта, почему нарушена инкапсуляция?

Не могу ответить, зависит от логики.

Так… как тогда различить где скрывается реализация от пользователя и где нет. Ведь из Ваших ответов следует, что изменение логики (да, небольшое, но изменение) не влияет на сокрытие? А что тогда влияет?

И читали ли Вы раздел «Скрывайте секреты (к вопросу о сокрытии информации)» главы «Компоненты проектирования: эвристические принципы» книги «Совершенный код» С. Макконнелл?
Значит реализация от Вас скрыта.

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

Так… как тогда различить где скрывается реализация от пользователя и где нет.

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

книги «Совершенный код» С. Макконнелл

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

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

А я, кстати, не против неформальных правил, вроде комментариев и т.п. Если конечно они действительно явно присутствуют. Это в какой-то мере решает проблему(в какой-то). Подробнее тут: habrahabr.ru/post/147927/?reply_to=5000284#comment_4999627

В том плане, что если в комментариях сеттера написать, например: «этот метод не использовать ни при каких обстоятельствах», — то да, можно говорить, что это как бы решение. :)
Это решение согласно принятым соглашениям. Но весь код пишется согласно соглашениям (внутри команды или соглашения синтаксиса — не суть), значит это нормальное решение, а не «как бы» :)

P.S. Не копируйте ссылки с
?reply_to=N1#comment_N2 — они делают как-то не то что ожидается.
> Это решение согласно принятым соглашениям

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

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

> P.S. Не копируйте ссылки с

ой, сорри. Не заметил :)
Соглашения принимаются для удобства. Будет команде удобно писать в одном методе — буду писать :)
могу привести в пример python с ruby. там даже при обращении к полю я могу подменить реализацию и сделать что угодно. получается, что даже обращение к полю напрямую по вашему принципу инкапсулировано.

Но вообще ваш собеседник выдвигает другую идею, что в чистом ООП случае пользователь вашего кода вообще не должен знать, что у вашего класса есть такая штука как name. Грубо говоря не я должен спросить у него имя и потом что-то сделать сам, а попросить его сделать то, что мне нужно.
Да, Вы правы. В php тоже можно сделать. И, да, реализация инкапсулирована, потому что я как пользователь класса не знаю как реализована та или иная часть, а могу только предполагать. Ведь для этого такие возможности и были добавлены в языки, чтобы можно было изменить реализацию не изменяя интерфейс класса.

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

С другой стороны класс никогда не будет инкапсулирован для разработчика этого класса потому что он будет знать его внутренности «с ног до головы». Так?

Прочтите, пожалуйста: «Скрывайте секреты (к вопросу о сокрытии информации)» главы «Компоненты проектирования: эвристические принципы» книги «Совершенный код» С. Макконнелл. А потом выскажете своё мнение по поводу прочитанного.
то есть по вашему
var fullName = person.getFirstName() + person.getLastName() 

это инкапсуляция? Правильно я понимаю?
Так же как
person.setName("ololo");
person.someMethod();

тоже?
Я не знаю что это за язык. Поэтому в данном случае я могу сказать, что формирование fullName от меня скрыто, потому что оператор + мог быть перегружен. Не инкапсулировано, потому что оператор это не метод класса, а просто скрыто. Но от чего зависит fullName от меня не скрыто.

У меня есть задача, получить fullName и у меня есть класс person, из которого я могу получить имя и фамилию. Ну… беру и получаю. Но при этом я не знаю откуда они там: из Базы, из файлов, из памяти, из кэша и т.п… И не знаю что происходит во время получения: срабатывает событие, что-то логируется, лениво загружается, инициализируется, отправляется что-то по почте и т.п…

Тоже самое и с остальными методами.
а вам не кажется, что по логике fullName должен получаться из person? а так получается, что мы в другом классе не используя никаких полей и методов этого другого класса реализуем логику только над данными класса person.
firstName и lastName это строки обычные, так что перекрыть оператор "+" не выйдет.
Откуда я знаю? Всё зависит от целей. Возможно, нужен более сложный метод. Ведь на каждый случай не напасёшся методов: getNameWithGreetings, getFullNameWithGreetings, getFullNameWithBirthday, getBirthdayWithFullName, getNameWithThirdName и т.п. А сделать один метод: getInfoFormated('%greetings %second_name, %first_name'); или как-то так.

Только я не знаю как это связано с инкапсуляцией. Ведь реализация класса в любом случае скрыта от Вас. Хоть есть там этот метод, хоть нет.
а для случаев есть полиморфизм. это не повод давать другим объектам доступ к своим внутренностям пусть даже и через геттеры. разве нет?
Эм… при чём здесь полиморфизм? Чтобы получить имя пользователя немного в другом формате нужно создать несколько классов?

Эм… методы для этого и нужны, чтобы работать с внутренним состоянием объекта. А как они это делают не наша забота.
Вообще самая ООП-шная идея тут заключается в том, что ваш класс может принимать некий билдер, которому ваш класс может через некий метод билдера передать нужные ему данные и вернуть то, что билдер сконструировал. То есть что-то вроде вот такого.
interface IBuilder
{
    string build(string firstName, string lastName);
}
class ConcreteBuilder: IBuilder
{
    string build(string firstName, string lastName)
   {
      return string.format("sir {0} {1}", firstName, lastName) 
}
class Person
{
    private string _firstName;
    private string _secondName;
    public Greetings(IBuilder builder)
   {
       return builder.build(_firstName, _secondName);
   }
}

..

person = new Person();
builder = new ConcreteBuilder()
string greetings = person.Greetings(builder)

возможно паттерн называется как-то по-другому, точно не помню. но идея думаю понятна. Нужно другое приветствие — пишите другой билдер, а не лазайте по кишкам person. Вот тут полиморфизм и инкапсуляция. Каждый класс работает только со своими данными и другими классами, но не с данными других классов.
Это как раз не ООП-шная идея, а ее abuse. Потому что вы порождаете кучу ненужных сущностей ради простой задачи. В частности, сущность Builder, не имеющую смысла.
Ну это чистое ООП без всяких нарушений. я не говорил, что это идеальное практическое решение. но инкапсуляция зато есть, никто не знает структуры других объектов.
По поводу злоупотребления. Есть принцип OCP, который гласит, что классы должны быть закрыты для модификации, но открыты для расширения.
А так да. ООП вообще славен многословностью. В данном примере с тем же успехом можно сделать и без ООП и будет проще.
struct Person
{
 string _firstName;
 string _lastName;
}
string greetings1(Person person) {...}
string greetings2(Person person) {...}
Ну это чистое ООП без всяких нарушений.

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

Есть принцип OCP, который гласит, что классы должны быть закрыты для модификации, но открыты для расширения.

И как он применим к данному случаю?
публично доступные данные нарушают инкапсуляцию.

OCP применим в том, что я не изменяю класс Person, для новых приветствий, а просто передаю другой билдер.
публично доступные данные нарушают инкапсуляцию.

Нет. Инкапсуляцию нарушают публично доступные данные, не входящие в оправданный задачей контракт.
Я бы не сказал, что без нарушений. Нарушена, как минимум, бытовая логика :) Когда я хочу с вами поздороваться, я просто смотрю ваше публичное свойство «имя» и пишу «Здравствуйте, Кирилл», а не жму кнопку «поздороваться» и не ввожу туда «Здравствуйте, <имя>» :)

P.S. Когда я писал на C и у меня было много функций с сигнатурами типа greetings1(Person person, ...), greetings2(Person person, ...), greetings1(Pet pet, ...) и greetings2(Pet pet, ...) я сам пришёл к принципам инкапсуляции и полиморфизма, до наследования не додумался. :)
Но вы же не пишете мне «Здравствуй владелец такого-то счета в таком-то банке».
Кстати отличный пример, большинство комментариев не содержат в себе имени адресата. Логика того, кому комментарий отправлен скрыта. Порой даже не задумываются, кому пишут комментарии, потому что это и не надо. Есть кнопка «ответить» и поле для ввода текста.
Это скорее стратегия. И надо отметить, что такой подход как раз не характерен для ООП, а скорее это функциональный подход.

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

Инкапсуляция это
1) технические меры, которые предпринимаются для того, чтобы не допустить неправильного использования класса. Именно технические, а не описания навроде «вызывайте это в таком порядке потому что иначе не работает». Это кстати называется принципом инварианта. собственно инкапсуляция и позволяет сохранять инвариант. Случай с именами вырожден, это очевидно, но существуют и более сложные случаи.

2) исключение использования данных одного класса внутри другого. Каждый класс несет ответственность за свои данные и не должен нести ответственность за данные другого класса.

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

От кого скрываем?
От пользователя класса? — нет, в общем случае это невозможно. Пользователь может просто заглянуть в класс и всё посмотреть. А от себя так, вообще, не скроешь.

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

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

Ещё можно вспомнить для чего было придумано ООП. Чтобы было проще программировать. Как инкапсуляция позволяет упростить написание кода?

1. Упрощается абстракция. В момент использования класса мы можем «забыть» все его внутренности, и использовать только то, что открыто. Т.е. не надо помнить как какой класс и как именно реализован и что он будет использовать.

2. Локализовать будущие изменения. Изменения того или иного куска кода будет проще, если мы будем знать кто им пользуется. Инкапсуляция позволяет сократить таких пользователей путём расстановки ограничений.
Если в описании бизнес-модели фигурирует только «полное имя» как свойство для чтения объекта «персона», а класс его не реализует, то это просто плохая реализация. Если же класс предоставляет доступ для чтения к своим полям «имя» и «фамилия», хотя это нигде не документировано как публичный контракт класса, то это нарушение инкапсуляции. При этом доступ для записи к этим полям, если он документирован в описании бизнес-модели, нарушением инкапсуляции не будет. Грубо говоря, если в документации написано «вы можете установить значения свойств „имя“ и „фамилия“ и получить значение свойства „полное имя“, формируемое из них», то методы, свойства и поля позволяющие это сделать инкапсуляцию не нарушают. А нарушать её будет если наряду с этим можно получить значение полей «имя» и «фамилия» или записать значение свойства «полное имя». То есть если объект предоставляет доступ к своему состоянию способом, недокументированным как «валидный» в его публичном контракте (не путать с public свойствами, имплементацией интерфейсов и прочими элементами синтаксиса), то нарушение инкапсуляции налицо. Но не видя документации, публичного контракта класса говорить об этом нельзя — просто недостаточно информации для таких суждений. Если у вас в команде принято, что код должен быть самодокументируемым без комментариев, то модификатор public говорит о том, что поле/свойство/метод являются частью такого контракта и не могут нарушать инкапсуляцию по определению. Если же комментарии допустимы или ещё какой вид документации есть, то нужно читать их чтобы определять входит в публичный контракт даный атрибут класса или не, или разработчик ввёл его чисто для своих целей, и используя его как клиент вы будете нарушать инкапсуляцию. Замечу именно вы как разработчик клиента, а не разработчик сервиса. Это хорошо видно в языках типа python где нет синтаксических модификаторов доступа, но по соглашению принято, что аттрибуты начинающиеся с _ являются приватными/служебными и обращение к ним клиента будет означать нарушение клиентом инкапсуляции.

P.S. Некоторые языки позволяют перекрыть любой оператор в любом контексте, например, определить (по сути переопределить) метод класса String с сигнатурой +(String string) или __plus(String string).
ну то есть по вашей логике нарушением инкапсуляции не будет то, что я указал, что это не нарушение инкапсуляции в комментарии или документации?
инкапсуляция — это архитектурное понятие, а не понятие реализации и документации.
Именно инкапсулировано, если в документации я разрешил клиентам своего объекта менять значение этого поля или читать его. Или не предварил его подчёркиванием, если в команде принято соглашение, что поля и методы не начинающиеся с него являются публичным интерфейсом/контрактом.
Окей, тогда вся идея инкапсуляции не имеет смысла. Я пишу в документации, что поля публичные и используте как хотите. так же пишу, что вот это метод нельзя вызывать пока не проинициализировано вот это поле, и пока не вызван вот этот метод. Не, а что такого? в документации-то описано.

В свое время меня повеселила какая-то либа (по-моему DirectDraw или что-то в этом роде), в который был такой код:
SomeClass c = new SomeClass();
c.SetValue1(1);
c.SetValue2(2);
c.DoSomeWork();

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

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

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

Но всё же ещё раз напомню пропущенную часть моего утверждения: класс может поменяться в реализации в любой момент. Именно поэтому прямой доступ к публичному полю не эквивалентен доступу к приватному полю через геттер и сеттер, получающий/устанавливающий это поле без дополнительных действий. В случае геттера/сеттера пространство возможных реализаций шире, чем просто чтение и установка поля и независимо от внешнего кода, поэтому инкапсуляция не нарушена.
По поводу определений я ответил выше: habrahabr.ru/post/147927/#comment_4998329

> Именно поэтому прямой доступ к публичному полю не эквивалентен доступу к приватному полю через геттер и сеттер

С этим утверждение я не спорил. Действительно, заворачитвать в геттеры и сеттеры как правило лучше, чем не заворачивать вообще.
Смотрите на инкапсуляцию как «пользователь» класса, а не как его «создатель», возможно это внесет ясность.
С точки зрения «пользователя» вам в реальности не важна реализация (а вы заостряетесь на ней).
С точки зрения «создателя» вас просто гложет то, что у вас «лишний» метод там, где он не нужен, а значит вы слишком рано начинаете думать об оптимизации и ставите ее выше поддержки\изменяемости кода.
Раньше я любит использовать field (было лень писать свойства), но тогда мне часто, рано или поздно, приходилось менять их на свойства все равно ;)
Да не об оптимизации речь совершенно. Ещё раз на пальцах.

C:
struct Person {
char* name;
int weight;
int height;
};

void sayHey(Person p) {
printf(«Hey %s», p.name);
}

Person — данные. sayHey — действия.

Java:
class Person {
String name;
int weight;
int height;

public void sayHey() {
System.out.println(«Hey », this.name);
}
}

Person — это и данные, и действия. Это то, как обычно описывается ООП. Но на практике есть 2 типа объектов — те, которые совершают действия, и те, которые несут данные (в Java — это JavaBeans). С объектами, которые совершают действия, всё просто. А вот бины по сути ничего не прячут, их суть — нести данные. Доставить данные о человеке из БД на JSP страницу. Они не ведут себя как объекты, описанные Аланом Кеем. Они не скрывают своё состояние и не выполняют никаких действий. Там нет какой-то скрытой реализации, вообще методов кроме геттеров и сеттеров нет. Они просто несут данные и предоставляют к ним доступ, и не важно, открывают ли они поля, или предоставляют методы доступа — они открывают своё внутреннее состояние, потому что кроме него у них ничего нет.

А геттеры и сеттеры, кстати, большинством JVM тупо инлайнятся, так что ручная оптимизация ни к чему.
Узко мыслите.
Представьте теперь, что кроме Person есть еще Alien extends Person. Для поддержки Alien вы добавляете в функцию sayHey(Person p) функциональность работы с Alien, а потом РУКАМИ в рантайме выбираете, какую же функцию вам позвать. В ООП это красиво оборачивается в иерархию классов, и клиенту будет уже по барабану, кто там этот ваш наследник — Person, Alien или Animal. Понимаете? Мы абстрагируемся от физической природы и работаем с контрактом того, что с этой иерархией можно поздороваться. В процедурном прог
Извиняюсь. В процедурном программировании вы еще развязываете данные и логику работы с этими данными, хотя в случае работы с объектами они друг от друга неотделимы.

Для примера можно еще привести автомобиль. Вот вы садитесь в него, у него двигатель, карбюратор, номерной знак и еще кучу всего. Но для вождения вам все это неважно. Важно, что есть интерфейс к нему — руль и педали. Так это видит ООП. А процедурное это видит, как коробку с деталями(двигателем и т.д.), лежащие в одном месте и коробку с управлением, в которую надо засунуть первую коробку, чтобы что-то с ней сделать.
Простите, я не очень понял, как это относится к JavaBeans?
Это не к JavaBeans. Это к вашей реализации Person. А утверждение «на практике есть два типа объектов» — спорное, потому что верно не везде. Где-то это конечно так, но много где еще — нет. Есть объекты, которые каким-то образом что-то делают и для этого хранят внутри состояние. Возьмите StringBuffer — отличный класс, который призван конкатенировать строки потоко-безопасно. Что там внутри — без разницы, главное известен его контракт. Но при этом он несет в себе данные — все строки, что ему переданы, но форма хранения этих данных неизвестна.
Ну это как раз первый тип объектов — функциональный. Ну, или, если быть точным, то можно выделить 3 типа объектов. Обзавём их так:

1. Stateless Business Objects — объекты, не имеющие состояния как такового, а просто хранящие набор методов (возможно, статических) для обработки данных. Это могут быть объекты для обработки коллекций, решения системы уравнений, ввода/вывода и т.д. (при этом внутри класс для ввода/вывода, например, может хранить какой-то буфер с состоянием, но для пользовательского кода операции write и read всё равно будут выглядеть как stateless).
2. Stateful Business Objects — то же, но с сохранением состояния. Т.е. когда поведение объекта зависит от текущего состояния. Это может быть, например, класс для соединения с базой данных. Поля объекта скрыты от пользовательского кода, а состояние соединения мы можем только по средствам методов типа isOpen(). Ну и работаем с объектом мы тоже через соответсвуюзие методы — open(), close(), request() и т.д.
3. Data Transfer Objects — объекты для переноса данных. Они тупо хранят данные. Они предоставляют прозрачный доступ к своему содержимому и не пытаются быть полноценными членами ОО-сообщества. Примеры — PersonData, AccountState, Food. В основном такие данные движутся от БД к вьюшкам и обратно, иногда попадая в обработчики, состоящие из Stateless и Stateful Business Objects.

Так вот, StringBuffer — это Stateful Business Object. Ну и заодно паттерн Builder, конечно же.
Смотрите на инкапсуляцию как «пользователь» класса, а не как его «создатель», возможно это внесет ясность

Инкапсуляция относится к внутреннему дизайну класса. Если рассматривать с точки зрения пользователя, понятие «инкапсуляция» становится бесполезным, именно потому, что: "С точки зрения «пользователя» вам в реальности не важна реализация".

С точки зрения «создателя» вас просто гложет то, что у вас «лишний» метод там, где он не нужен, а значит вы слишком рано начинаете думать об оптимизации и ставите ее выше поддержки\изменяемости кода.

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

Раньше я любит использовать field

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

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

Я имел ввиду, что автор думает о том, что методы get\set внутри реализован вот так то (что как бы плохо), поэтому и надо посмотреть как пользователь — тогда тебе не важно что там внутри.
Вы знаете, когда Кнут говорил «Преждевременная оптимизация — корень всех зол»

Ну тут отчасти это… я как раз и имел ввиду «преждевременную оптимизацию». Я предположил, что автору не нравятся свойства, т.к. это лишние методы, легче просто обращаться с полю.
Я имел ввиду, что автор думает о том, что методы get\set внутри реализован вот так то (что как бы плохо), поэтому и надо посмотреть как пользователь — тогда тебе не важно что там внутри.

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

пары (x, y) потом, вдруг решили, что большинству запросов проще работать с (угол, расстояние) какую дисциплину в коде вы будете поддерживать, чтобы такой переход был безболезненный.
НЛО прилетело и опубликовало эту надпись здесь
>>>В Вашем примере даже если бы в интерфейсе было setX и setY, но возникло желание задавать координаты по-другому, все равно пришлось бы весь использующий эти сеттеры код переписать, потому что для пересчета полярных координат в декартовы нужно передать вместе и угол, и расстояние от центра.

1. Если в каждый момент времени есть две координаты, ничего не мешает задавать их по отдельности
2. Может быть много кода, который использует только геттеры — лучше переделать сеттеры и не переделывать геттеры, чем переделывать и то и другое.

>>>Поэтому я бы сделал статический класс с тремя методами:

1. По условиям адачи нам надо теперь хранить полярные координаты.
2. Чем это лучше пропертей в point
Так, к слову: не забывайте про сохранение инварианта. Если у точки координата `x` установлена, а `y` равна null, то точка находится в несогласованном состоянии.
при чем тут это?
Некоторые моменты в обсуждении наводят на мысль о речь идёт о раздельной установке координат X и Y, что может повлечь за собой проблемы согласованности объекта и/или расширяемости. Решил на всякий случай напомнить.
Хотя бы чтобы тот же «конфиг» был в непротиворечивом состоянии.
НЛО прилетело и опубликовало эту надпись здесь
Псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо return this.foo и this.foo = foo, на самом деле, ничем не лучше.

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

Получается такого рода код
class Mailer {
    SendMail(User CurrentUser) {
        full_email = '"' + CurrentUser.getName() + '" <' + CurrentUser.getEmail() + '>'
        ...
   }
}


то есть выглядит как ООП, но им не является.
Если хочется SomeObject.foo — или SomeObject.getFoo() — надо прежде всего подумать, как поменять архитектуру, чтобы было this.foo, а не плодить геттеры и сеттеры. Tell, don't ask.
Совершенно с вами согласен.

Там выше еще были комментарии по содержанию близкие к вашему. Их заминусовали, как я понимаю, за то, что в случае прямого доступа нету возможности заменить в подклассах реализацию «геттера».

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

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

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

На внешнем виде интерфейса, разумеется, это никак не скажется. Что говорит лишь о том, что с точки зрения подтипового полиморфизма класс спроектирован хорошо.
>>ничем не лучше

оно лучше тем, что резализацию getName можно подменить не меняя вызывающий код. Корявая абстракция лучше чем никакой.
С точки зрения инкапсуляции в этом классе — ничем не лучше. Подменить же можно только в подклассе, а в родительском так и останется нарушенная инкапсуляция.
кто мне мешает изменить код getName() в этом классе?
Еще раз. То что foo.getName() лучше foo.name — с этим никто не спорит. То что foo.getName() формально нарушает принцип инкапсуляции точно также как и foo.name — это очевидный факт. Ну, во всяком случае, я пытаюсь это донести.

> кто мне мешает изменить код getName() в этом классе?

Тогда условия задачи меняются.
>>>То что foo.getName() лучше foo.name — с этим никто не спорит

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

>>> Потому что в контексте принципа инкапсуляции наличие геттеров и сеттеров для поля равносильно открытию поля

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

ок. Мы друг друга недопонили. Просто обсуждали же инкапсуляцию вроде, ну да ладно.

> Геттер вводит абстракцию свойства мы можем подменить его реализацию, как хотим

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

> Это и есть инкапсуляция — реализация изолирована от интерфейса

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

я имею ввиду класс CurrentUser — getter же его? Мы можем изменить реализацию getName в классе CurrentUser не трогая функцию SendMail

> Это и есть инкапсуляция — реализация изолирована от интерфейса

>>> Инкапсуляция — это скрытие реализации от пользователя. В данном случае реализация фактически открыта. Сам по себе факт отделения реализации от интерфейса не означает инкапсуляцию. Например, в C файл интерфейса функций можно отделить от имплементации. Тем не менее, все данные лежат снаружи.

В файле интерфейса C будет декларирована структура user. И код SendMail не будет гарантирован от залезания туда.

Либо будет ООП реализованное библиотекой C
> Мы можем изменить реализацию getName в классе CurrentUser не трогая функцию SendMail

Каким образом? Код же уже написан. И Mailer, и CurrentUser.

> В файле интерфейса C будет декларирована структура user. И код SendMail не будет гарантирован от залезания туда.

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

======

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

Что вы можете сказать о реализации Foo::getName() не глядя в исходники класса? Ничего. Возможно это обращение к частному полю, а возможно сложное ленивое вычисление или запрос к внешнему сервису.
Обратите, пожалуйста, внимание, что у нас и геттер, и сеттер(по условию) есть.

Да, мне как пользователю класса не важно, что там внутри, а как разработчику класса — важно. Так как, если я напрямую соединяю геттер и сеттер с полем(в соответствии с условием), я даю потенциальную возможность для пользователя повлиять ну внутреннюю работу класса, испортить там что-нибудь в его механизме.

А если поле — какая-то совсем отдельная сущность, которая никак внутри ничего не может испортить, значит эта сущность по смыслу не относится к классу, то есть это опять же нарушение инкапсуляции.

Еще раз подчеркиваю, что мы рассматриваем именно случай, когда у нас геттер и сеттер напрямую подключены к полю, никаких внешних сервисом или ленивых вычислений. В примере у symbix об этом явно сказано.
Ну, вызов любого метода даёт теоретическую возможность пользователю что-то испортить. Их вызывают (за исключением геттеров) чтобы повлиять на внутреннюю работу класса.
Как раз теоретически — нет. :)

Я понимаю, что на практике делают по всякому. Но мы-то говорим о том, как надо.
Если портит — берём и исправляем реализацию сеттера. При наличии прямого доступа к полю это было бы невозможно. Так что геттер/сеттер — правильнее и не эквивалентен прямому доступу.
Более того, в классе родителе прямая запись в поле могла ничего не портить, зато в наследнике — логика меняется. При наличии сеттера это не вопрос, при прямом доступе к полю — проблема неразрешима.
> Так что геттер/сеттер — правильнее и не эквивалентен прямому доступу.

Так никто же не спорит с этим :)
А если поле — какая-то совсем отдельная сущность, которая никак внутри ничего не может испортить, значит эта сущность по смыслу не относится к классу, то есть это опять же нарушение инкапсуляции.

То есть если все значения поля допустимы, то это обязательно отдельная сущность?

Почему порча является определяющим признаком принадлежности к классу?

То что foo.getName() формально нарушает принцип инкапсуляции точно также как и foo.name — это очевидный факт.

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

Или вы хотите сказать, что атрибут сущности с ней не связан? Тоже как-то не убедительно.
> Или вы хотите сказать, что атрибут сущности с ней не связан?

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

Повторю еще раз.

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

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

Вы меня простите, но у вас тут адская путаница.

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

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

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

Поймите, публичная операция дайИмя() ничем не отличается от публичной же метода дайРезультатРаботы() или посчитайЗарплату(). Это все — часть публичного контракта.
А, по-моему, путаница у вас.

Вы говорите об инкапсуляции в контексте контракта? В контексте контракта говорить об инкапсуляции бессмысленно. Понятие инкапсуляции относится к реализации, а не контракту.

И мы обсуждаем конкретную реализацию, а не контракт вообще.
А нет никакой «конкретной реализации», есть property (в терминах C#) Name, которое сегодня — automatic property, завтра — property with backing field, послезавтра — полторы тонны логики. Дадада, это тот самый случай, о котором автор статьи пишет «объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C#».

Так вот, properties в C# сами по себе не являются нарушением инкапсуляции. Нарушением инкапсуляции является публично доступный field.
Конкретная реализация есть. Она приведена автором комента, который мы обсуждаем: habrahabr.ru/post/147927/#comment_4990497

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

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

Поэтому постулат «псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо return this.foo и this.foo = foo, на самом деле, ничем не лучше» неверен. Лучше именно тем, что в любой момент времени автор конкретной реализации может ее заменить.
> Вас не смущает тот факт, что эта реализация может быть в любой момент изменена без нарушения внешнего контракта, и это, собственно, и есть инкапсуляция?

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

> Никто никогда не фиксирует намертво реализацию внутри класса, потому что тогда никакая инкапсуляция не была бы нужна.

Как это не фиксирует? Ок, я понимаю, бывают такие говнокодные проекты, где разработчик класса рассчитывает, что пользователь если что — залезит в исходник и там что-нибудь авось подкрутит.

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

При чем тут подтипы, если мы говорим об изменении одного класса?

Как это не фиксирует?

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

Сценарий-то банальный, как жизнь.

Был класс Interval, у него было свойство ElapsedSeconds (конечно, публичное). Класс лежит в сборке, наружу (так уж и быть, не будем лишнюю абстракцию вводить) виден, все им пользуются. Свойство внутри смотрит на поле seconds, снаружи не видное, никакой логики в нем нет и не надо.

А теперь мы выясняем, что считать с точностью до секунды — недостаточно, надо считать с точностью до милисекунды. Что мы делаем? Мы убиваем поле seconds, вводим новое поле milliseconds, меняем всю логику внутри класса с учетом этого изменения, в том числе и свойство ElapsedSeconds, которое теперь делает не банальный return seconds, а return Round(milliseconds*1000).

(ну и добавляем новые свойства/методы для работы с мс, это не так критично)

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

Нет, инкапсуляция к этому не имеет отношения. lair, извините, мне кажется вы просто не понимаете что значит инкапсуляция. Я даже ссылку вам с Вики привел с формальным определением, но вы все равно продолжаете называть «инкапсуляцией» вещи, которые никакого отношения к ней не имеют.

> Что мы делаем? Мы убиваем поле seconds

Если класс уже ушел в продакшн, то уже ничего не поменяешь. А если и поменяешь — это будет другой класс, другой релиз.
Нет, инкапсуляция к этому не имеет отношения. lair, извините, мне кажется вы просто не понимаете что значит инкапсуляция. Я даже ссылку вам с Вики привел с формальным определением, но вы все равно продолжаете называть «инкапсуляцией» вещи, которые никакого отношения к ней не имеют.

Да ну?

Вот берем прямо ваше определение из вики:
In a programming language, encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination thereof:
— A language mechanism for restricting access to some of the object's components.
— A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.

Извините, но свойства в C# строго соответствуют обоим этим пунктам. Ограничение доступа есть? Есть, пользователь класса не может получить доступ к филду. Упрощение связывание данных с методами, работающими над этими данными? Тоже есть.

Если класс уже ушел в продакшн, то уже ничего не поменяешь

Во-первых, есть жизнь и до продакшна.

А если и поменяешь — это будет другой класс, другой релиз.

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

Что вы вкладываете в понятие «доступ»?

> Во-первых, есть жизнь и до продакшна.

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

Возможность обратиться из кода.

Если обсуждаемый пример заменить на другой, то нечего и обсуждать.

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

Тогда это только частный случай «A language mechanism for restricting access to some of the object's components».
Значит, это частный случай инкапсуляции. QED.
Если класс уже ушел в продакшн, то уже ничего не поменяешь

Как это не поменяешь? Да сразу после выхода в продакшн изменения учащаются, по сравнению со стадией разработки. Стабильным остаётся только интерфейс, но реализация — пилится и точится под весьма динамичные требования, пока не будет достигнут приемлемый статус и не будут выловлены все баги и бутылочные горлышки на реальных задачах.
BlessMaster, я уже несколько раз в этом треде ответил на аналогичный вопрос. Можно я просто приведу ссылку на свой ответ? :)

habrahabr.ru/post/147927/?reply_to=4993346#comment_4992338
Извините, но я нихрена не понял. :) Почему нарушена инкапсуляция? Вы знаете что у персоны есть имя, так? Так. Но вы знаете как это имя вы получаете? Нет. Реализация скрыта? Да. В чем проблема?
Ничего что я лезу? :)
Проблема не в получении имени, а в его замене. Ну, вместо имени подаст пользователь в setName, скажем, xss-инъекцию, и все, у админа увели пароль :)
Ну так вот как раз и функция setName() позволит мне реализовать проверку. Если ее не будет, а будет торчать тупо Name, то тогда я проверить не смогу изменения. Вот и все.

BTW: мне кажется что вы тупо троллите пример :) Нет?
Если так много людей ведутся, значит не тупо!
Ну в смысле не тролите глупо, а упрямо. :)
«Так вот, properties в C# сами по себе не являются нарушением инкапсуляции. Нарушением инкапсуляции является публично доступный field.» — вот это верно :)
Вот тут спорный момент. Та же статья на Википедии очень осторожно говорит о «компонентнах» объекта. Если брать техническую реализацию (ну, то есть, тот же C# или Java), то, вероятно, речь идёт о сокрытии полей. Однако, с точки зрения самой парадигмы (которая знает только про объекты, что у них есть состояние и есть сообщения для обработки), то никаких полей вообще не существует (ну или они не существены), а сокрытие реализации означает запрет доступа к состоянию объекта. И вот тут уже всё равно, каким образом вы получаете состояние объекта в конкретной реализации: напрямую из поля или через методы — с точки зрения парадигмы вы получили доступ к состоянию объекта (внутренностям, которые не должны видеть), а это плохо.
сокрытие реализации означает запрет доступа к состоянию объекта

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

А давайте тогда не путать возможность нарушить инкапсуляцию (которая есть вне зависимости от геттеров-сеттеров, и это ошибка уровня проектирования контракта) и тот посыл, что геттеры-сеттеры всегда ее нарушают (который приведен в посте).
Как уже выяснили выше (и как я уже упомянул наверное в сотне сообщений), DTO объектами в смысле ООП не являются. Ну а для бизнес объектов методы getXXX() и setXXX() ещё не означают, что они дают прямой доступ к полям.
Угу. Вот только фраза в посте про геттеры и сеттеры не связана ни с тем, ни с другим. И именно поэтому ложна, кстати.
У вас претензии к посту или к моим рассуждениям?

Пост апдейтил, надеюсь, теперь путаницы не будет. Хотя не исключено, что сейчас налетят те, кто всё-таки считает DTO объектами в смысле ООП.
У меня претензия к этой конкретной фразе (которая, афаик, не ваша, а кто-то ее раньше сказал), и которая неверна.

Сами рассуждения в ваше посте для меня too vague, извините.
Фраза моя. Читайте мои комменты, я там снизу много конкретики приводил.
С теми из ваших комментов, которые мне были интересны, я уже поспорил.
С теми из ваших комментов, которые мне были интересны, я уже поспорил.


Извините, не удержался. Фраза просто гениальная вышла!

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

>>>Если атрибут не связан с внутренней реализацией, значит класс объединяет в себе левую сущность, то есть нарушена инкапсуляция опять же по определению

Как это нарушает инкапсуляцию?
Name getName()
{
    nameHistory.GetByDate(CurrentDate());
}


> процитируйте, пожалуйста определение послностью — а то вы говорите, про связанность а цитируете про сокрытие.

И связанность, и скрытие. Я ссылаюсь на определение из Вики: http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)

> Как это нарушает инкапсуляцию?

Это — никак.
Какую надо реализацию, чтоб нарушило?
Там не приведена реализация getName
В самом начале же сказано:

> Псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо return this.foo и this.foo = foo
Ага. То есть если я прокеширую name в Person инкапсуляция вдруг пропадет, а если перестану кешировать, то появится?

Мне кажется, вы делаете логическую ошибку.

Name getName()
{
     return name;
}


здесь есть скрытие реализации — так как пользователь не знает возвращение это просто поля или еще что-то.
здесь есть bundle — объединение данных и кода.

Какой букве определения это противоречит?

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

> Какой букве определения это противоречит?

Понятие скрытия в определении предполагает, что класс полностью контроллирует взаимодействие с ним.
А вы что, не видите что реализация скрыта и класс полностью контролирует взаимодействие с собой?
Почитайте, пожалуйста, повнимательнее ветку. В исходном примере он как раз таки неконтроллирует. Ну, пример просто такой.
Я дико извиняюсь, но где он? о.О
habrahabr.ru/post/147927/#comment_4990497

> Псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо return this.foo и this.foo = foo, на самом деле, ничем не лучше
Вот вам три совершенно одинаковых подхода, .net:

public class Person
{

public string Name {get;set;} //first

private string _name;
private string Name
{
get {return _name;}
set {_name = value;}
}//second

private string _name;
public string GetName() {return _name;}
public void SetName(string value){_name = value;} //third

}

Код не скомпилится ессно, это пример. Во всех трех примерах соблюдены принципы инкапсуляции. Т.к. класс Person всегда может узнать что кто то захотел чего то сделать с Name. И ничего что ни один пример ничего больше не делает как возвращает значение или присваивает. Фишка в том, что если я захочу отследить момент изменения данных, то я легко это сделаю.
> И ничего что ни один пример ничего больше не делает как возвращает значение или присваивает.

Плохо то, что туда можно подсунуть что-нибудь, что именем не является, а Person будет думать, что это имя.

> Фишка в том, что если я захочу отследить момент изменения данных, то я легко это сделаю

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


А как написать такую реализацию, чтобы она получала имена и ничего, кроме них?

Если этот код уйдет в продакшн — то уже будет поздно.


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

> Всегда можно сделать хотфикс

Я не утверждал, что идеальная инкапсуляция реализуема. Более того, я не утверждал, что строго следовать этому принципу правильно. Думаете я таких геттеров и сеттеров не стряпаю? :)

Я просто говорил о том, что является, а что не является инкапсуляцией
1. Покажите место в определении инкапсуляции, где говорится о контроле сеттеров.

2. Констрактом setName может быть что в случае ввода не имени результат неопределен.
1. «A language mechanism for restricting access to some of the object's components» Сеттер — частный случай access.

2. Тогда инкапсуляция соблюдена.
А вот наличие сеттера дает возможность достучаться пользователю напрямую к внутренности класса.

Нет, не дает. Как раз setter и является тем изолятором, который контролирует взаимодействие с данными класса.
В данном случае он не контроллирует. Он ничего не проверяет, просто напрямую присваивает значение как есть.
Как вам уже неоднократно сказали, вся прелесть свойств в том, что сегодня эта реализация такова, а завтра — совсем иная.
Как я вам уже неоднократно сказал, то о чем вы говорите не относится к инкапсуляции.
Согласно приведенному вами же определению из вики — относится.
Вы правы. Успокойтесь. Либо троллит, либо… Ну не оскорблять же. Спокойной ночи. :)
Да троллит — просто игнорирует ответы, адресованные ему и всё время повторяет одно и то же увтерждение, ничем не подтверждённое.
Ну тут то вы и попались. :) Это вы видя код это знаете, а если код не видете, то вы же не знаете чего я там делаю? Верно? Вы то думаете что просто значение получаете:) Вот и сокрытие реализации. :) А я там зафигачу проверку на xss и в полицию буду сразу звонить. :) Вот сюрприз то будет. :) :) Т.е. принцип инкапсуляции выполнен? Разве нет? :)
Если «зафигачите» проверку — это уже будет другой пример, там инкапсуляция не нарушена, вероятно.
А вы, уважаемый, заканчивайте троллить. Вы же не считаете что геттеры и сеттеры инкапсуляцию нарушают? НУ вот по настоящему если? Без шуток? И дело ведь не в проверках. А в сокрытии. Если бы поле торчало, то вы железно бы знали что и как получаете/меняете, а если геттеры и сеттеры юзаются, то вы же не знаете чего там происходит. Верно? Без шуток? Но мне уже все равно, я спать.
Поскольку разговор перешел на личности, с вашего позволения я не буду его развивать.
А вот наличие сеттера дает возможность достучаться пользователю напрямую к внутренности класса.


Через сеттер, это не напрямую.

При этом, для того, чтобы что-то испортить внутри класса, пользователю совершенно не обязательно знать, как реализован этот сеттер

То есть если допустимы любые значения поля, то нарушения инкапсуляции нет?

General definition

In general, encapsulation is one of the 4 fundamentals of OOP (object-oriented programming). Encapsulation is to hide the variables or something inside a class, preventing unauthorized parties to use. So the public methods like getter and setter access it and the other classes call these methods for accessing.
Ладно мужики, я спать, жена уже сны видит. :) В целом, статья зачетная потому что на поговорить разводит, но вредная. Еще начнут думать что ООП зло и не надо никому. А геттеры и сеттеры инкапсуляцию нарушают… Брррр… До сих пор глаза кровью наливаются.
> Через сеттер, это не напрямую.

ок. Я некорректно выразился. Прямо или косвенно — это не играет роли, если нормальная работа класса в конечном итоге будет нарушена.

> То есть если допустимы любые значения поля, то нарушения инкапсуляции нет?

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

Если речь идет не о инкапсуляции, то не играет, иначе играет.

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

На практике редко контролируют 100%.
Например name — это любая строка вполне может быть.
Программа — это модель, а в модели не всегда учитывают все условия.
> Если речь идет не о инкапсуляции, то не играет, иначе играет.

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

> Программа — это модель, а в модели не всегда учитывают все условия.

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

Это ваше определение, определение из википедии я приводил.
Это определение скорее относится к defensive программированию.

Так и есть. И инкапсуляция не всегда соблюдается по этой же причине.

Однако простой сеттер или простой геттер не является нарушением инкапсуляции.
> Это ваше определение, определение из википедии я приводил

Это утверждение не противоречит приведенному вами отрывку.

> Однако простой сеттер или простой геттер не является нарушением инкапсуляции

А вот это противоречит, поскольку в общем случае нарушает «preventing unauthorized parties to use».
Если опосредованный доступ считать за accecc, то тогда смысла в определении нет, так как поля без опосредованного доступа бессмысленны.

Сеттер и геттер != поле, они не позволяют достучаться до поля а только опосредованно сообщить ему значение. Unauthorized users не юзают поле, а юзают сеттер и геттер, последние, в свою очередь, юзают поле. Если бы parties юзали поле, они бы ломались от подмены реализации.
> Сеттер и геттер != поле

habrahabr.ru/post/147927/?reply_to=4992889#comment_4993007

Вот тут ffriend дает очень хороший ответ, как мне кажется.

======

ApeCoder, вы знаете, для меня то, что в приведенном примере нарушается инкапсуляция очевидно как то, что 2*2=4. Как я понимаю, для вас совершенно очевидно обратное. Вчера я сказал очень много слов, гораздо больше чем следовало(так как еще и было настроение поспорить), чтобы объяснить почему я считаю именно так. По большому счету добавить то особо и нечего.

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

В любом случае, ApeCoder, если вы хотите продолжить дискуссию, предлагаю перенести ее в ЛС, так как тут уже просто тред разросся совершенно аномально.
А что плохого в большом ттреде?
Плохо то, что товарищи вроде veitmen начинают грубить и даже хамить: habrahabr.ru/post/147927/#comment_4992827 Мне это очень неприятно.

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

Честно приношу свои извинения. Но продолжаю недоумевать по поводу ваших доводов. :)
Извинения приняты.

Хм… Да, доводы у меня довольно так себе конечно. Я думаю, тут есть фундаментальные различия между нашими с вами подходами к программированию.

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

Так вот, в строгом ФП есть свои механизмы, позволяющие решать проблемы, аналогичные тем, которые пытаются решить в ООП с помощью «инкапсуляции». Однако в ФП языках эти проблемы решаются by-design, то есть, там в прицнипе невозможно испортить объект «подав в него» что-то не то. В то же время, решение ФП накладывает такие ограничения, которых нету в ООП. Это и плюс, и минус ООП. Минус в том, что обязанность следить за тем, чтобы пользователь не мог испортить объект возлагается на разработчика. И, часто получается, что разработчик за этим не следит.

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

Как я понял из этого треда, многие программисты в понятие «инкапсуляция» вкладывают довольно простую вещь. Собственно говоря, на этом и строились все споры — споры о том, что такое «слова».

Однако более простое понимание инкапсуляции, когда завернуть в геттер и сеттер методы достаточно, мне не нравится тем, что при таком понимании, с точки зрения других парадигм, таких как например ФП, ООП получается значительно более ущербным.
«ООП получается значительно более ущербным.» — может быть более гибкими? :)

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

ООП это сложно и много вещей надо учитывать, но я уверен что это гораздо проще в плане подхода к разработке ПО. Т.к. проще чем взаимодействие объектов программу не опишешь. Разве нет? :) Я думаю есть исключения, но которые подтверждают правило. :)
Однако в ФП языках эти проблемы решаются by-design, то есть, там в прицнипе невозможно испортить объект «подав в него» что-то не то

В ФП нет объетов (чего-то у чего есть identity отличное от state).

И каким же мобразом в ФП достигается абстрагирование от структуры данных?
> В ФП нет объетов (чего-то у чего есть identity отличное от state).

Под словом «объект» мы с вами называем разные вещи. В той терминологии, которой придерживаюсь я, в ФП есть объекты.

> И каким же мобразом в ФП достигается абстрагирование от структуры данных?

Зачем в ФП от нее абстрагироваться?
>>>Зачем в ФП от нее абстрагироваться?

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

Строгие ФП языки(без сторонних эффектов и со статической типизацией) проектируются таким образом, что в них невозможно изменить способ хранения так, что старый код начнет работать некорректно.
а так что старый код просто перестанет компилироваться?
Покажите на примере с точкой и сменой хранения с x, y на angle, distance
> а так что старый код просто перестанет компилироваться?

Да.

> Покажите на примере с точкой и сменой хранения с x, y на angle, distance

Наличие чисел в ФП языке — это элемент нестрогой типизации.
Как вариант, абстракция строится на наборе функций. На примере с точкой: есть структура Point, к которой запрещено обращаться напрямую, но разрешено через функции xCoord(), yCoord(), distance() и angle(). Ну и плюс функции для установки координат (если язык подразумевает мутабельность) или для создания нового Point. Всё, это весь интерфейс структуры Point, все операции делаются через него, и при необходимости изменить реализацию, меняюся только эти 4 точки.
>>к которой запрещено обращаться напрямую

как запрещено
Обычно — логически, и этого, как правило, хватает. Но если очень хочется, можно и средствами языка. Например, сделать функцию makePoint публичной, а саму структуру Point скрыть во внутренностях модуля. Более точный вариант зависит от конкретного языка.
А как ее скрыть во внутренностях модуля, но в то же время сделать возвращаемой makePoint?
Смотря на чём: в динамеческих языках, где это чаще вего используется, объявление типа не требуется, а на каком-нибудь C проще вернуться к плану А — большим жирным комментом «НЕ ИСПОЛЬЗОВАТЬ НАПРЯМУЮ» :)
Уважаемый, честно попробовал описать основную ошибку всей статьи вот тут. Может быть тогда вы сможете понять почему в данном примере нет ошибки?

habrahabr.ru/post/147927/#comment_4993414
Если вы уберёте из класса Person поле name, то оставлять метод getName() тоже не имеет смысла

Что за неизвестный смысл в вакууме? Класс должен возвращать name, значит он будет возвращать name. Он не может вернуть name? Здесь не нужен этот класс. О каких смыслах вообще идёт речь?
ВыгулМенеджер лишает меня удовольствия от прогулки с собакой, а деньги я получаю от бездушного БанкСчёта (эй, где та милая девушка, у которой я менял деньги на прошлой неделе?).

А вот тут хотелось бы поподробнее — в какой парадигме остаётся удовольствие от общения с девушкой? Что это за критерий такой? Или пункт асбтракция мы забыли сразу, как только раскритиковали?

А вообще вся статья производит впечатление «я придумал много новых способов сказать плохо об ООП, главной претензией к которому остаётся то, что я его не понял». Как-то оно…
Ок, неочевидно, поясню: некоторые детали в тексте имеют чисто художественное значение. Чтобы читать не так скучно было. Но если вы настаиваете, следующую статью я напишу в строгом консервативном стиле.

Я вообще нигде не говорил, что ООП — это плохо. Я указал на некоторые кривые моменты (причём в основном в конкретных реализациях и инфраструктуре), о которых размышляю уже несколько лет. Так, например, до сих пор нет чёткого определения того, что такое ООП. Сколько книг я читал, со сколькими людьми говорил, но никто не может дать точное определение этой парадигмы. А это ведёт к различию в терминологии и более плохому пониманию некоторых концепций программирования.
Чтобы читать не так скучно было. Но если вы настаиваете, следующую статью я напишу в строгом консервативном стиле

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

Хорошо, критиковать все умеем, давайте тогда больше позитива и конструктивизма :-)

Раз уж речь зашла о моделировании реального мира через ООП — я думаю это тоже такое образное выражение, которое используется для объяснения сути новичкам :-)

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

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

например, до сих пор нет чёткого определения того, что такое ООП

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

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

Есть четыре вышеперечисленных качества? Можем писать в стиле ООП. Каким бы при этом «не ООП» язык ни называли.
Нет. Можно писать в функциональном стиле, но при этом иметь все четыре атрибута ООП.
А насчет ооп элементов в хаскелле, это как функциональные элементы в Java и C# — они как бы есть, и это как бы клево, но всем пофиг.
Как там говорят — если ты выглядишь как собака, бегаешь как собака, лаешь как собака — ты собака… или другая реализующая интерфейс собаки сущность.
Все эти «разные» парадигмы являются развитием одного и того же предка. И пытаются решать одни и те же задачи. Разный, по большому счету, только синтаксис, мелкими разницами можно много томов заполнить, но принципиальных различий нет.

ЗЫ кто нибудь уже напишет статью «я не понимаю функциональную парадигму»? Или «функциональные яп не отстой»…
Все эти «разные» парадигмы являются развитием одного и того же предка.
Не совсем. Императивное программирование растет из машины Тьюринга, а функциональное — из лямбда-исчисления. Это не одно и то же.
Ну и ещё ООП в Java растёт из Smalltalk, а в Haskell — из теории категорий.
Разве лямбда исчисление появилось не раньше теории категорий?
Лямбда-исчисление ортогонально теории категорий. На лямбда исчислении построена модель, собтсвенно, вычислений в Haskell, на теории категорий — система типов оного.
Сообщите, когда сможете на Haskell унаследовать реализацию типа данных. То, что в языке есть какие-то средства, ещё не значит, что они работают так же, как в другом языке.

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

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

Только небольшое уточнение насчет унаследования реализации Haskell. Насколько я понимаю, если два типа-экземпляра реализуют один и тот же класс типов, и, скажем, один будет вызывать соответствующую функцию из второго экземпляра, это можно считать аналогом «виртуального наследования». Конечно, это довольно громоздко.

Поправьте меня, если я ошибаюсь, я вообще-то с Хаскелем почти не работал. :)
Честно говоря, слабо представляю, что вы имеете ввиду. Сами по себе типы не вызывают функции — это просто типы. Это вы, как программист, вызываете функции, которые обрабатывают данные таких-то и таких-то типов. Важно понять, что в Haskell функции не привязаны к типам, а привязаны к ним. Грубо говоря, как pattern matching.

А классы типов — это вообще интерфейсы в понимании ООП :)
ffriend, я конечно профан в Хаскеле, но такие элементарные вещи, как, например, что такое классы типов, вроде бы хорошо понимаю :)

В том абзаце я попытался изложить, как понятие наследования классов в ООП можно перенести на имеющиеся в Хаскеле инструменты. Я опустил некоторые моменты, как например, то как выразить понятие «вызова функции» В ООП в терминах Хаскеля. Получилось конечно скомканно.
Ну, я так и подозревал, просто решил уточнить, чтобы выяснить, где наше с вами понимание расходится. Извините, если обидел :)
Может, Хаскель не объектно-ориентированный язык, потому что там нет объектов? :-D
Если поспорить о терминологии, то я бы сказал, что в Хаскеле таки есть объекты, там нету состояний. :)
Давайте не будем :-)
Есть, но только иммутабельные;-)
> Но в данном случае вызывает протест несправедливый подтекст.

Опа, у меня внезапно подтекст выскочил. И в чём же он, а то я то сам и не пойму.

> Вот не знаю, как по мне два слова «наследование» и «полиморфизм» являются более чем исчерпывающим
> определением, что такое ООП.

Наследование? В JavaScript его нету.
Полиморфизм? Опять же, в JavaScript полиморфизм сводится к указателям на анонимные функции. Как и в большинстве динамически типизированных языков, кстати.
С другой стороны, в Haskell есть и то, и другое, но он ну ни разу не объектно-ориентированный язык (хотя и с поддержкой ООП).
у меня внезапно подтекст выскочил. И в чём же он, а то я то сам и не пойму.

Вообще-то с этого тред начался: «обещали пряник, а заставили работать» — «вместо общения с девушкой приходится программировать».

В JavaScript его нету.
А? Прототипы канули в Лету?

Полиморфизм? Опять же, в JavaScript полиморфизм сводится к указателям на анонимные функции.

Указатели в JavaScript?
Ну да и указатели, если бы они — чем не угодили? Они как-то не так работают, полиморфизм чем-то отличается?

Динамическая типизации к полиморфизму и наследованию — ортогональна.
> Вообще-то с этого тред начался: «обещали пряник, а заставили работать» —
> «вместо общения с девушкой приходится программировать».

Это был художественный приём. Простите, непонятно написал. Больше не буду.

> А? Прототипы канули в Лету?

Наследование /= прототипирование. У них совершенно разная природа.

> Указатели в JavaScript?
> Ну да и указатели, если бы они — чем не угодили?

То, что язык не позволяет работать напрямую с указателями, ещё не значит, что их нет ;)
Полиморфизм полиморфизму рознь. Такого полиморфизма, как в Java, в JavaScript нет. Почему не надо смешивать разные понятия с одним именем, я уже написал выше.
>>>Наследование /= прототипирование. У них совершенно разная природа.

В одной из редакций UML notation guide я видел разделение наследования и обобщения, каковое показалось мне полезным.

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

Соответственно, обобщение сопровождается наследованием. Как и создание объекта по прототипу.

См разъяснение в доке про язык self www.selflanguage.org/_static/published/parents-shared-parts.pdf

То, что язык не позволяет работать напрямую с указателями, ещё не значит, что их нет ;)

Если в реализации используется указатель, это совершенно не значит, что реализуемое — является указателем. Да и откуда Вы знаете, что там именно указатель используется, а не индекс в таблице сущностей?

Полиморфизм полиморфизму рознь. Такого полиморфизма, как в Java, в JavaScript нет.

Полиморфи́зм (от греч. πολὺ- — много, и μορφή — форма) в языках программирования — возможность объектов с одинаковой спецификацией иметь различную реализацию.

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

А в чём по Вашему разница в полиморфизмах? Чем один столь существенно отличается от другого, что Вы предлагаете говорить о разных сущностях и случайном совпадении названий?
Да и откуда Вы знаете, что там именно указатель используется, а не индекс в таблице сущностей?

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

А в чём по Вашему разница в полиморфизмах? Чем один столь существенно отличается от другого, что Вы предлагаете говорить о разных сущностях и случайном совпадении названий?

Есть подтиповый полиморфизм, есть полиморфизм с автоматическим удовлетворением типу (как в Go), есть динамическая типизация, есть параметрический полиморфизм, есть дженерики с автоматическим ограничением типа (как в Haskell), есть мульиметоды. В конце концов, в определении полиморфизма нет ни слова о методах. Технически, спецификация объекта «стул» описывает огромный набор реализаций стульев, ни один из которых никаких функций не выполняет (функции «сидеть» и «кататься на стуле» относятся уже к вам, а не к стулу).

Ну и да, объекты в динамических языках, в т.ч. JavaScript, не имеют спецификации, так что понятие полиморфизма к ним притянуто за уши.
Объекты «стул» выполняют функции типа «выдержать вес» :)
В конце концов, в определении полиморфизма нет ни слова о методах. Технически, спецификация объекта «стул» описывает огромный набор реализаций стульев, ни один из которых никаких функций не выполняет (функции «сидеть» и «кататься на стуле» относятся уже к вам, а не к стулу).

Возможно Вы выбрали неудачный пример со стулом, но мне кажется, что неверен всё-таки сам изначальный посыл, что свойство не имеет никакого отношения к методам. На этом фоне интересно отметить, что в русском языке состояния передаются глаголами — стоит, лежит, падает. То, что мы используем как прилагательные — большой, белый — является отдельными значениями свойств, а сами свойства задаются через «методы» быть (в русском языке опускаемый глагол в настоящем времени, вследствие чего неочевидный), стать. Был белым, стал серым. Причём вполне закономерно, что стал серым в результате внешнего воздействия — кто-то сделал серым. У обоих объектов методы: и у активного, и у пассивного, что в нашем языке выражается через действительный и страдательный залоги. А состояния — результаты выполнения методов.

class Амбал (Человек)
    def разбить (Хрупкий предмет)
        return предмет.ударить (об=я.найти (твёрдая поверхность), сила=100500)

Такое вот видение абстракции в ООП.

Также хочу заметить, что в действительности нет никаких стульев или ещё каких классов — это не более чем абстрактные названия для конфигураций волновых пакетов в нашем клеточном автомате, который мы называем реальностью.
Поэтому ООП — это всего лишь проекция нашего способа мышления на языки программирования. Независимо от того, поверх процедурной парадигмы (что мы и привыкли ООП называть) или поверх функциональной (что видим в том же Хаскеле, который не называют ООП языком). Так что по итогу — либо и то и другое называть ООП, либо отказываться от самого термина в его нынешнем употреблении.
Но тут ничего не поменяется: термин ООП — давно уже превратился в бренд.
Вот вы последней парой предложений просто выразили смысл статьи :)
Да, я тоже так подумал :-)
Но всё же статья — очень эмоционально и посредством достаточно спорных обобщений выражает этот смысл. Впрочем, сама завязавшаяся вокруг статьи дискуссия того стоила. По крайней мере для себя я нашёл много интересных и полезных, как мне кажется, идей и замечаний.
Мне кажется, обсуждая ООП и моделирование реального мира, стоит делать акцент на том, что ООП — это инструмент для моделирования реального мира. Само по себе ООП не даст ответы на вопросы, куда закатать наследование, какое поле скрыть, а что обернуть интерфейсом. ООП — это возможность использовать наследование, полиморфизм и что там ещё в нём есть. А вот всевозможные DDD уже отвечают на вопрос, как моделировать этот реальный мир посредством ООП.
Именно так. Причём «сферическое» ООП как парадигма ещё дальше от этого вопроса, чем ООП реализованное в конкретных языках. И одна и та же задача в разных языках будет оптимально решена разными способами.
Речь идёт об объектах, являющихся контейнерами для данных. Как правило, такие объекты отображают предметную область, а в программе проходят путь от вьюшки к базе данных и обратно. Если по какой-то причине было решено убрать некоторое поле (например, пришёл судья и приказал убрать все личные данные о людях, как это сейчас модно), то это поле исчезает и из объекта, и из базы данных, и из вьюшки. Смысла оставлять геттер тоже нет.
В описанной ситуации меняется интерфейс и логика продукта. Ну так и геттер в таком случае будет выброшен в виду смены интерфейса. Но при чём тут инкапсуляция?
Изменение достаточно незначительное. Как раз тот случай, где нужно использовать наследование, вроде бы… Но тут мы нарушаем принцип подстановки Лисков, а значит придется менять интерфейс, логику и т.д. А это — написание нового кода. И все, приплыли — ООП оказывается очень плох в том, что касается повторного использования кода.
Я не понимаю. Может это был самый неудачный пример от визави, но в ТЗ сказано «убрать любой ценой», при чём тут ООП? Я полагаю, что класс Person проектировался не для того, чтобы в нём исчезло поле, являющееся одним из его смыслов, а также станут неработоспособны все компоненты, которые писались с расчётом на то, что этот класс будет предоставлять такой интерфейс, а они даже близко не должны быть хоть сколько-то «родственными» классу Person или даже друг другу. Это пример вообще не про ООП — в любой реализации, из которой вынимают фундамент, будет подобный хаос, независимо от парадигмы.

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

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

Именно так. Наличие в руках молотка, не означает, что кругом только гвозди, хотя подобные мысли могут и мелькать ))
Так речь же не о фундаменте, а об изменении поведения всего лишь одного поля.


Зачастую удаление одного поля ведет к коренной переделке программы, безотносительно используемого языка, парадигмы и фреймворка. Лишение всех людей своих имен — именно тот самый случай.
Не всех, а, скажем, новорожденных. Или свидетелей по уголовному делу, чье имя не должно предаваться огласке. Или анонимных доноров.
Почему изменение поведения 5% записей должно вести к коренной переделке программы?
Если это всего лишь 5% записей, то метод getName никуда не девается, а продолжает либо возвращать пустую строку, либо кидать исключение!

Выше рассматривался случай, когда поле name пропадает «настолько сильно», что его геттер также должен исчезнуть.
Начинает кидать исключение — это замечательно.
Как быть с кодовой базой, написанной задолго до этого, и которая не ожидает того, что этот объект может кинуть такое исключение?
Это как вам вопрос вообще-то.
Как вводить новорожденных в проект, которых до того только со взрослыми работал?

Вы что доказать пытаетесь? Что добавить работу с новорожденными в структурном программировании проще? Или что ООП не решает поставленных перед ним задач?

Сформулируйте, пожалуйста, ваш тезис, который вы доказываете. До этого момента спор бессмысленен.
Да, на случай встречного вопроса.
«Наличие простых гетеров и сеттеров не нарушает принцип инкапсуляции».
Почему обязательно «вводить в проект»?
Может быть написать новый проект, в котором хотелось бы использовать уже готовые, протестированные наработки.
А нельзя.
P.S. что же вы лезете спорить, если не знаете о чем разговор идет?
Я знаю, о чем спор начался, но я перестал понимать, как ваши аргументы соотносятся с предметом спора.

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


Почему изменение поведения 5% записей должно вести к коренной переделке программы?


Определитесь, пожалуйста, это всего лишь «изменение 5% записей», или написание нового проекта.
IRL новорождённых идентифицируют по матери — сразу после рождения прикрепляют соотв. бирку на руку, по крайней мере в 2000 так было.

Свидетель и анонимный донор — это не люди, а роли, делать их потомками класса Person сегодня рассматривается как bad design. Надо не наследовать, а агрегировать.

Для новорождённых тоже можно сделать отдельный класс, не наследуемый от Person.
Вроде ещё в 1987 решили, что при наследовании ничто не должно скрываться и удаляться… Liskov substitution principle — не оно?
Да, о нем и речь.
Вот вам дался этот name, пусть будет weight, и использовался он в одном месте — при отображении на вьюшке, ни в какой логике не учавствовал.
А какая разница какое поле. Тот же wight, если он объявлен в интерфейсе, а затем из этого интерфейса будет удалён, то чем больше компонентов реализовало этот интерфейс, тем масштабнее «разрушения».
Пример будет справедлив для любого поля в такой ситуации.

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

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

Но это так же относилось бы и к функциональному программированию, если бы кто-то взял и удалил одну из функций, которую использует значительная часть кода — код бы просто оказался нерабочим. Здесь то же самое, только в пространстве конкретного интерфейса.
Поразительно, насколько большую дискуссию вызвал один маленький пример. Пример касался инкапсуляции, а именно того, является ли создание getter/setter-методов проявлением оной. Привёл я этот пример, чтобы показать неразделимость и эквивалентность поля с данными и методовов доступа к нему в объектах DTO. Если на текущей итерации проекта логика подразумевает удаление свойства name, то это означает удаление как поля, так и методов доступа к нему. Конечно, если удаление поля потребует серьёзного и нежелательного рефакторинга (хотя если решили удалить поле, то либо оно мало где использовалось, либо команда готова к рефакторингу), то можно его на время оставить. Но здесь нет разницы — удалить поле и оставить пустые методы или удалить и то, и другое. Надо удалить — удаляй и рефактори, не хочется удалять — не удаляй, оставь на время как есть.
В случае акцессоров можно заменить реализации так что геттер будет всегда возвращать NULL или пустую строку, а сеттер будет просто пустой вызов. А если оставить поле, то придётся клиентский код изменять.
>>>проекта логика подразумевает удаление свойства name, то это означает удаление как поля, так и методов доступа к нему

А если подразумевает удаление поля, но не подразумевает удаление свойства?
Слабо себе представляю такую ситуацию, но если уж так получилось, и вы решили в своей программе использовать паблик поля, то проще уже оставить поле и не мучаться.
>>>Слабо себе представляю такую ситуацию,

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

>>>использовать паблик поля, то проще уже оставить поле и не мучаться.

Поле не паблик, паблик — свойства. Просто первоналальная реализация — получение поля.
Не, я имел ввиду конкретную реализацию, типа `public String name`.
собственно, если не нужна бинарная совместимость, то в C# можно и так.
А в джаве — нет
БОльшая часть дискуссии была вызвана смежной темой удаления геттера с сеттером.
Поле weight, вместе со своим свойством и геттерами-сеттерами, используемое только в одном месте — это грубейшее нарушение принципа абстракции.
Вместо класса «Человек» получился класс «Человек, которого иногда рисуют на форме».

Неудивительно, что другие принципы также затрещали по швам.
Какое отношение абстракция имеет к количеству раз использования?
«убрать любой ценой» — это ценой провала проекта, потери всей своей доли рынка и штрафных санкций за неустойку от того, что с новой версии продукт стал работоспособен на 0%, — тоже?
Смысл ООП в том, что объекты взаимодействуют друг с другом по средствам предопределённых сообщений, и каждое сообщение ведёт к некоторому действию. О внутренней структуре друг друга объекты не должны знать просто идеологически — им должно быть всё равно из чего состоит другой объект, главное, что тот объект может сдедать. Сейчас эта идея развивается в виде agent oriented software и SOA, когда разные узлы в сети обмениваются сообщениями с просьбой что-то сделать. Один сервис не должен знать, что у другого внутри. У другого сервиса не должно быть протокола для получения его состояния, количества минут после запуска и соединений в данный момент — вся эта информация скрыта, ибо бесполезна для сервиса-пользователя.

Объекты-контейнеры не вписываются в такую парадигму. Они несут данные. В SOA объектами контейнерами являются сообщения, передаваемые между узлами. В таких языках как Java и C# объекты-контейнеры смешаны с обычными объектами, отсюда возникает неразбериха. Лично я стараюсь чётко разделять функциональные объекты, и объекты-контейнеры.
Мне даже возразить нечего. А о чём мы тогда спорим? ))
Ну, как я понял, вы воспринимаете метод getName() как смысловой, очень важный в логике. Я же говорю о нём как просто о «праведном» способе достучаться до данных, переносимых объектом.
Поле — ничто, оно может быть, его может не быть — кому какое дело до того, что в чёрном ящике.

Метод — часть интерфейса. Смена важного интерфейса = «слезай, приехали».

При удалении поля или создании пяти полей вместо одного, метод остаётся, меняется его устройство.

При удалении метода… ну тут уже выше написали, с кем и что делают благодарные программеры из зависимых проектов.
Если вы оставите методы setName() и getName(), которые будут просто кидать исключение FieldDoesNotExist, то программерам из зависимых проектов будет ни разу не легче.
Именно так — интерфейс летит ко всем чертям вместе со всем зависимым кодом, поскольку зависимый код ожидает от класса, что с name он работать умеет и будет.

И без объектов будет такая же досада — если функция ожидает, что в структуре просто обязано быть поле name, а его оттуда убирают — все функции, работающие с этой структурой уже не будут работоспособны.

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

Но, как было сказано выше, контейнеры данных — это не объекты, и инкапсулировать их не надо.
Пусть у нас есть класс Person, который знает про Имя и Фамилию, и может их возвращать
Добавляем в программу класс Newborn: extends Person. Новорожденный, который еще не имеет имени. Готовы ли программы к тому, что новый класс при вызове метода getName() может бросить исключение NameNotDefinedYetException, передать NULL, или вернуть фамилию/рег.номер?
Про мозгосносящий конфликт наследования эллипса-круга я уже молчу (нет, он не сложный, но он заставляет крепко задуматься об ООП парадигмах).

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

В целом, с именами всё не так просто, чтобы использовать их вообще в качестве поля — тут на эту тему как раз недавно была хорошая заметка

Про мозгосносящий конфликт наследования эллипса-круга я уже молчу

Можно чуть подробней о чём идёт речь? Но я наверно предположу, что речь идёт про то, что некий класс Эллипс наследует класс Круг? Это бессмысленное наследование, поскольку круг является частным случаем эллипса, а не наоборот. Но наоборот часто пытаются сделать не понимая этой логики, а просто следуя желанию сэкономить поле в структуре данных. Индуктивное правило «наследники усложняются по сравнению с предками» — ложно.
Можно чуть подробней о чём идёт речь?

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

А если для immutable будем использовать Factory для построения объектов? По параметрам для конструктора он может выбрать правильного (с точки зрения задания) наследника.
Для immutable проблем нету, особенно с фабрикой.
А для mutators я не пойму
1) Чем поведение типа — при stretchX круг растягивается равномерно плохо? Кроме того, при проектировании подобных графических элементов я бы логично ввел свойство LockAspectRatio (что можно наблюдать в графических редакторах).

2) Ну и по большому счету (исходя из условий задачи) — с точки зрения процедуры модификации объекта и именно в тех условиях, что поставлены в задаче в вики, круг не является элипсом. Попахивает подменой понятий в условии задачи.
1. Не подходит. Не понятно, что будет, если вызвать scaleX(2); scaleY(2); Эллипс бы растянулся в два раза, а вот круг растянется в четыре.

2. Подмена понятий произошла не в условии задачи, а в формулировке наследования и отношения is-a в ООП парадигме. Данная задача наглядно это демонстрирует.
Как раз LockAspectRatio и разруливает такое поведение. Более того, сущность Circle, как фиксирующая определенное состояние объекта, становится избыточной. С тем же успехом мы можем придумать сущности для фиксации rX=5, rY=8 итд. А значит задача надумана.

Кроме того вы сами выше продемонстрировали что при модификации поведение объектов различно. При этом у круга появляется инвариант — равенство радиусов rX==rY. А значит нельзя говорить что круг это эллипс с данной точки зрения (круг имеет свою логику масштабирования).
Какая прелесть ;)
Очень жаль, что этого нет в популярных языках.
Несовместимо с тем, что классовая принадлежность объекта и иерархия классов фиксированы (как в самых популярных языках).
С иммутаблями через фабрику — можно и при фиксированности классовой принадлежности.
Глупая проблема.

«Вам шашечки или ехать»? Задача заключается в построении идеальной иерархии наследования при странных ограничениях, или в написании библиотеки для работы с кругами и эллипсами?

Решений у этой проблемы куча.
1) Общая база: EllipseBase. Введение объекта, который имеет все свойства эллипса, но имеет только методы изменения, поддерживаемые окружностью.

2) Неизменяемые объекты. В результате операции масштабирования должен создаваться новый объект. Да, в таком случае теоретически возможна ситуация, когда у некоторого эллипса сравняются оси, и он станет кругом, но будет продолжать иметь тип Ellipse. Но практически само условие «у эллипса сравнялись оси» не имеет смысла из-за ограниченной точности вычислений с ПЗ.

В той самой статье приведено еще множество возможных решений.
Кстати, возвращаясь к парадоксу эллипс/круг, ромб/квадрат и т.п. — возможно ошибка кроется ещё и в том, что круг в действительности не является другой, а тем более наследующей, сущностью, а лишь частным случаем состояния изначальной. В итоге может получиться архитектура, где объект со свойствами фигуры — один, но у него есть диспетчер поведения, который может выбрать разные реализации под конкретные случаи и уже у классов поведения будут разные методы модификации экземпляра фигуры, но не у самой фигуры, а экземпляры классов поведения будут неразрывно связаны с породившим их экземпляром фигуры. То есть будет mutable Фигура и immutable морфинг_фигуры.
То есть вопрос здесь не в ООП, а лишь в том, что в любой парадигме можно выбрать неудачный способ решения задачи. Кроме того, сама постановка «парадокса» не уточняет такой важный параметр, который ожидается от поведения системы в целом — наследники должны переопределять правила поведения или не должны. Под что система проектируется, таков и путь решения задачи и возможно это моё утверждение действительно ставит под вопрос абсолютную безусловность пункта L.
Ну и, как выше уже была попытка привести в пример эллипс с lockedAspectRatio — наличие такого свойства у эллипса уже даже без наследования приведёт к разному поведению кода

fig.stretchX(2).stretchY(2);

для разных состояний поля lockedAspectRatio, независимо от того, равны оси или не равны.

В общем, после некоторой медитации над этой задачей прихожу к выводу, что здесь проблема именно в попытке разделить сущность круга и сущность эллипса на две различные, в то время как это недопустимо, следуя из use cases модели. Подмножества состояний объекта не проецируются прямо на подмножества моделей поведения объекта. Решать задачу «в лоб» — будет ошибкой.
Частный случай — и есть показание для наследования.
Парадокс в том, что ООП наследование не позволяет реализовать уменьшение интерфейса. А это — нормальное явление в реальном мире (айФон не поддерживает функцию «Заменить аккумулятор», у цифрового телевидения нет функции «Изменить частоту вещаемого канала», а у круга лишь один радиус).
Эти проблемы можно решить только постфактум — когда выясняется, что эти методы/свойства больше не принадлежат всем классам, то начинает меняться иерархия классов, появляются функции вроде isFeatureExists() и так далее.
Еслиб поддерживал, это нарушения LSP и тогда, деларацию того, что функция работает с эллипсами не стоила бы ничего.

> ООП наследование не позволяет реализовать уменьшение интерфейса.

Да, так и есть. Наследование в ООП — это расширение интерфейса и/или переопределение методов с условием, что метод может заменить исходное предусловие эквивалентным или более слабым, а исходное постусловие — эквивалентным или более сильным. Поэтому и нельзя наследовать круг от эллипса. В таких случаях, а также когда необходимо уменьшение интерфейса следует применять факторизацию, т.е. вынесение общего в абстрактный класс.
Тут всё сложнее. Дело в том, что данный частный случай — это не уменьшение интерфейса, а его увеличение, поскольку круг по прежнему сохраняет все свойства эллипса и при определённой модификации он просто лишается своих частных свойств, что прекрасно отражено в примере с Immutable реализацией.
А вот смена логики работы методов stretch..., когда вместо растягивания по одной оси круг начинает растягиваться по двум осям — действительно неправомерна. Для изменения двух осей у круга должен быть отдельный метод вроде stretchRadius. Тогда принцип подстановки Лисков нарушен не будет.
Да, но при растягивании по одной оси круг перестаёт быть кругом, а значит методы stretch… должны либо нарушить правила наследования (установив более сильные предусловия и нарушая LSP), либо должны кидать исключения (что по сути и есть уменьшение интерфейса), либо должны возвращать объект другого типа.
Именно что в результате растягивания круг перестаёт быть кругом. Круг — это не отдельная независимая сущность, а лишь частный случай возможного состояния эллипса. Круг не перестаёт быть эллипсом, сохраняя все его свойства. Методы круга не должны нарушать логику поведения эллипса. Если эллипс менял пропорции осей, то и круг должен их поменять, потеряв при этом свойства круга.

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

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

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

Обычно её решают как раз тривиально, в данном случае метод просто будет всегда возвращать Ellipse, а в более общем случае IFigure.
А абстрактный пример с окружностью и эллипсом сильно надуман и конкретная реализация зависит от контекста применения данных классов. В целом можно сказать, что вариантов реализации, не нарушающих SOLID, в данном примере вполне хватает.
Нарушение LSP, как мне кажется, в большинстве случае является следствием попытки затащить в статическую систему типов динамические свойства объектов.
Более того, объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C# и т.д.

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

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


f = fopen("filename", "rw");


На что «ссылается» f? На файл на диске, на устройство ввода-вывода, на удаленный компьютер? Или filename — это ссылка на /dev/null?
Заметьте, это все сделано на чистом C, без классов.
Заметьте, что f — файловый дескриптор, который в сущности ссылается на поток байтов (хоть это файл на диске, устройство ввода-вывода или удаленный компьютер). И естественно, что вы можете работать с потоком байтов как с потоком байтов.
Общение с БД — это в сущности тоже поток байтов ;)
Да, поток байтов. Вот только формируя и передавая его, вы работаете не с регистрами/памятью напрямую, а, грубо говоря, пользуетесь удобными абстракциями в виде функций fopen, fread и прочих.
Хотя нет, вы пользуетесь абстракциями уровнем выше, которые предоставляет вам драйвер БД, и работаете с запросами, а не с потоками байтов.
А теперь нужна абстракция, чтобы скрыть работу с драйвером, а работать с общим для баз данных интерфейсом. И реализовать ее в парадигме ООП будет намного быстрей и изящней.

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


Пожалуйста, не путайте объектно-ориентированное программирование с объектно-ориентированным синтаксисом.

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

Хм. Возможно речь идёт не про ООП, а про конкретные языки? Это вполне стоит целой серии статей.
Ну да. Мало ли что в википедии про ООП написано? М.б. это и правда, а может быть — marketing bullshit для неофитов.
Можно начать с конкретных языков, охватить 95% рынка, а затем сделать обобщающие выводы. Может быть и правда, что-то не так в датском королевстве?
Говоря про языки я имел в виду наверно немного другое (если я Вас правильно понял).

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

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

Википедия даёт 2 определения того, что такое инкапсуляция. Первое — ваше, и мне оно не нравится, т.к. не понятно, чем тогда инкапсуляция отличается от, собственно, объекта. Второе определение: языковой механизм ограничения доступа к определённым компонентам объекта, т.е. именно то, что описал я.

> Абстракция (а точнее АТД)

Не совсем верно. АТД по определению покрывает именно типы данных, понятие абстракции гораздо шире. Почитайте SICP: там как раз первые две главы описывают абстракцию по средствам процедур и по средствам данных.

> Например, абстракция «база данных», скрывающая низкоуровневую реализацию, и позволяющая работать с разными
> базами данных через единый интерфейс (достаточно будет, например, просто указать нужный «драйвер»).

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

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

Не понял, каким образом вы сравниваете инкапсуляцию и объект? Если:
Я имею ввиду, что инкапсуляция — это, по первому определению, связывание данных и методов; объект — это как раз получившаяся связка.

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

Не совсем верно. АТД по определению покрывает именно типы данных, понятие абстракции гораздо шире. Почитайте SICP: там как раз первые две главы описывают абстракцию по средствам процедур и по средствам данных.

Именно. В SICP и говорится, что абстракции лежат в основе программирования (и парадигм программирования). Зачем выделять их особенно для ООП — непонятно.
С другой стороны, открываем «Совершенный код», листаем до главы 6.1 и читаем про «АТД в основе концепции классов».
Возможность использования единого интерфейса для работы с разными БД — это конкретно полиморфизм, он там на пару пунктов ниже идёт.

Используется более общий, «абстрактный» интерфейс для работы с БД, а более специфичная реализация сокрыта. Чем не пример абстракции? А уж используется ли тут полиморфизм — менее значимо.
Инкапсуляция в данном случае — когда вы оперируете не напрямую с данными, а со свойствами объекта.

Ну это уже ближе к сокрытию информации, т.е. ко второму определению.

Зачем выделять их особенно для ООП — непонятно.

Вот и я о том же, тем не менее, в ряде источников (в т.ч. Википедии) поступают именно так.

Используется более общий, «абстрактный» интерфейс для работы с БД, а более специфичная реализация сокрыта. Чем не пример абстракции? А уж используется ли тут полиморфизм — менее значимо.

Вы в изначальном посте сказали, что ООП предоставляет абстракции чуть более выского уровня. Я предположил, что вы имеете ввиду возможность реализации интерфейса к БД через полиморфизм. Если это не так, то я не совсем понимаю, о каком именно более высоком уровне абстракций идёт речь.
В той статье, кстати, инкапсуляция, абстракция, полиморфизм и наследование названы не «принципами», а всего лишь «важнейшими механизмами» ООП.
Ваша проблема не в ООП, а в том, что вы решаете пытаетесь решить проблемы до их возникновения.
Может быть, предвидеть и предотвратить потенциальные проблемы? Не вижу в этом ничего плохого.
Плохо то, что у вас это не получается, и вы начинаете обвинять инструмент.
Хотя возникновение непредвиденных проблем — в общем-то, нормальная ситуация.
Я начинаю обвинять плохо предсказуемый инструмент в том, что он плохо предсказуем? Ну да, так и есть. Это логично, как бы.
Приведите пример такого инструмента, который не создаст проблем после слов начальника «да, все хорошо, но надо бы добавить еще и такую вот фичу...»
ООП — причем сделанный в чистом стиле, гарантирует расширяемость без проблем.
Ну вот, сейчас все начнется по второму кругу…
Уважаемый tac, если вы не заметили, то автор статьи уже который час объясняет нам всем, что ООП сам по себе ничего не гарантирует.
К сожалению, вокруг ООП уже ходит столько мифов, что автор статьи, разочаровавшись в этих мифах, почему-то разочаровался в ООП.


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

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

Гарантирует. Другое дело, что на ООП- языках можно нарушить все мыслимые законы ООП.
Нет. Расширяемость гарантирует правильная архитектура приложения.
правильная архитектура приложения — может быть сделана ТОЛЬКО в ООП стиле :)
Надеюсь, это шутка?
Да, нет. В другом стиле — вы не минуемо закладываете ошибки, которые затем сказываются на расширении. Это доказано для процедурного стиля. Для НОВОГО функционального типа Haskell — не так очевидно, т.к. там для все сперто из ООП, и имеет свои аналоги, единственно вкинули свою идею ограничений, и недоделали понятие объекта. И кроме того, усложнили логику написания своим глупым синтаксисом. В то время как в ООП можно по прежнему в легком синтаксисе просто добавить ограничения, их иногда закидывают (представляют) в кучки кода тестирования, а те кто не занимается подобное фигнистикой как авто тестирование — создает ограничения как часть поведения классов.
Даже не знаю, стоит ли объяснять, почему это бред.

Вирт и Гуткнехт за 3 года с групкой студентов написали компилятор и операционную систему, не создав ни одного объекта.

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

public class SuitList
{
  private ArrayList SuitArray = new ArrayList();
 
  public bool Add(Suit argSuit)
  {
    if (argSuit.IsSuit(argSuit) == false)
    {
      return false;
    }
    SuitArray.Add(argSuit);
    return true;
  }
  public Suit GetItem(int Index)
  {
    return SuitArray[Index] as Suit;
  }
}
 
public abstract class Suit
{
  public bool IsSuit(Suit argSuit)
  {
    bool retValue = true;
 
    spades locSuit1 = argSuit as spades;
    hearts locSuit2 = argSuit as hearts;
    diamonds locSuit3 = argSuit as diamonds;
    clubs locSuit4 = argSuit as clubs;
 
    if (locSuit1 == null && locSuit2 == null && locSuit3 == null && locSuit4 == null)
    { retValue = false; }
 
    return retValue;
  }
}
 
public sealed class spades : Suit //(♠) 
{}
 
public sealed class hearts : Suit //(♥)
{}
 
public sealed class diamonds : Suit // (♦)
{}
 
public sealed class clubs : Suit // (♣)
{ }
 
таким образом, вводим в ООП ваши алгебраические типы — и дальше кодируем в ООП. И используем их где хотим.
И да, метод GetItem лучше заменить индексотором

public Suit this[int Index]
{
  get 
  { 
    return SuitArray[Index] as Suit; 
  }
}
 
Я надеялся, что вы хотя бы фабрику вспомните.

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

Ну и, к тому же, ваш код намноооого сложнее воспринять, чем

data Suit = Spades | Hearts | Diamonds | Clubs
Фабрику я не вспомню — потому что это бред, не связанный с ООП, а есть частность от COM.

Нет мой код воспринять как раз легче, т.к. он показывает реализацию, а у вас показан лишь синтаксис, и все скрыто за траслятором. Я уже говорил, что сделать фреймфорк, в который будет переданы 4 типа Spades | Hearts | Diamonds | Clubs, только через запятую также легко. Вы же видите в коде реализацию, а не декларацию. Поэтому сравнивайте вещи которые можно сравнивать.
«месяцы в году» — смотря для каких целей, DateTime — уже не канает?
Поставлю вопрос по другому: как бы вы реализовали DateTime? Пункт об ограничениях остаётся.
«перечислимый тип со строго ограниченным набором возможных значений»

— enum
У перечислений нет конструктора типа, попробуйте с их помощью задать выражения для парсера или хотя бы бинарное дерево.
Окей запилите следующий вариант на enum
data Color = Black |  White | LightGrey | RGB Int Int Int
...
doSome :: Color -> Whatever
doSome color = ...
...
doSome Grey
...
doSome Black
...
doSome (RGB 125 0 0)

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

В ответ могу предложить реализовать классические классы, с наследованием и динамическим полиморфизмом, в функциональном стиле.

А по поводу высказывания tac: подтверждаю, это ерунда, причем ничем не подтвержденная.
> реализовать классические классы, с наследованием и динамическим полиморфизмом, в функциональном стиле

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

во как мы её уделали за полчаса программирования на ООП :)
Вы этот костыль называете «уделали»? Вся прелесть ADT — в проверке на уровне транслятора.

Без этой проверки все ограничится максимум выкинутым исключением, с которым еще и непонятно что делать. То-то образуется пользователь, когда это исключение ему все-таки вывалится!
Речь шла о том, можно или нет это проверить в ООП. Там может быть и получше решение, если бы C# разрешал бы делать

private abstract class Suit

в то время как наследники public — но компилятор не разрешает — это к разработчикам.

Но все это такие вымышленные ситуации, которые нормальный разработчик в здравом уме обрабатывать не будет.
Это не вымышленная ситуация, а жизнь, суровая и неприятная. Чтобы далеко не ходить, реализация AST в .net 3.5 и выше.
Ну, тот же вопрос с цветами… ну если программист, что-то наследует от Color — он что совсем сбрендил, чтобы создать наследника не являющегося цветом? Зачем это перепроверять? А если он расширяет набор цветов — то это на порядок удобнее делать — осуществляя наследование. А в функциональном стиле, видимо нужно исправлять ранее введенное ограничение — а если это разные .dll — все труба. И это одна из причин почему ФП — менее удобно расширять !
Разработчики библиотеки System.Drawing изящно выкрутились с классом Image: сам класс публичный, но его единственный конструктор — internal. С мастями можно поступить так же.

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

Вместо type A = B | C можно было написать type A = B; type A = C (извиняюсь за «чужой» синтаксис).

Подобные расширяемые ADT ложатся на ООП очень хорошо…
Правильно про internal я то и забыл. Спасибо за подсказку!
Проверил, не-а, internal не годится. Объект то они не создадут, но дело то в другом. Нужно запретить создавать наследника, кроме 4 разрешенных. А раз класс public, то создать запрещенного наследника не проблема, а в нем переопределить конструктор — плевое дело. И вуа-ля работает запрещенный наследник.
Нефига. Конструктор наследника обязан вызвать конструктор базового класса, а он internal.
> Конструктор наследника обязан вызвать конструктор базового класса

Да, не фига. Ничего он не обязан — это моя добрая воля вызывать конструктор базового класса или нет. Я перепроверял если что.
Это я перепроверял, если что
Код в студию!
Давайте ваш код, где " Конструктор наследника обязан вызвать конструктор базового класса". Мой код все тот же сверху, только в класс Suit, добавьте

internal Suit() { }
public abstract class Suit
{
    internal Suit() { }
}

public sealed class spades : Suit //(♠) 
{ }

public sealed class hearts : Suit //(♥)
{ }

public sealed class diamonds : Suit // (♦)
{ }

public sealed class clubs : Suit // (♣)
{ }

// В другой сборке

public sealed class NoSuit : Suit { } 
// ошибка CS0143: Для типа "Suit" не определен конструктор
Да, точно! Это моя ошибка.
Но тем лучше, значит используя разные сборки можно запретить, проблема остается только в рамка одной сборки, что не существенно. Возращаемся к тому, что ООП — крут :)
это моя добрая воля вызывать конструктор базового класса или нет

Фигвам. Если конструктор не вызвать явно, будет вызван конструктор без параметров (если такой есть; если его нет, создать конструктор, не вызывающий базовый явно, не удастся). Соответственно, если прав на этот конструктор нет, не взлетит.

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

if (locSuit1 == null && locSuit2 == null && locSuit3 == null && locSuit4 == null)


А это вообще шедевр, с месяцами видимо 12 штук будет. А с цветами я себе боюсь представить. Китайцы в восторге.
Ну так пример я привёл именно чтобы показать, что далеко не все полезные концепции можно красиво реализовать средставами ООП. Обратное, естественно, также справедливо (правда объекты ООП довольно просто реализуются через замыкания, но если брать Haskell, то в нём нет состояния, а если какой-нибудь Lisp, где состояние есть, так там и ADT нету).
Так кто спорит что одни концепции легче реализовывать, другие сложнее в рамках конкретного языка, но почти все возможно. Даже на С можно писать ОО программу. Только больше нужно будет решать на уровне договоренностей между программистами. Договор по наименованию функций, структур и модулей, например.

Сам по себе ОО язык не заставит человека проектировать в терминах объектов :)

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

ООП — это всего лишь ещё один пункт на пути абстрагирования.
Я лично не понял тему про 95% кода в каком-то стиле. Если ООП не подходит под ваше описание технологии — это значит, что проблемы в ООП, а не в описании?
Про 95% я знаю только одно реально действующее правило, и оно не только про программирование )

В конечном итоге понятие ООП должно помогать, а не вводить в диссонанс. Если оно перестает помогать, перестаньте его использовать.
Я лично не понял тему про 95% кода в каком-то стиле. Если ООП не подходит под ваше описание технологии — это значит, что проблемы в ООП, а не в описании?

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

Что касается предсказуемости, то тут, наверное, надо говорить сразу и про сайд-эффекты. Возьмём для примера классических оппонентов — ООП и ФП. Причём ФП не строго типизированное, как Haskell, а какое-нибудь динамическое типа Clojure. В Clojure я строю архитектуру на основе функций. Для функций нужно определить только две вещи — входные и выходные параметры. Вариант использования у неё тоже только 1 — вызвать её. Т.е. кто бы и где бы ни использовал нашу функцию, всё, что он сможет с ней сделать, — это вызвать. Если же говорить про ООП, то мы начинаем проектировать архитектуру начиная с классов. А вот для классов и атрибутов, и вариантов использования гораздо больше. Начнём с того, что нам надо решить, будет ли объект класса создаваться через new, или в нашей системе лучше использовать для этого фабрику. Дальше нужно решить, будем ли мы позволять наследоваться от нашего класса, и если так, то какие элементы мы разрешим изменять (модификаторы private/protected). Очень быстро нам становится тесно в одном классе и мы начинаем оперировать связками классов, делая предположения о возможных отношениях между ними в будущем. Ну и т.д. И каждое решение строится на предположениях, как будет лучше, а предположения, очевидно, могут оказаться неверными. ФП «отбирает» у нас свободу, но зато становится проще просчитать варианты изменений. ООП, наоборот, даёт полную свободу, но не даёт никаких подсказок, как именно делать, чтобы потом не было больно. Это ни в коем случае не критика ни ООП, ни ФП, это просто положение дел.

Лично я предпочитаю, чтобы ООП было опциональным для языка — когда есть задача, хорошо ложащаяся на ООП, оно здорово помогает. Но когда на ООП приходится решать задачи, плохо под него ложащиеся, тогда велик риск получить батхёрт.
Хех, оказывается я проектирую ООП архитектуру яастично в ФП стиле. :) Сначала метод, его вход и выход, а потом уже в процессе рефакторинга переношу часть параметров в поля и инициализирую их конструктором/сеттерами.
Полезная парадигма: позволяет удобно упаковывать код. Особенно при проектировании методом рефакторинга.
Что-то я не понимаю, вы сейчас говорите о написании реального проекта или о каком-то абстрактном коде, который нигде не будет использоваться?
Не имел дела с ФП дальше лабораторок, но врядли функции там нерасширяемые. Думаю, Template Method работает и там, и в этом случае тоже есть много вариантов использования функции.
Ну а если нет, тогда программисту придется либо писать новую функцию, которая устроит его, либо переписывать существующую.
Для меня нет никаких проблем с решением о том, для чего и как мой класс должен использоваться. Предположения о том, как будет лучше — это вообще основная часть работы программиста, если он код пишет не по инструкции.
Если большая часть вашего кода по факту написана в процедурном стиле (данные, методы, объекты пораждаются в основном для выполнения каких то действией), а ООП-шные фишечки (хотя бы те же наследование, полиморфизм и т.д.) используются относительно редко, то мне непонятно, почему в общем и целом это называется ООП
По факту это модульное программирование. От ООП отличается только отсутствием неявного аргумента this</e>;-).
Наверное, надо его хвалить за то, что он эмерджентен, разнообразен и удивителен;-)
>Показательно, что Scala, которую называют наследницей Java, во многих местах по умолчанию разрешает передавать только аргументы именно указанного типа, хотя это поведение и можно изменить.

Можно с этого места подробнее? Не замечал такого за scala. Ковариантность — это несколько другое и в C# есть.
Речь идёт об обобщённых коллекциях. Если у вас есть функция, принимающая, скажем, Array[Person], то по умолчанию она не станет принимать Array[Child], хотя Child и наследуется от Person. Вообще вы правы, надо было расписать как следует, иначе смысл немножко теряется. Но получилось итак много, так что простите грешного :)
Таки да, речь о ковариантных типах. В C# есть, в java пока с этим сложнее.
Это логично. Я больше скажу, переменной типа Person[] нельзя присвоить Child[] (безо всяких обобщенных коллекций!), и это правильно.

Кстати, в C# четвертой версии появились обобщенных интерфейсы с ослабленным ограничением: IList по-прежнему нельзя привести к IList, а вот с IEnumerable<> так делать уже можно.
Извиняюсь, предпросмотреть забыл…

IList<Child> по-прежнему нельзя привести к IList<Person>
IList.Cast.ToList(), не?
блин, парсер съел

лучше по факту уже, чтобы в машине не возить такие опасносте

IList<Child> blabla;
blabla.Cast<Person>.ToList()
Вы действительно не можете различить приведение типа и полное копирование списка?
полное копирование списка
А не проксирование?
Нет, метод называется все-таки
ToList, а не ToIList.

Внутренняя же реализация List<> не поддерживает проксирования.

Ну и, конечно же, согласно документации, все методы вида ToXXX — копирующие (в отличие от проксирующих AsXXX)
Спасибо за просвещение. Я не спец в C#.
просто красивая статья
Интересный пост. Есть много спорных моментов, но я опущу свою педантичность и соглашусь с общим смыслом текста.
Не холивара ради, но, как по мне, так очень близок к идеалу код на чистом С. Возможно я слишком консервативен…
Более того, объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C# и т.д.


Мне кажется здесь вы перепутали инкапсуляцию с доступом к свойствам. Часто это взаимосвязанные вещи, но не тождественные.

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

А инкапсуляция — это уже сокрытие реализации. К чему вам видеть, что у объекта имеется десять вспомогательных методов, которые выполняют «грязную работу»? Объект предоставляет вам красивые публичные методы для работы (меню ресторана), и не пускает вас на кухню, чтобы вы не лезли с советами к шеф-повару. :)

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

Полиморфизм — это тоже весьма сильное средство в умелых руках.

ООП даёт вам возможность, сообразуясь с вашей целью, написать соответствующий «язык» на основе другого «языка». Фреймворки — это, по большому счёту, некоторое специфическое подмножество языка (С, Java, PHP — не суть важно) которое предоставляет определённый формат работы с ним. Если он вам подходит — отлично, меньше лишней работы. Не подходит — пишите своё или ищите подходящий вашим задачам.

Это не проблема ООП, это проблема выбора. :)
Если мы берём функциональный объект, то прямой доступ к данным может навредить гораздо больше, чем использование приватных методов. Если же речь об объектах-контейнерах, то в них тупо не будет других методов, кроме getters и setters (ну и пары служебных типа ToString()). ООП обычно не разделяет эти виды объектов, отсюда полная неразбериха в терминологии и правилах использования объектов.

Так а какой язык не даёт возможности написать другой свой «язык»? ООП в этом смысле далеко позади, за Scala, Ruby и, конечно же, Lisp-ом.
Прочитал ваш коммент про необходимость разделять объекты-контейнеры и функциональные объекты. С этим согласен.
Но на мой взгляд, это все же не проблема ООП, а скорее вопрос архитектурных соглашений в рамках проекта или языка.
ООП эти виды объектов как раз разделяет: вторые вообще объектами не считаются!

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

Кстати хороший способ усложнить себе жизнь: уменьшить количество объектов за счет того. что многие из них начнут стабильно переходить между поколениями.
> Кто это вас вынуждает? Пишите в конце файла. Когда напишете в 3-й раз, вынесете.

После 3 раз вынесу — я не гордый. Но очень часто я остановлюсь 1 разе, и тогда создавать отдельный класс — как то не комильфо.
Конец файла — это в конец класса? Если метод логически не принадлежит классу, то и не должен в нём находиться, он должен нахидться просто недалеко от места использования. Чтобы и логично, и далеко за ним лазить не пришлось.

> Возвращайте new Object[]{value1, value2}, если лень объявлять новый класс. Или какой-нибудь Tuple2<BigDecimal, BigDecimal>

Object потом ещё приводить к нужному типу придётся, да и потеря типизации. Tuple2 — нужно подключать библиотеку или писать самому, тогда уже разницы нет — создать Tuple2 или именованный осмысленный класс. Я говорю именно о единичных случаях, которые больше не повторяться никогда.

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

Вот хотел написать, и забыл. Слышали про высокую детскую смертность объекто в Java? Таким нехитрым названием инженеры Sun назвали явление очень короткого периода жизни большинства объектов. Они создаются и погибают внутри одного метода. Да, они небольшие, но их настолько много, что как раз они и составляют основную проблему сборщика мусора. Здесь долго рассказывать, почитайте оракловские доки по оптимизации сборщика мусора.

> Сборщик мусора оптимизирован для ситуаций, когда надо засорять память.

Только не «когда надо засорять», а «под то, что память будет засоряться». Ибо в ОО-языках её приходится засорять.

> И вы всегда можете спустится на процедурный уровень и писать static

Это непринято не просто так. Такие языки как Java плохо поддерживают статические методы, дискреминируя их по сравнению с виртуальными. Например, статические методы нельзя объявить в интерфейсе, а это значит, что если мне нужно работать с двумя базами данных через единый интерфейс, мне _придётся_ сделать эти методы виртуальными и создать для них объекты.

Я пробовал писать на ОО языках код по-другому. Много пробовал. И нашёл ооочень много граблей, мешающих сменить парадигму.
>Здесь долго рассказывать, почитайте оракловские доки по оптимизации сборщика мусора.
Ну так это проблема сборщика мусора.

> Только не «когда надо засорять», а «под то, что память будет засоряться». Ибо в ОО-языках её приходится засорять.
И что с того? Как засорится, так и очистится.

Вы так и не поняли главную мысль, высказанную vsb:
" Но сейчас в большинстве проектов принято писать код, в первую очередь эффективный по метрике «скорость написания», «минимизация количества ошибок», «скорость исправления ошибок», а не по метрике «скорость выполнения» или «объём потребляемой памяти»."
Это вы ещё с высоконагруженными системами/системами реального времени не работали. Не обязательно, чтобы язык был изначально заточен под высокую производительность, но важно, чтобы от него такой производительности можно было добиться.
>Не обязательно, чтобы язык был изначально заточен под высокую производительность, но важно, чтобы от него такой производительности можно было добиться.
Утверждаете что от managed ЯП нельзя добиться высокой производительности?
Утверждаю, что в том стиле, в котором принято сейчас писать программы на C# и особенно на Java производительность сильно страдает из-за высокой нагрузки на сборщик мусора.
А у вас есть какое-нибудь фактическое подтверждение вашему утверждению?

Не знаю, результаты профилирования ста самых распространенных приложений на c# с указанием времени, потраченного на сборку мусора.
У меня есть код больших опен сорс проектов для высоконагруженных систем, в которых стараются по максимуму повторно использовать объекты. Сойдёт?
Нет. Нужны именно результаты профилирования.
Конец файла — это в конец класса? Если метод логически не принадлежит классу, то и не должен в нём находиться, он должен нахидться просто недалеко от места использования. Чтобы и логично, и далеко за ним лазить не пришлось.

На приватные методы никаких ограничений нет.

Это непринято не просто так. Такие языки как Java плохо поддерживают статические методы, дискреминируя их по сравнению с виртуальными. Например, статические методы нельзя объявить в интерфейсе, а это значит, что если мне нужно работать с двумя базами данных через единый интерфейс, мне _придётся_ сделать эти методы виртуальными и создать для них объекты.

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

Как только вам требуется работать с двумя базами данных через единый интерфейс, вам потребуется:
1. Выделить общий набор операций в этот самый единый интерфейс. Это — абстрагирование от конкретной БД.
2. Придумать способ, позволяющий передать конкретную реализацию интерфейса тому коду, который будет ее использовать. Это — полиморфизм.

Кроме того, если вам не хочется получить на выходе исключительно запутанную программу, вам придется скрывать детали реализации доступа к БД. Это будет инкапсуляцией. (Кстати, комментарий в коде вида /* Кто обратится к этому полю — оборву руки */ — это тоже способ инкапсуляции)

В итоге получается, что любое решение поставленной вами задачи будет использовать ООП, независимо от того, на каком языке вы ее решите — C#, Java, чистый C, ассемблер или Haskell.

Да, по поводу статических методов. Как вы вообще представляете виртуальный статический метод? Насколько я помню, такое чудо существовало только на Object Pascal. Но не спешите радоваться: это было связано с тем, что в Паскале статические методы являются методами экземпляра метакласса, то есть реализованы через скрытый объект.
В итоге получается, что любое решение поставленной вами задачи будет использовать ООП, независимо от того, на каком языке вы ее решите — C#, Java, чистый C, ассемблер или Haskell.

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

Как вы вообще представляете виртуальный статический метод?

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

Тогда это все лирика. Пишите объектно на чистом C, ассемблере или Haskell`е — и не будет ни каких претензий. Что… ой, ой — никак? Ну, так что же так?
Во-первых, не понял логику размышлений. Как из перетягивания объектно-ориентированным программированием общих понятий (в том числе и понятия «объект», кстати) следует необходимость писать объектно на C?

Во-вторых, не вижу никаких проблем ни в написании программ в объектном стиле на C, ни просто в написании программ на C в его родном стиле (что было бы логичней).
Под аббревиатурой ООП скрываются два разных понятия: объектно-ориентированное проектирование и объектно-ориентированное программирование. ОО-проектирование можно использовать на любом языке и понимать под объектом что угодно, хоть указатель на кучу. То, что обсуждается в статье, имеет отношение именно к проектированию.
А ОО-программирование накладывает лишь одно условие: всё есть объект, причём объект — экземпляр класса. Ну и как следствие, класс есть объект :-)
При программировании на языке с полной поддержкой этого условия, типа Ruby, у вас будет не 95, а 100% кода удовлетворять этой идее.

P.S. Кстати, насчёт Ruby в статье есть ошибка. На нём нельзя написать программу без классов, просто это объявление может быть неявным (сделано на уровне языка). Да и на ОО-чистоту надо проверять не так, а по возможности программировать без объектов.
Но в любом случае контекст выполнения находится всегда внутри некого объекта. Можете убедиться в этом, написав в любом месте
puts self.inspect
puts self.class.inspect
Вот вы сказали «трюки». Да, Java трюки не разрешает. Java — серьезный язык программирования со статической типизацией. Java использует не трюки, а паттерны проектирования.

Вы считаете, что это ограничивает программистов? Ничего подобного! Приведите сколь угодно запутанную программу на Python, и я ее портирую на Java.

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

Java и Python — очень разные языки. Но к ООП эта разница не имеет никакого отношения.

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


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

Ну сами согласитесь, гораздо же проще писать
struct some_interface {
  virtual void some_method() = 0;
};
struct derived : some_interface {
  void some_method();
};
//...
some_interface* obj = new derived();
//...
obj->some_method();


чем

struct some_interface {
  void (*some_method)(some_interface*);
};
struct derived {
  some_interface some_interface_inst;
};
//...
derived* dd = malloc(sizeof(derived));
dd->some_interface_inst->some_method = derived_some_method_impl;
some_interface* obj = &dd->some_interface_inst;
//...
(*(obj->some_method))(obj);


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

typedef int (*behavior_t)(char);

typedef struct {
   int a;
   int b;
   behavior_t doSomething;
} foo;

void setA(foo*, int);
int getA(foo*);
void setB(foo*, int);
int getB(foo*);

void init(foo* f, int a, int b, behavior_t behavior);

//Наследование
void bar(foo* f, int a, int b) {
   behavior_t behavior = 0;
   init(f, a, b, behavior);
}

polymorphic_call(foo*);


Тоже самое, например, на python:
import abc


class Foo:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @abc.abstractmethod
    def doSomething(self, c):
        pass


или на Java:
interface Foo {
    int a;
    int b;
    int doSomething(char c);
}


В С можно описывать интерфейс в понятиях данных и функций

В Python интерфейс можно описать в терминах класса и абстрактных методов

В Java можно описать интерфейс как интерфейс

В C++ можно описать интерфейс как абстрактный класс

ОО код можно писать при этом везде, это хорошо показано, например, в SICP.

ОО код отличается главным образом тем что он описывает задачу в терминах объектов, позволяет легко абстрагироваться от конкретных (типов) объектов через тот-же полиморфизм.
Инкапсуляция важна, но в том-же Python она «реализована» через name mangling — тем не менее это тоже ОО язык.

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

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

Вот вы сказали «трюки». Да, Java трюки не разрешает. Java — серьезный язык программирования со статической типизацией. Java использует не трюки, а паттерны проектирования.

Речь вроде бы шла об интерфесах для классов в Java и, в общем, об интерфейсах для модулей. «Трюк» в данном случае можно читать как «действие», без какой-то эмоциональной нагрузки.

А во-вторых, понятия тоже никто не присваивал. До появление концепции ООП этих понятий попросту не существовало

Не существовало таких слов, а понятия были задолго до этого, ещё до появления программирования. Обратите внимание на математику — там есть практически все модели, существующие в современно программировании. Полиморфизм, например, проявляется при алгебраических операциях над числами — вы можете сложить два реальных числа, комплексных, матрицы или вообще 2 полинома, и использовать для этого всё тот же оператор `+`.

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

Ну так я об этом и говорю — по факту большая часть кода в ОО-программах написана на процедурных языках.
… написана в процедурном стиле, конечно же.
Еще раз повторяю: все, реализуемое на Python, реализуемо на Java.

Интерфейс для модуля? Запросто! Делаем этот самый модуль синглтоном (самым простым, с инициализацией при загрузке класса), и теперь — ура! — мы можем выделить интерфейс к нему.

Оверхед составляет одна (!) строчка:
public static final MyModule instance = new MyModule();

Ну и не забыть конструктор сделать приватным, конечно же.

Что вам не нравится в таком решении? Java изначально задумывалась как язык с минимальными достаточным возможностями. Статические интерфейсы в Java отсутствуют не потому что кто-то захотел вам насолить, а просто потому что без них можно обойтись.
Вы спросили, как я себе представляю интерфейс для статических методов. Я сказал, что технически это не проблема — такое реализовано в Python (ну, грубо говоря) и впринципе может быть реализовано для Java (не путать с «на Java»). С тем, что можно реализовать такую же функциональность на обычных объектах и виртуальных методах, я не спорил.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Это — Multiton Design Pattern. Вне ООП невозможна.
А как насчет GUI без ООП? Что-то не припомню ни одного достойного функционального фреймворка для GUI.
Можно, но удобно ли? Выглядит как набор костылей.
Ещё и разные Basic'и есть.

И на баше можно вроде
Вы не поверите, и на ASMе можно!
На асме очень удобно под gtk+ писать, вызовы те-же, а накладных расходов на них в разы меньше, да и не нужно постоянно вызывать GTK_WIDGET, GTK_WINDOW и т.д. и т.п., можно просто передать указатель, типизации-то нет.
Даёшь ООП на асме!
Посмотрите в сторону работы с COM объектами. На MASM32 с макросами и типизацией указателей очень неплохо получается.

Ну masm я привел исключетельно в качестве примера. На нем у меня хоть есть практический опыт работы с COM. А так наверняка не только TASM, но и FASM, NASM и прочие имеют подходящий «синтаксический сахар» для подобных задач.

GTK?
Ну так GObject же. И на той же объектности Vala построена, так что не очень-то хороший пример.
GObject это же структура.
GTK был сделан процедурным, как понимаю, для удобства «портирования» на максимальное число ЯП.
Ну, тем не менее, объектность там используется, хоть и в непривычном виде. Хотя, не важно, это всего лишь терминология.
Там чистой воды структурное программирование. Да, оно старается быть похожим на ООП, но им не является.
GTK+ написана на языке Си, но тем не менее, является объектно-ориентированной[9]
© Википедия
Ну мало ли что там написано :)
ООП там никакого: все через структуры и функции. Хоть внутри и есть некоторые замашки.
ООП GTK это скорее GTKmm.

Понятно, что это все вопрос внешнего интерфейса взаимодействия.
Зря минусуют человека. Кто пробовал писать на GTK? Там от ООП почти ничего нет.
А GTKmm — Очень сырой
Пусть минусуют: анонимные минусы без комментариев меня нисколько не волнуют — ценности и адекватности в них нет.
А если кто-то уверенно считает, что это настоящее ООП:
struct GObjectClass {
  GTypeClass   g_type_class;
...
  void       (*set_property)		(GObject        *object,
                                         guint           property_id,
                                         const GValue   *value,
                                         GParamSpec     *pspec);
...
}
...
g_object_set_property(G_OBJECT(session), pspec->name, &value);


Уж не говорю про работу с сигналами.

Тогда ок. Но у меня несколько другой взгляд.
Настоящее ООП — это то, которое у Кея в Smalltalk, а все ваши жабки и цпп это помесь ООП и процедурщины (ну изредка функциональщины и метапрограммирования). До тех пор пока в языке есть управляющие конструкции if, for и так далее, это не ООП, я процедурщина.
Вы промахнулись с ответом?
НЛО прилетело и опубликовало эту надпись здесь
Есть такое слово «мультипарадигменность». Где-то ООП, где-то функциональщина лучше.

А в модульной парадигме, если вы подразумеваете Оберон, модуль соответсвует классу ООП. С определенными нюансами но идея та же. Функционально выделенный кусок кода с открытым интерфейсом и закрытой реализацией.
Модули в Обероне — это гораздо больше, чем классы в ООП. Натянуть одно понятие на другое, конечно, можно, но вот этих «нюансов» окажется столько, что очень скоро вы решите писать в стиле, родном для языка. Сейчас нет времени объяснять, попробуйте сами — получите массу удовольствия ;)
Я пробовал. Убогость IDE в конце концов достала и бросил это дело. Сейчас такое время что среда разработки важнее языка, да.
Я про программирование на Java в модульном стиле.
На Джаве только в институте писал, сейчас в основном C++/Qt, раньше Delphi. Сами понимаете, там без ООП очень печально.
Я не умею программировать на объектно-ориентированных языках. Не научился. После 5 лет промышленного программирования на Java я всё ещё не знаю, как создать хорошую систему в объектно-ориентированном стиле. Просто не понимаю.
На этом стоило бы статью и закончить. Sad but true.
НЛО прилетело и опубликовало эту надпись здесь
А оно бывает в чистом виде?)
Не могу не высказаться, т.к. статья задела за живое :) Долгое время меня терзали такие же мысли, как и автора, но в какой-то момент я осознал истинную мощь ООП. ООП полезно не для того, чтобы программу представить в виде какой-то совокупности объектов (реальных или придуманных — не важно), а чтобы за счет абстракций построить такую схему взаимодействия частей (компонентов, модулей) программы, при которой очень легко добавлять/изменять функционал. Т.е. ООП позволяет сделать систему гибкой и расширяемой. Насколько хорошо это получится, зависит от корректности предположений о том, какие именно части системы должны быть гибкими (а для этого нужно очень хорошо понимать проблемы пользователя и представлять сценарии использования системы), а также от профессионализма разработчика в плане построения архитектуры и организации взамиодействия частей системы). Действительно гибкие и мощные системы просто нецелесообразно базировать на процедурных принципах. Да, объектно ориентированные структуры данным могут иногда показаться сложными/тормознутыми/тяжеловесными/иногда слишком абстрагированными, но по сравнению с той гибкостью, которую они обеспечивают, я считаю, что это меньшее из зол.
Системы более гибкой, чем та что написана на LISPе у вас никогда не будет.
Категорично.
Возведение возможностей языка в абсолют всегда приводит к печальным последствиям
>> а чтобы за счет абстракций построить такую схему взаимодействия частей (компонентов, модулей) программы, при которой очень легко добавлять/изменять функционал.

Для этого не нужен ООП. Я абстракции могу легко описать в XML, а остальной код реализовать чуть ли не в функциональном стиле. И мое кунг-фу будет ровно таким-же как и ООП кунг-фу

>>ООП позволяет сделать систему гибкой и расширяемой

Опять же, вынужден не согласиться. Гибкой и расширяемой ее делает архитектура. Если в ней это заложено, то так и будет. *nix — отличный образчик гибкой и расширяемой архитектуры. И где там ООП?

>>могу легко описать в XML
Разделяйте, пожалуйста, «могу» и «делаю». Описание в XML само не заработает, нужен еще какой-то код, который на основе вашего описания будет что-то делать. Попробуйте описать в XML систему уровня AutoCAD, 3D-Max, Visual Studio или Microsoft Office. Попробуйте написать свою СУБД. Попробуйте создать платформу уровня 1С. Попробуйте написать свой браузер. Если мы говорим о примитивных приложениях, там конечно пофиг, какой подход использовать. Если мы немного выбираемся в мир крупных приложений, мы начинаем говорить про ООП.

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

Про *nix скажу проще, не важно для кого именно построена система, для гиков или домохозяек. Базовые принципы, которые были в ней заложены, позволяют максимально гибко подстраивать их практически под любые изменения окружающего мира. И они настолько просты, что поражают этой своей особенность: унифицированный формат передачи данных внутри системы, много мелких утилит, которые благодаря сценариям, можно собрать в огромную функциональность. Другими словами, логика отделена от функций, и все это работает через унифицированный интерфейс передачи данных. Черт, да это же принципы ООП без единого применения этой парадигмы в кодах программ :)
1. Я и не путаю. Просто для вас, видимо, абстракции живут в красивых UML чартах, а для меня они присутствуют еще и в коде. Объект (а уж если быть точным, то его интерфейс или реализуемый им интерфейс) — это и есть абстракция. Абстракции, реализованные не через объекты — допускаю, что теоретически такое возможно, но на практике я таких извращений не встречал.

2. >максимально гибко подстраивать их практически под любые изменения окружающего мира
Гибкость не в том, чтобы что угодно стыковалось с чем угодно, а чтобы оно стыковалось разумно и эффективно в контексте конкретной решаемой задачи. Стыковкой маленьких супер-универсальных кусочков, по-вашему, пользователь будет заниматься?

Меня просто убивает тот факт, что при том, что 99% GUI-приложений Windows основаны на принципах ООП, народ продолжает дискутировать о том, что ООП — это зло и что его лучше не использовать.
1. Объект есть абстракцией какого-то конкретного физического объекта, процесса или чего-то еще. А я говорил про еще большую абстракцию, которая связывает множество объектов и их поведение воедино. Например, сценарий игры или бизнес-процесс.

2. Давайте не скатываться в оффтопик, еще раз повторюсь, что абсолютно не важно, кто пишет сценарии, контекст моего возражения был таков, что максимальная гибкость достижима и без ООП

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

Сейчас работаем над системой в которой используются метаданные повсеместно. Так вот, описание процесса есть набор шагов и параметров в них, а также описание зависимостей значений параметров и следующего шага. Так вот, как раз в наших абстракциях бизнес процесс есть объект, который взаимодействует с другими объектами, содержит состояние и поведение.
Насчет всех *nix не знаю, а вот ядро Linux является полностью объектно-ориентированным. Там даже наследование используется, хотя написано все на чистом C. Такие вот дела.
Многие правильно отмечали в комментариях, что принципы ООП больше относятся к подходам, а не конкретным особенностям языка. Поэтому да, я уверен, что в ядре это именно так и работает.
Особенно последний абзац.

До сих пор не понимаю почему в этом коде разработчики библиотеки не сделали первый аргумент строкой-URL.

AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:app_urls]] success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
 
       self.apps = [[JSON objectForKey:@"feed"] objectForKey:@"entry"];
        

        [self.tableView reloadData];
        
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
        //
        
    }];

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

А почему тогда не сделать оба варианта? Ну, т.е красиво инкапсулировать все в простой метод, а для тех кто хочет более тонкой настройки оставить сложные варианты?
Ровно по той же причине, по которой нет CGRectMakeЧто-то, принимавшего бы на вход CGPoint и CGSize. Apple склоняется к минимализму в API. По крайней мере раньше склонялась.
Ну зачем плодить методы в API, если вам нужно объявите категиорию для AFJSONRequestOperation и в ней уже реализуйте этот метод.
Интересная точка зрения.
Я считаю, что ООП язык сам по себе не существует. Методы классов, все их алгоритмы построены по прежнему на функциональном языке.
Собствено функциональный язык, программу на нем написанную(на всех тех «промышленных» примерах, которые я видел), можно представить как один god-object, у которого глобальные переменные это поля этого единственнного объекта.
ООП лишь позволяет выделить сущности, и тем углубить декомпозицию на том уровне, который не доступен для чисто функционального языка.

ЗЫ и не зря god-object нарекли антипаттерном — поддержка продукта написанного на функциональном языке — одно большое зло.
Вы не перепутали функциональные языки с процедурными?
Все уже перепутано до нас. Те же лямбды — есть частичка функционального программирования в ООП языках, а то что в функциональных языках называют типами, как выше сказали, есть ничто иное как объекты.
Если же противопоставлять функциональный язык языку объектному, то он вырождается в свое подмножество — процедурный язык. «Ленивый», синтаксически продвинутый, но тем не менее.
> метод X можно будет передать параметр типа X или его наследника. Казалось бы, ну и что? Наследники класса X всё равно будут иметь те же методы, что и X. Методы методами, а вот логика работы может оказаться совершенно другой

А как же SOLID? В данном случае — Liskov Substitution Principle.

Статья — крик души. Я из нее понял только то, что вы все-таки знаете ООП, но вам не нравятся языки, который поддерживают только эту парадигму. Ну смените работу тогда, — пишите на Питоне или Руби. Если вам прям так сильно не нравится ООП, тогда наверное Питон даже предпочтительней.
> А как же SOLID? В данном случае — Liskov Substitution Principle.

Я в статье как раз описал ситуацию, в которой это не работает. Просто потому что нет времени разбираться, какие объекты и как можно заменять. Я пробовал сделать такую замену, когда копался в Lucene, но, слава богу, мне хватило мозгов тщательно изучить документацию и понять, сколько времени займёт нормальная реализация.
А по поводу крика души — нет, это не крик, а структурированные мысли, собранные за несколько лет. Вообще статья — это способ один раз записать, чтобы потом 100 раз не рассказывать. Написал, теперь можно будет просто кидать ссылку :)
Третий пост за два дня!
Неделя ООП на хабре!
Поддержим товарищи!
И что?
1. Я вот после такой серии статей хочу на Erlang посмотреть поближе ;)
2. Такие статьи с такими комментами — это кладезь практической информации со ссылками на разные статьи, блоги и т.д. Или вы хотите читать про то, как не могут коробку открыть?
Я очень поверхностно ознакомился к концепцией erlang'а, и заметил интересную вещь. Говоря языком околоматематическим, концепция erlang'а является изоморфной концепции ООП в том смысле, что можно провести параллели в терминологии. То есть, если заменить термины erlang'а определенным образом, можно вернуться к ООП. Процессы можно считать объектами, изоляция процессов — так же самая инкапсуляция, сообщения — вызовы методов… Поправьте меня, возможно, я ошибаюсь, но в эрланге много от ООП, просто с другим названием.
Абстракция, инкапсуляция, и т.д. — это не принципы ООП, а принципы хорошего программирования. ООП и Erlang реализуют эти принципы, но реализуют их по-разному. Вопрос в том — кто это делает лучше.
Иными словами, незачем морочить себе голову терминологией, нужно просто программировать. Желательно хорошо. :)
Недавно была статья на Hacker News, где создатель erlang'a утверждал, что они описывали его ОО всего лишь для лучшей продаваемости :)
Эх, а вместо написания (и чтения) данной статьи можно было просто сесть и покодить, на чем душе нравиться.
ну, человек покодил на русском языке — довольно эффективно, я бы сказал, судя по реакции сообщества :-)
Чтобы сделать один HTTP запрос мне нужно создать объект типа URL, затем объект типа HttpConnection, затем объект типа Request… ну, вы поняли.


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

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

ООП — очень мощная, хорошая штука. Но создавать классы/шаблоны на каждый чих — плохая идея. К этому достаточно просто внимательно относиться, и проблем с пониманием станет куда меньше, во всяком случае от перегруженности архитектуры избавит.
Я думаю, что объектное программирование было создано не только из соображения удобства написания кода, немаловажной деталью является использование однажды написанного в других проектах. Причем желательно без лишних телодвижений в виде copy/paste (что влечет за собой также перенос несуразиц и ошибок, трудности поддержки и т.д. и т.п.). И так или иначе Вы все равно придете к концепции модульности, потом инкапсуляции (что, кстати, позволит Вам проводить эффективное тестирование) и, вероятно, в итоге к внедрению зависимостей (dependency injection).
Итак, абстракция, инкапсуляция, наследование и полиморфизм — всё это есть в ООП, но ничто из этого не является его неотъемлемым атрибутом.

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

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

И не надо говорить что если в каких то ООП языках можно писать в процедурном стиле, то значит ООП не поддерживает все принципы ООП.
Даже такие языки как C# поддерживают не только ООП парадигму, а например функциональный стиль.

Конечно у ООП есть свои недостатки и область применения, но эта статья больше похоже на холивар, чем на серьезное исследование.
По моему автор объелся груш.
Подозреваю, что автор просто выбрал не правильное слово. И смысл его высказывания в том, что половина из т.н. «принципов ООП» — не являются эксклюзивными для ООП и существовали и до него. Абстрация и инкапсуляция уже были, «наследвоание» немного подумав предали анафеме распевая про Fragaile Base Calss. Остается только полиморфизм. Неплохо, но на «парадигму» не тянет.
Как уже говорили выше, Haskell поддерживает все 4 принципа. Haskell — объектно-ориентированный язык?
Смотря что такое ООП язык в Вашем понимании.

C++ процедурный язык или нет? — процедурная парадигма в нем реализуема и даже «родная».
C уж точно не ООП язык — но мы сможем там реализовать ООП с использованием специальных паттернов.

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

Ну ок.

struct Person {
  char* name;
  int weight;
  int height;
};

int foo(Person p) {
  // ...
}


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

Судя по всему это может быть ООП (а может и не быть, зависит, какой еще код будет)
Если вы будете и дальше каким то образом организовывать Person, методы работы с ним и методы поддержания согласованности в одном месте, и точно так же организовывать другие объекты, с которым работает Person или которые работают с ним — получится вполне себе ОО дизайн.
У вас будет объект Person, с которым вы будете работать через выделенный интерфейс.
Другое дело, что если у вас нет языковых инструментов, то будет тяжело провести это разграничение (его можно будет сделать только организационно) и его могут очень легко поломать.
У ООП есть свои недостатки, тема о которых уже неоднократно поднимались.
Статья большая, но весь ее смысл вполне укладывается в заголовок.
Да, ООП базируется на абстракции, инкапсуляции (как инструменте абстрагирования), наследовании (как очень хрупком инструменте) и полиморфизме. Не следует думать, что эти принципы используются только в ООП, конечно нет, любой язык без абстракции, инкапсуляции и полиморфизма довольно неудобен.
Вы спрашиваете, что отличает ООП от других? Да объекты же! Абстрактные типы данных, инкапсулированные, унаследованые, полиморфные, неважно. Принцип в том, что объекты представляют тесно связанный модуль, который хорошо решает свою задачу.
Естественно не всякий объект будет хорошим и подходящим, язык не сделает работу за вас, это только инструмент. Если хотите делать хорошие объекты используйте SOLID.
Да не правда все что вы тут говорите.
Присутствие объектов не определяет ООП.
Легко представить себе язык программирования в котором есть объекты, но нет наследования.
И это уже не ООП язык.

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

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

Использование объектов без наследования — это например язык C. Можно там объявить структуру, определить у нее поля и методы в виде указателей на функции.
И будет вполне себе полноценный объект. Но это никакое не ООП.
То есть там не имеют смысл ни паттерны ООП (включая SOLID), ни ОО подход к проектированию.

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

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

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

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

В C можно и передавать в функции в явном виде указатель на объект — тогда он еще больше станет похож на объект из ООП. Ну если этот пример Вам не нравится — возьмите там DTO объект например — у них тоже нет полиморфизма — это разве ООП стиль?

Что касается Haskell — вот тут вроде описано как разрабатывать в рамках ООП с его помощью.

Что касается ссылки на Алана Кея, Вы совершенно верно заметили, что общепризнанного определения ООП еще нет. А Алан так вообще писал что то сильно заточенное под смоллток.

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

И в данный момент это наиболее распространенное определение.
Его популяризировал Страуструп, как раз когда C++ стал тем, как мы сегодня представляем себе классический ОО язык.

Все паттерны и подходы в ООП проистекают из этих трех основопологающих принципов, а вовсе не из понятия объектности.
Паттерны и подходы в ООП разделяются на две группы: первая достаточно общая, чтобы ее применять не только к ООП (например MVC, почти все принципы SOLID, если вдумываться в смысл), вторая же призвана как-то обходить недостатки одного-двух мейнстримных языков (например С++, Java).
Хотя конечно наша беседа начинает принимать форму спора о том, какие термины использовать, пусть будет по-вашему :)
Мы пытаемся дать определение ООП — при чем тут термины.
Я предложил аксиоматику сказав, что данное Вами определение неверно.

Вы можете привести аргументы, опровергнув мое мнение или согласится с ним.

MVC не омеет отношения к ООП на мой взгляд. Это более общий паттерн.

Принципы SOLID никакого концептуального значения не имеют — это чисто ОО паттерны и за пределами ООП полностью теряют смысл.

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

Вообще-то, нет. Все принципы, кроме Interface segregation применимы и к структурному коду.
Ну попробуйте описать как применить LSP без полиморфизма.

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

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

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

Ну попробуйте описать как применить LSP без полиморфизма.

Легко. Функция, переданная как аргумент в ФВП, должна выполнять те действия, которые от нее ожидают в рамках контракта ФВП.

Там не предполагается что процедуры будут как то расширятся.

… а они это регулярно делают.

Какой смысл имеет SRP без понятия объекта, на котором может быть ответственность (ну для функций и процедур его притянув за уши можно применить — но там он не нужен).

Он там как раз очень нужен. Ровно для того, чтобы ограничить число причин изменения кода. Прекрасно описано у МакКоннелла.

То есть он работает в модульных архитектурах — но сам принцип там не особо нужен — всместо него есть базовые принципы организации зависимостей.

Это какие «базовые принципы»? Я знаю только один, IoC, он же Dependency Inversion из SOLID.
Слово Inversion подразумевает наличие минимум двух :)
Двух чего?
Принципов.
Вообще-то, Dependency Inversion — это инверсия не принципов, а зависимостей.
Два принципа: прямая зависимость и обратная (инвертированная).
Прямая зависимость — это не принцип, а ошибка. Точно так же, как нет принципа, обратного LSP.
Это принцип, по которому строится условно плохая архитектура. По DI — условно хорошая. Мне нравится второй, но и первым я не брезгую.
Нет такого принципа, повторюсь. DIP — есть. А такого — нет. То, что кто-то так строит, еще не означает, что есть такой принцип построения.
Просто этот принцип очевиден настолько, что давать ему имя собственное никто не озаботился :)
Вы спрашиваете, что отличает ООП от других? Да объекты же! Абстрактные типы данных, инкапсулированные, унаследованые, полиморфные, неважно. Принцип в том, что объекты представляют тесно связанный модуль, который хорошо решает свою задачу.

И в чём же тогда отличие от модульного программирования?
Объект является независимым экземпляром модуля, называемого класс.
И тут-то Яваскрипт подгадил определение своим отсутствием классов.
Это не имеет особого значения, тут подчеркивается не отличие, а скорее тождество. Модульное программирование слишком общее понятие, нынешние языки все модульные и ООП в том числе.
Заключение статьи имхо подкачало, но в целом: добро пожаловать в клуб )
Каждой парадигме — свое целевое использование. Если вспомнить, то ООП стал широко популярным в эру GUI-интерфейсов, где было необходимо создать иерархию однотипных визуальных компонентов в памяти, которые работают в single-thread окружении. И ООП здесь подходит лучше всего.

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

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

Другая проблема как виртуализация объектов (запись состояния в persistent storage). Поскольку функционал контроллера, сервиса и хранителя состояния был замешан в одном объекте, сериализация и десериализация такого объекта (а тем более графа) стала нетривиальной задачей.

Благодаря этим недостаткам большинство современных фреймворков давно отошло от парадигмы ООП и использует модель, где все операции деляются stateless контроллероп, а состояния хранятся отдельно в простых структурах POJO.
Вскользь упомянутый Oberon является прекрасным примером ООП без классов. Вместо них есть концепция расширяемого типа данных и связанных с ним процедур. Ведь класс, есть тип данных вместе с методами для взаимодействия с ним. Также, классы являются развитием идей модульного программирования — было сокрытие деталей реализации на уровне пакетов, а теперь добавилось и на микроуровне(классов). Поэтому я не в состоянии понять холиваров процедурное программирование vs ООП — второе, по сути, как логически, так и исторически происходит из первого. С ФП vs ООП поинтереснее, но предметного обсуждения темы, без фанатизма, я, к сожалению, еще не встречал.
Почему-то все топики, где упоминается какая-то парадигма, язык программирования или известная технология, сразу воспринимаются как начало холивара. Во всей статье нет ни слова о том, что ООП плохое, а что-то другое — хорошее. Да, описываются недостатки тех или иных фич парадигмы, но также обширно описываются и преимущества. Но нет, всё равно все видят фанатика с горящими глазами.

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

Какие только ответы тут не давались — ООП — это использование объектов, ООП — это использование структур с поведением.
И принципы ООП дискредитировали, в наследование вообще начали считать чем то не до ООП-ным.

Вроде все просто: инкапсуляция + наследование + полиморфизм = ООП.

На базе ООП, реализуются специфические паттерны, которые только в рамках концепции имеют смысл.
Все остальное: использовать ли множественное наследование, что лучше — наследование или делегирование, классы или прототипы — все это вопрос реализации принципов ООП в конкретной технологии.
Если вы настолько восторгаетесь процедурными языками, и негодуете по поводу «не очевидного отражения сути ваших программ в коде», рекомендую к прочтению заметку о парадигме DCI (http://www.artima.com/articles/dci_vision.html) от товарища Трюгве Реенскауга (основоположника MVC). Возможно вы найдёте в ней то самое зерно, что сделает вас счастливым разработчиком (если вы конечно до сих пор не счастливы).
Простите, я неправильно выразился. Речь идёт не о процедурных языках, а о всеми нами любимых объектно-ориентированных. Дело в том, что DCI позволяет избавиться от вопросов типа: «кому выгуливать собаку» или «не нарушу ли я инкапсуляцию, добавив в этот класс парочку гетеров-сетеров». Причём (следовательно) код становится более читаемым, поскольку большая его часть всё также выражена в простом процедурном стиле.
Хотя, на сколько я понял, основное ваше негодование было именно относительно того что объектно-ориентированный код не на 95% является таковым…
Тема конечно больная :) но многим ООП действительно таки помогает.
Моё основное негодование касается отсутствия внятного объяснения парадигмы. Мне нравится ООП. Оно позволяет решить многие задачи гораздо проще. Если только вас не заставляют писать в стиле ООП. А полные объектно-ориентироанные языки делают именно это.

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

Среди читателей есть еще совсем не окрепшие умы. Те, кто только начинает постигать азы программирования/проектирования. А вы сейчас им втюхиваете вашу, ошибочную, точку зрения. Прочитав вашу статью эти люди могут начать развиваться не в том направлении. Люди ведь думают что, если это написали на Хабре, то это факт стоящая статья и в ней есть смысл. Я считаю что подобные статьи опасны и вредны, без пометки о том, что это охинея. Уж простите.

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

Отдельно хочется отметить описание наследования. Оно логично, и достаточно много верных выражений там есть. За исключением того, что это до сих пор один из самых простых, надежных и гибких методов для решения проблем дублирования кода, и, как это ни странно, возможности легкого внесения изменений. Так же, без наследования невозможна реализация большинства паттернов банды четырех. И если вы редко используете это в реальном коде, то это лично ваш фейл.
Вы зря смеетесь. Сплю и вижу следующую картину:
Студент (или кто нить другой), который не может «въехать» в ООП и не может развить в себе объектное мышление, приходит и начинает доказывать что «объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C# и т.д.». Страшное дело… Мало того что он будет думать что ООП зло, так еще и будет теперь уверен что специальные методы для обращения к свойствам класса есть нарушение инкапсуляции, а не механизм, который позволяет эту самую инкапсуляцию реализовать.
Статья вредная и опасная. Охинея.
Позволяет — помогает.
Если студент приходит к преподавателю (я так погляжу по профилю, что вы преподаватель), и оказывается, что он «не может «въехать» в ООП и не может развить в себе объектное мышление», то тут два варианта:
1. Нам не нужен в будущем такой специалист (пускай отчисляется или переводится).
2. Нам не нужен такой преподаватель.
А «вредные и опасные статьи» — это мысли цензора, а не преподавателя.
3. Или хреново учили
а. Плохой преподаватель
б. Плохой материал
Хреново учили — это целиком и полностью «Нам не нужен такой преподаватель».
А «вредные и опасные статьи» — это мысли цензора, а не преподавателя.

Это почему это?
Вы считаете что я не смог объяснить вред статьи или что? Или преподаватель не может сказать что материал вреден и опасен для студента? Я не понимаю.
veitmen выразил мысль, очень похожую на довольно распространённое мнение про GoF и подобные книги. Студенту читать подобное — противопоказано. Он будет думать что ООП == паттерны, паттерны == silver bullet и как результат: «Нам не нужен в будущем такой специалист». Подобные книги можно читать только после того, как мозг настроился на ООП, а юношеский максимализм (утрирую) — прошёл.
Блин. Ну GoF не говорит что ООП зло. Не говорит что сеттеры и геттеры нарушают правила инкапсуляции. Я против и считаю что это зло, т.к. тут написано с колокольни явно не до конца познавшего дзен человека. Вот почему это опасно!
Да я разве с вами спорю. Наоборот, думал что защищаю точку зрения :))
Да я понимаю, просто я не считаю что GoF не нужен. :)
Я не говорил что он не нужен, я говорил что он опасен для неокрепшего ума. Точно так же, как и подобные статьи
Уважаемый, чуть ниже я попробовал описать почему это не так. :)
Этот текст есть сильное ИМХО автора с примесью охинеи.
Господи, да ахинея же.
Да блин, ну знаю я. :) Ну нравится мне охать. :) :)
И да, GoF для студентов не опасно. Т.к. там очень много умных штук, которые очень полезны в любом возрасте.
GoF крайне опасен для начинающих разработчиков. Значительно опаснее этой статьи.
Вы тут что, аккаунтами поменялись? :)))

+1
Да вроде нет, все по-прежнему ;) veitmen за ООП и GoF, а я немножко против.
Тут у каждого своя правда. Спорить сложно, но у меня есть несколько доводов в пользу GoF как можно раньше, даже не до конца понимая все вместе взятое. Попробую объяснить свою точку зрения.

Основная проблема ООП и начинающий проггеров (студентов в том числе) — это объектное мышление. Его необходимо развивать так, что бы вы могли легко представить декомпозицию любой сложной задачи в программировании, как взаимодействие понятных вам объектов. Вы обращали внимание на названия паттернов? Вы знаете что фабрика и строитель различаются в своей сути ровно, как завод по производству автомобилей ВАЗ и дядя Ваня в гараже, который собирает те же ВАЗы из своих деталей? А допустим паттерн Посетитель делает тоже самое что посетитель любого магазина? Замечали наверное? Так вот, как раз эти самые четыре бандита через свои паттерны, очень сильно помогают понять, как же мы можем переложить взаимодействие объектов реального мира на код. И не важно что вы не до конца понимаете что такое три столпа ООП, зато вы начинаете мыслить объектно. И лишь после этого, вы до конца начинаете понимать зачем нам три столпа и что такое абстракция в рамках ООП. GoF сделали чуть больше чем просто приготовили рецепты.
А вы читали код студента (начинающего программиста) после прочтения GoF? Я видел и чужой, и свой :) Пока ты не в теме ООП и не мыслишь объектно, любая задача сводится к «какие 5 паттернов я смогу сюда применить». Затем, спустя кучу зафакапленных проектов, ты перечитываешь GoF и тут наступает прозрение…
Да, вы просто научились мыслить объектно :)
Это как? Я не троллю, мне реально хочется понять, что вкладывается в смысл «мыслить объектно»? И как это мышление может мне помочь, какую из проблем оно может решить?
Хм… Попробую объяснить. Перед этим прочитайте вот этот большой текст :):
habrahabr.ru/post/147927/#comment_4993414

Мыслить объектно, это означает что вы можете представить решение любой задачи программирования как взаимодействие объектов. Абсолютно любой задачи. Будь то программирование сложного бизнес процесса. Будь то разработка игры. Но в вашей программе все построено на взаимодействие объектов, при реализации которых вы выбрали правильный уровень абстракции. Т.е. при реализации системы «Город», вы оперируете домами и количеством квартир, но не тем что в этих квартирах есть (ну это такой простой пример для понимания уровня абстракции).

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

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

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

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

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

Угу. Свойством CurrentState и поведением MoveToNextState(). Это вырожденный объект, он не соответствует тому бизнесу, который моделирует.

Так можно сделать, но это менее эффективно.
Ну вы мне сейчас привели пример хреновой абстракции. :) Не более того :)

«Так можно сделать, но это менее эффективно.» — безусловно такой объект не эффективен. :)
А у процесса нет другой абстракции (поверьте человеку, который написал две системы управления workflow). Именно поэтому процессы даже отображаются иными диаграммами, нежели объекты.
А я уверен что есть. Я могу процесс понимать как набор шагов. Т.е. процесс — объект который содержит состояние (на каком шаге сейчас, какие шаги могут быть и т.д.) и поведение (перейти на другой шаг, откатиться на начало и т.д.).

«Именно поэтому процессы даже отображаются иными диаграммами, нежели объекты.» — а как таковой диаграммы объектов нет. Есть диаграмма классов. А вот как раз если говорить про диаграммы показывающие то, как объект меняет свое состояние — уже есть диаграмма процесса.
Т.е. процесс — объект который содержит состояние (на каком шаге сейчас, какие шаги могут быть и т.д.) и поведение (перейти на другой шаг, откатиться на начало и т.д.).

Вы правда не видите, что это и есть та самая абстракция, которую я предлагал раньше?

«Именно поэтому процессы даже отображаются иными диаграммами, нежели объекты.» — а как таковой диаграммы объектов нет.

Вообще, есть. Она называется entity-relationship-модель.
entity-relationship — это скорее из реляционных теорий. :) И да, это тоже самое что диаграмма классов. Ток в реляционном представлении.

«Вы правда не видите, что это и есть та самая абстракция, которую я предлагал раньше?» — ммм… А я чет не помню где вы это говорили? В данной ветке не говорили. :)
entity-relationship — это скорее из реляционных теорий

Нет, это из моделирования бизнес-требований.

И да, это тоже самое что диаграмма классов.

Не совсем.
А понял вы про что :)

«Угу. Свойством CurrentState и поведением MoveToNextState(). Это вырожденный объект, он не соответствует тому бизнесу, который моделирует.» — вы это про процесс имели ввиду?

А почему по вашему подобная абстракция процесса не решает задачу? И мы сейчас говорим только про процесс! Те действия которые будут выполняться в рамках процесса уже есть другие объекты и другие абстракции. Но суть объекта процесс именно в этом и полностью решает свои задачи.
почему по вашему подобная абстракция процесса не решает задачу?

Я не говорил, что не решает. Я говорил, что решает неэффективно.

Понимаете ли, объекты — это абстракция, придуманная для того, чтобы программисту было удобно моделировать задачу. Так вот, не все задачи удобно моделировать объектами. Процессы — неудобно. ETL — неудобно.
Ммм… Погодите. Я понял о чем вы.

Описать процесс конечно проще используя BPL какой нить. Но это же описание того, как меняется состояние объекта и какие действия необходимо выполнить. Т.е. при проектировании системы, реализующей этот бизнес процесс вы (или точнее я) все равно буду рассматривать это как вызов методов и изменение состояния объектов моей системы. Понимаете? А выше, я имел ввиду что вы создаете систему, в которой вы можете создавать/моделировать БП. И вот в рамках такой системы я могу рассматривать БП как объект. Я вот про что :)

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

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

Только это не проблема ООП, это проблема применимости ООП к решению определенного класса бизнес-задач.
Вообще-то сценарии везде применяются. Это суть программ
Сценарии применяются везде, но где-то их удобнее выражать в объектах, а где-то — другими способами. PoEAA, часть 2, раздел 9.
А можно где-то сценарий, выраженный в объектах, посмотреть? Интересная тема для исследований
PoEAA, часть 2, раздел 9, сразу ведь написал.
Почитал, это не те сценарии. В книге описывается жестко зафиксированная логика, которая описывает какой-то локальный процесс. Другими словами, какое-то состояние будет вызывать строго фиксированный процесс обработки, но не по факту изменения состояния будет запускаться некая абстрактная логика.
А что такое «абстрактная логика»? Не понимаю вас.
Это логика, описанная по всем правилам ООП :)
Это, простите, для меня достаточно бессмысленная фраза.
Ваш пример только подтверждает мои слова, что для того, чтобы в ООП реализовать сценарии, пришлось использовать чуждую ООП технологию — XML. Управления состояниями не достаточно для выполнения поведенческих сценариев.
там xml используется только для хранения графа объектов, кстати, тот же XAML что и в WPF. WPF — не объектный? Так можно сказать про любую технологию, где есть серилизация.
>>>а как таковой диаграммы объектов нет.

Collaboration diagram, Activity diagram, Sequence diagram изображают взаимодействующие объеты
А вы не задумывались что поток, протокол или скажем сценарий тоже есть объекты? Которые обладают свойствами и поведением. Которые взаимодействуют с другими объектами.

По логике, Протокол и Сценарий это некоторые описания взаимодействий. Каким-таким «поведением» они должны обладать? Я могу использовать протокол, могу не использовать. Но я не могу представить, что протокол еще может себя как-то вести (отказаться использоваться? полиморфно подмениться на другой?.. не могу придумать здравый вариант).
Тьфу елки. Ну протокол, как протокол передачи данных это описание того как данные передаются. При чем тут вообще программирование? А вот когда вы реализуете протокол передачи данных, то ваша реализация протокола уже обладает поведением и состоянием. Поведение передать данные, получить данные, поймать эксепшн и т.д. Состояние это куда передавать, скок передали, можем передать и т.д. Т.е. я использую абстракцию в которой протокол для меня это объект. И это нормально.
Реализуем процесс передачи данных и/или процесс управления в соответствии с некоторым протоколом. Мы можем создать объекты типов HttpServer и HttpClient, обладающие состоянием и/или поведением, но состояние или поведение объекта Http я не представляю. Это правила процесса, и как раз для декларации правил объектная парадигма подходит не очень, правила инкапсулируются внутри объектов и, зачастую, размазываются по ним. Вроде протокол HTTP реализован, но ткнуть пальцем и сказать «вот здесь» мы не можем, у нас есть система, в которой правила размазаны и спрятаны, нужно прилагать нешуточные усилия, чтобы по системе восстановить правила, по которой система создана и понять процессы, которые в ней происходят.
«чтобы по системе восстановить правила, по которой система создана и понять процессы, которые в ней происходят.» — так мы же это и делаем. Зачем мне знать правила и принципы работы протокола если он уже реализован? Принцип инкапсуляции. Я просто использую HttpServer и HttpClient. Передаю и получаю данные. Зачем мне знать как они передаются?
Реализован на .net, а вам надо перенести в микроконтроллер, под который есть только компилятор Си
Во-первых, я не знаю почему вы взяли именно протокол передачи данных, а не, например, протокол преобразования (или еще чего-нибудь).

Во-вторых, даже в описанном примере, протокол — описание порядка передачи данных от «кого-то» «кому-то» и поведение «передать данные» присуще «кого-то» (а не протоколу), «получить данные» — это поведение «кому-то» (а не протокола) и т.д. Тоже самое с состоянием.
Эм… Я не понимаю…

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

Реализация протокола — есть непосредтвенно возможность передачи данных используя указанные пути передачи и форматы данных.

В чем проблема? Я могу передавать через протокол данные. Есть объект который может передавать данные в заданном формате и по заданным каналам. Почему я не могу сказать ему передай данные туда то. При этом куда сейчас передаются данные есть состояние объекта, реализующего протокол. Передай данные — есть поведение объекта реализующего протокол. Что не так?
Хотя бы то, что абстракция «протокол» у вас физически размазана по минимум двум объектам — передатчики и приемнике, в каждом из них реализована лишь часть протокола.
Сценарий не есть объект. Вернее, какие-то его свойства можно выделить и описать в ОО стиле, но сам сценарий, как выполнение каких-то действий в определенном порядке, не является объектом. Это скорее можно представить как данные + поток выполнения.

Не согласны? Давайте опишем в объектном стиле следующий сценарий

Если пришло время кормления собаки, то нужно ее накормить. Если еды нет — надо пойти в магазин и купить, потом уже накормить. Если собака не хочет есть, то спрятать еду в холодильник.
И да, GoF помогают в этом. :) Я уже сказал что аналогии между кодом и объектами реального мира очень просты и элегантны у них.
Ну я же чувствую, что в глубине души мы все втрём согласны :) GoF — это офигенно! Но только после того, как ты сделаешь первый уверенный шаг в сторону ООП, не спотыкаясь
*втроём
скорее не сколько объектно сколько абстрактно… абстрактного мышления очень многим критически не хватает, из-за чего декомпозиция через одно место…
Это одно и тоже. Вы не можете мыслить объектно без абстракции. Не будет абстракции, провалитесь до атомов. :)
По поводу кода студента после прочтения GoF. Ну по крайней мере попытки декомпозиции лучше чем процедурный подход. :) Это хоть как то способствует дальнейшему развитию. :)
У меня есть ряд доводов против GoF в начале изучения ООП. Каждый из этих доводов — это реальный проект с которым мне приходилось работать.
Начинающими разработчиками GoF используется совершенно не так, как должен.

1. Привыкшие к глобальному состоянию, они начинают использовать Singletone, везде, где только можно. 18 синглтонов на один небольшой проект, как вам?
2. Использование фабрик и фасадов, для того чтобы написать «типа свою» библиотеку для работы со всем, чем приходится. Не для решения реальных задач, вроде кроссплатформенности или расширения функционала, а для решения проблемы NIH.
3. Другие паттерны же усложняют задачу, вместо упрощения.

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

В первую очередь нужно заставлять писать код и читать Фаулера, потом писать еще код, и только потом давать GoF.
Вы знаете, мы сейчас начнем спорить по поводу нашего образования. :)

Ваши доводы верны. И код ужасен, и применяют не туда… Но, как показывает практика, самая сложная задача — это научить мыслить объектно, а не писать код. Код у меня и жена напишет и красивый после прочтения МакКонела и Фаулера. А вот мыслить объектно ее научить сложно, и умение писать код, без декомпозиции не нужно. Для того что бы научить мыслить студента объектно, подходит все. И GoF в том числе. Т.к. помогает класть на код взаимодействия объектов реального мира. Но безусловно нельзя просто давать книгу! Это ужасно! Нужно объяснять и показывать на пальцах. Иначе это не нужно.
Во-первых, по-моему очевидно, что статья не расчитана на людей, до этого не изучавших ООП. По моей оценке понять статью может человек, как минимум хорошо знающий процедурное программирование и знакомый с большинством аспектов промышленного программирования на объектно-ориентированном языке. Иначе ему просто будут непонятны названия типа stateless, ВыгулМенеджер и т.д.

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

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

Ну и в-четвёртых, как я и написал в статье, тезис про хрупкость архитектуры с наследованием взять не из головы, а из той же GoF. Или они, по-вашему, тоже чушь несут?
Вы знаете что любая теория состоит из тезисов, которые формируют собой некую цепь доводов. Слабость цепи характеризуется самым слабым звеном.

«объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C# и т.д.»
Т.е. единственная ваша притензия к статье — это фраза про инкапсуляцию? Выше уже выяснили, что есть DTO, которые вроде как и не объекты в понимании ООП, и на них инкапсуляция не распростаняется. Чуть ниже я дополнительно привёл 2 точки зрения: теоретическую и практическую (реализация). Чтобы вы не искали: с теоретической точки зрения (т.з. самой парадигмы) getters и setters нарушают инкапсуляцию, т.к. позволяют внешним объектам видеть и модифицировать внутреннее состояние. С точки зрения реализации в языке — не нарушают, т.к. не дают прямой доступ к полям. Лично я придерживаюсь первой точки зрения, т.к. она больше соответствует изначальной идее ООП — есть агенты (объекты), выполняющие какие-то задачи, и есть сообщения (данные, DTO в языках программирования), которыми эти агенты обмениваются.

Если вам есть что возразить — welcome.
Давайте начнем заново.

Итак что же такое ООП. ООП — это парадигма, которая говорит нам программировать/проектировать используя объекты и взаимодействия между ними. Т.е. когда я программирую/проектирую через ООП, я использую понятие объекта, и эти самые объекты взаимодействуют друг с другом таким образом, каким мне необходимо в заданной абстракции для решения моей задачи. Существует три инструмента для упрощения моей жизни. Именно три, какой КЭП вводит еще и абстракцию мне не понятно, т.к. это настолько очевидно, что даже слов нет. Первый инструмент это инкапсуляция. По сути ограничения для того, что бы мои объекты не жили «кишками наружу». Т.е. другие объекты не должны знать как они устроены. В языках есть инструмент, который позволяет скрыть реализацию, это наши любимые геттеры и сеттеры. Именно этот инструмент позволяет скрыть реализацию, т.к. объект, который взаимодействует с другим объектом, не знает что происходит при передачи/получения значения свойства. Т.е. реализация скрыта. Что там проиходит, это ваще никого не волнует, даже если просто присваивание полю происходит. Просто если происходит тупо присваивание поля, то это означает что на моем уровне абстракции мой объект устроен именно так. Пример с маньяком. Если маньяк у человека отрезает кусок, т.е. меняет вес человека, и после этого должен вызвать метод проверки умер чел или нет, то это ошибка проектирования и инкапсуляция не соблюдена. А если меняя вес, человек проверяет что вес стал меньше нуля, и если стал то умирает, инкапсуляция соблюдена, т.к. маньяк не знает внутреннего устройства человека. А рамках абстракции еще одной у меня плевать на вес человека. И он никогда не умрет от этого. Маньяк меняет вес как угодно, и мне не нужны доп проверки в сеттере веса, тогда тоже все верно. Так вот, геттеры и сеттеры это лишь инструмент, с помощью которого можно удобно реализовывать инкапсуляцию.

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

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

Теперь про DTO. Я вообще не понимаю почему этот паттерн противоречит ООП. Как я уже выше объяснил что инкапсуляция есть лишь инструмент, который позволяет решить проблему «кишки наружу» в рамках моей абстракции. Отсюда получается что в рамках моей абстракции DTO может быть чем угодно. И если объект обладает ток свойствами, но не поведением, то он объектом быть не перестал. Я ведь использую ООП и в рамках взаимодействия моих объектов, этот самый DTO объект есть ничто иное как объект, использую который мои объекты передают друг другу данные. И почему сообщения, которыми обмениваются мои объекты не объекты? Ведь все зависит от используемых мною абстракций? :)

Почему я считаю что статья вредная? Как я уже объяснил есть не верные утверждения, а так как все написанное вами есть ваше личное мнение, по сути теория, то она не тверда, по причине объясненной выше. Но безусловно основное зло, это сам смысл статьи. Вы подаете ООП как некий инструмент который не нужен. А он очень нужен, т.к. если вы начнете мыслить объектно, начнете находить нужный уровень абстракции, то ваши приложения станут легкими и шелковистыми, а код ваш будет создавать в головах других программистов отличные аналогии, которые помогут им быстро и легко понять чего вы там написали. Поймите что ООП это не три кита, это полностью другой подход к видению «внутренностей» программы в первую очередь.
С вашего позволения буду сюда правки вносить, даже скорее уточнения:
Наследование и полиморфизм, это инструменты с помощью которых я могу легко масштабировать взаимодействие моих объектов при минимальном дублировании кода. =
Наследование и полиморфизм, это инструменты с помощью которых я могу легко масштабировать взаимодействие И ПОВЕДЕНИЕ моих объектов при минимальном дублировании кода.
И еще немого про DTO. Данные сами по себе не нужны, в рамках ООП. Данные это есть свойства какого либо объекта. Его состояние. Почему передавая данные я не могу использовать объект контейнер? Это ведь будет его состояние. Разве нет? Т.е. объект контейнер передан куда то там, который несет в себе данные. Не забывайте что ООП это в первую очередь объекты и их взаимодействие, т.е. объектно ориентированное программирование.
ООП — это красивая и очень удобная надстройка над процедурным программированием, но никак не «полностью другой подход».
Другой подход — это логическое и функциональное программирование. Prolog, LISP, Erlang — вот совершенно другой подход.
А статья полезная хотя бы потому, что в ней оспаривается популярное заблуждение, что ООП — «серебряная пуля». Увы, но апологетов ООП слишком много. Их необходимо разбавлять.
Мне жаль что вы так считаете. :(

Это гораздо более чем просто надстройка на процедурным программированием. Это изменение восприятия кода и элементов программы. Это объектно-абстрактное мышление. Это совсем другое, нежели процедурных подход.
И да, безусловно не серебряная, но и не пуля, а некая МБР, если проводить аналогию с оружием дальше. :) :)
ООП — это инверсия процедурного подхода :) Не взять данные и произвести над ними какие-то действия, а послать данным сообщение, чтобы они над собой произвели какое-то действие. Он именно что полностью другой, противоположный. А ФП — просто другое измерение.
Раскрою почему: «Если маньяк у человека отрезает кусок, т.е. меняет вес человека, и после этого должен вызвать метод проверки умер чел или нет, то это ошибка проектирования и инкапсуляция не соблюдена. „

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

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

Выше товарища жёстко заминусовали за пристрастность к Lisp, а тут я вижу пристрастность к ООП.
Если бы речь шла о Smalltalk, то я бы, наверное, согласился. Но речь, как я понимаю, о таких языках как Java и C#, а в них большая часть кода пишется в процедурном стиле. Ну и как тогда можно говорить, что это что-то принципиально другое?

Дальше про объектно-абстрактное мышление.

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

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

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

Инкапсуляция, по вашим словам, является, во-первых, неотъемлемой частью ООП, во-вторых, касается технической реализации, т.к. затрагивает технические особенности доступа к полям (напрямую или через методы доступа). Первый пункт сразу ставит крест на ООП таких языков как Python, которые не запрещают трогать их внутренности и полагаются на разумность разработчиков. Пока-пока Python! Второй означает техническую направленность определения объектно-ориентированного программирования, т.е. мы более не рассуждаем абстрактными понятиями доступа к данным, а конкретно говорим о том, позволяет ли объект напрямую копаться в его «кишках». Ок, в ООП объект не должен давать прямого доступа к своим полям. А рефлексия? С помощью рефлексии в Java или C# можно добраться до любой детали объекта даже без его разрешения. Использование такого хака противоречит ООП? Безусловно. Тогда почему использования методов доступа, которые делают абсолютно то же самое, не противоречит?

Про наследование и полиморфизм лень расписывать — вы сами сказали, что это лишь дополнительные ограничения, так что сначала разберитесь с первыми пунктами, а потом если понадобится вернёмся к наследованию и полиморфизму. Скажу только, что сами понятия также далеко не однозначные, и в большинстве языков они либо есть, либо реализуются элементарно.

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

И вот тут мы подходим к понятию DTO. Объекты переноски данных по определнию не являются агентами. Они не совершают никаких полезных действий, продвигающих логику. Всё, что они делают, — это упаковывают вместе набор данных, чтобы передать от одного агента другому. Если у объекта данных появляется поведение, связанное с изменением его содержимого, то он перестаёт быть DTO, он становится агентом. Человек, которому ваш маньяк отрезал ногу, был агентом и вполне логично должен был узнать, что другой агент изменил его состояние. Агентом, но никак не DTO. С другой стороны, запись в реестре поликлиники об этом человеке — это DTO: в них любое свойство можно читать напрямую, и так же напрямую это свойство менять («напрямую» не значит обращаться непосредственно к полю, это значит, что изменение данных не вызывает никаких побочных эффектов).

Разделение агентов и объектов данных — это единственная согласованная теория, которая не противоречит постулату об инкапсуляции и здравому смыслу.
Красиво, честно. :)

«Инкапсуляция, по вашим словам, является, во-первых, неотъемлемой частью ООП, во-вторых, касается технической реализации, т.к. затрагивает технические особенности доступа к полям (напрямую или через методы доступа). Первый пункт сразу ставит крест на ООП таких языков как Python, которые не запрещают трогать их внутренности и полагаются на разумность разработчиков. ». Я никогда не говорил что инкапсуляция затрагивает технические особенности доступа к полям. Я говорю что геттеры и сеттеры это инструмент, который помогает в реализации инкапсуляции. Вы вообще понимаете чего я пытаюсь сказать?

«Первый пункт сразу ставит крест на ООП таких языков как Python, которые не запрещают трогать их внутренности и полагаются на разумность разработчиков.» — вы что подразумеваете под трогать внутренности? И что значит что инкапсуляция запрещает трогать внутренности? И кто вам, блять, сказал что в пайтоне нереализуема инкапсуляция?
Я говорю что геттеры и сеттеры это инструмент, который помогает в реализации инкапсуляции. Вы вообще понимаете чего я пытаюсь сказать?

Геттеры и сеттеры, т.е. методы доступа к полям DTO объекта (именно DTO, потому что для бизнес объектов одноимённые методы уже будут являться элементами логики), никакой инкапсуляции не помогают — они не ограничивают доступ к данным, у них нет такой задачи. Наоборот, у них задача — обеспечить доступ к данным, на то они и методы доступа к данным. Нужны они в первую очередь на случай рефакторинга — вдруг что-то поменяется, нужно минимизировать влияние на систему. Но объектно-ориентированное программирование ортогонально рефакторингу. ООП — это способ декомозировать задачу на логические элементы, вопрос о возможных изменениях в конкретных реализациях конкретных объектов данных оно не рассматривает.

вы что подразумеваете под трогать внутренности? И что значит что инкапсуляция запрещает трогать внутренности? И кто вам, блять, сказал что в пайтоне нереализуема инкапсуляция?

Переменные
Методы
Если коротко, то поля класса вообще никак не защищены от внешнего доступа, методы доступны извне под немного другим названием, но сделано это по техническим причинам, а не по идеологическим. А так разделение на публичные и приватные делается тупо средствами нотации, без введения реальных средств защиты private полей и методов.
Есть агенты (объекты), выполняющие какие-то задачи, и есть сообщения (данные, DTO в языках программирования), которыми эти агенты обмениваются.

Респект! Теперь-то я понял что такое DTO — это часть механизма сообщений, часть вызова метода, упакованные для удобства параметры. Но от этого DTO не перестаёт быть объектом в смысле ООП, не перестаёт обладать поведением в частности. Просто это поведение зачастую примитивно установка состояния/получение состояния, сеттеры/геттеры), но вовсе не обязательно такое. Главное что это поведение сообщения, а не поведение объектов бизнес-логики, не бизнес-поведение.
Я бы хотел возразить по поводу модификации состояния.
Цель ОО дизайна — абстрагирование. Сделать объект с максимально простым поведением (и интерфейсом) снаружи, скрывающий сложное состояние и реализацию внутри.
Это не значит, что объект должен фанатично скрывать свое состояние и выглядеть снаружи как stateless. Частью его поведения вполне может быть то, что использующие его агенты знают его состояние и переводят объект из состояния в состояние. Важно лишь то, что для внешних агентов это делается просто и понятно.
Например объект для взаимодействия с оборудованием вполне может иметь состояния «не подключен», «подключен» и «неисправность» с описанием ошибки. Прекрасный объект, который скрывает в себе константы кодов ошибок, сложное api общения с устройством, детали подключения через usb.
В этом случае даже просто публичная переменная не является нарушением инкапсуляции — она часть публичного контракта и те, кто используют объект, о ней знают.
Геттеры сеттеры — просто сахар для того, чтобы иметь возможность сделать этот контракт безопаснее, оставить его простым снаружи (как переменная), но добавить проверку целостности, логгирование доступа и прочее внутри.
В этом смысле они не нарушают инкапсуляцию, а наоборот, являются удобным инструментом ее обеспечения.
В функциональном программировании одной из основных идей является отсутствие состояния. В структурном — декомпозиция. В модульном — разделение функционала в законченные блоки

А в ООП — объединение поведения и состояния в виде объектов. Разве нет?

В последнее время какая-то странная тенденция наметилась — стало модно ругаться на ООП :(
Ну парадигма и парадигма, ну не нравится — не пользуйтесь. Никто же не говорил, что это будет просто…
Я не ругаюсь, я указываю на недостатки (в основном терминологии и инфраструктуры). Как я уже говорил выше, было бы интересно посмотреть, как отреагировал бы народ на разбор быстрой сортировки, и её сравнение с другими алгоритмами :)

Просто связать процедуры и структуры — это как-то слабовато для новой парадигмы всё-таки.
А вы представьте себе программиста, который видел только чистый си, а потом увидел строчку
foreach (var shape in scene)
{
     shape.Show();
}
Представил. Что дальше?
Наверное, плохо представили :)
А, ёлки палки, я уж было подумал, вы мне показываете цикл foreach, которого нет в C :) Так это уже полиморфизм, т.е. больше, чем просто связанные данные и методы.
Это типа крутое ООП? фигура.Показать(). Что показать? саму фигуру? допустим. Где? На экране, на какой-то канве? Еще где-то?
>>>Это типа крутое ООП?

Этим круто ООП

>>>Что показать? саму фигуру? допустим. Где? На экране, на какой-то канве? Еще где-то?

В данном примере, не важно.
вот функциональный пример
lists:map(fun show/1, shapes(scene))
чем он хуже вашего?
lists:map(fun show/1, shapes(Scene))
если быть точным
1. это не чиствй си.
2. хотелось бы посмотреть на реализацию функции show
а кто говорил про чистый си? это вообще не си. и парадигма тут функциональная, а не структурная или процедурная, как в си.
покажите мне пример вашего метода show, я откуда знаю, что он у вас должен делать?
я говорил: «представьте себе программиста, который видел только чистый си»

abstract void Show()
{
}
окей, не заметил.
show(shape) ->

можете подсунуть любую функцию точно так же
вы о том, что средствами С организовать VMT?
Я извиняюсь, я не заметил фраз про кодера, который видел чистый с.
Придумайте лучше. Думаю все только «ЗА»=)
Лучшее ООП? Или лучшую терминологию?
Неважно что. Главное чтобы оно работало лучше с Вашей точки зрения.
Не, это уже изобретение велосипеда. А я пишу о том, как у уже имеющегося велосипеда выгнуть руль, чтобы не ехал куда ненадо.
Нет, вы именно ругаетесь. Даже вот последней строчкой вашего комментария. В ООП на самом деле всё хорошо, если не сводить логические рассуждения к холиварам get/set vs public field. Концентрация на такой ерунде вторична и только лишь говорит о том, что обе стороны спора — не знают или не хотят знать ООП.

Вы так подробно сконцентрировались на 4х принципах ООП, расписали их и пришли к выводу, что ООП их не открывало, эти принципы были всегда, и удовлетворение им — это ещё далеко не ООП. Разве кто-то утверждал обратное? Вспомните банально математику — из ООП следуют эти 4 принципа. Наоборот — нет. Значит, есть что-то ещё. Правильно, моделирование. Оно всегда было с нами, ещё до изобретения компьютеров и С++. Просто в какой-то момент программисты осознали, что оказывается можно моделировать и в коде! И назвали это ООП. Чётко и понятно — парадигма, вводящая понятия объектов, связей между ними, что даёт нам возможность моделировать предметную область на её же языке, используя объекты. Объект может иметь поведение и состояние. Как в реальном мире — меня зовут Антон, я живу в Санкт-Петербурге и я умею стучать по клавиатуре. И моделируя окружающий мир, становятся очевидными 4 принципа, без которых модель получается нежизнеспособной. Вам дали мощный инструмент, позволяющий выражать проблему в коде более наглядно, нежели это позволяют структуры и функции! И сформулировали принципы, помогающие проектировать предметную область с меньшим числом нестыковок. Да, чтобы проектировать качественные модели — нужно уметь пользоваться данным инструментом. Вам в этом помогают GoF, Эванс, Макионелл, Фаулер. Кто говорил, что будет просто? Попробуйте вспомнить школу или ВУЗ, когда был процедурный паскаль — ведь всё-равно в любой программе на нём чувствовалась частичка ООП, потому что это довольно естественно, моделировать задачу и её решение. Вот вам и дали объекты, чтобы избавить от всего этого вороха функций и структур, помочь сконцентрироваться на главном — как решить поставленную задачу, а не раздумывать над синтаксисом перегрузки функций, чтобы считать площадь, используя ссылки на структуры типа square, circle или ellipse.

А что же конкретно вы подразумеваете под инкапсуляцией и можно ли назвать код объектно-ориентированным, если не использовать геттеры\сеттеры — это по сути остаётся в стороне. Со временем, спроектировав достаточное количество моделей, ответы на эти вопросы сами появятся. Потому что it depends, и чёткого определения быть не может. Есть только набор базовых общих принципов и объекты :) И никто ничего не изобретал, ООП всегда было вокруг нас.
Да простит меня Макконнелл за то, что я до сих пор не выучил правильного написания его фамилии!
Блин, ну я же на всё это уже отвечал, либо в статье, либо в комментах.

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

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

В-третьих, эти 4 принципа следуют, например, из Haskell точно так же, как и из ОО-языков.

В-четвёртых, моделирование ну никак не упирается в ООП. Я об этом тоже уже писал. Более того, ООП зачастую приводит к нарушению потока контроля, наблюдаемого в реальном мире (ВыгулМенеджер.выгуливать(собака)). Так что это тоже не особенность ООП.

Отсюда вопрос: так что же такое ООП?
Просто время безраздельного господства этой парадигмы заканчивается и наконец стали слышны критические голоса. Они были и раньше, но их заглушало ураганным рекламным ветром.
ООП — это маркетинговый ход :)
Как же задолбали это теоретики «ни о чем»… ну какая нахрен разница, ООП или не ООП?
Есть несколько практик по программированию, их назвали ООП.
Нахрена рассуждать, есть ли чистый ООП языки или нет их? Ну что это измени-то?
GoF описывали одну из целей создания книги по паттернам проектирования как «создание единой терминологии для облегчения общения между разработчиками». Если хотите, цель статьи примерно в том же — разобраться, что есть что, и с чем его едят. Примеры порождаемых отсутствием нормальной терминологии проблем: lostechies.com/derickbailey/2011/03/28/encapsulation-youre-doing-it-wrong/
>>Серьёзно. Мне сложно сформулировать основные идеи ООП. В функциональном программировании одной из основных идей является отсутствие состояния.

Как бы из этого можно сформулировать основную идею ООП — наличие сущности имеющей состояние и поведение в зависимости от этого состояния. Всегда так на собеседованиях отвечаю. Остальные принципы не обязательны, что подтверждают различные реализации ЯП.
Чем это отличается от структуры и набора процедур? Просто синтаксическим сахаром, позволяющим неявно передавать this? Как-то слабовато для парадигмы.
В python ООП, хотя private там постольку поскольку и this (self) передается явно. А вот в том же Erlang например инкапсуляция гораздо круче чем в том же цпп, полиморфизм на модулях присутсвует. И подобие наследования через behaviour можно сделать, только вот это не делает его ООП.
Не совсем понял, это как-то противоречит моим словам или вы просто к слову?
это не противоречит, а как раз подтверждает. я тоже не понимаю особого сакрального смысла «чистоты ООП»
Вот именно, ООП можно делать и на структуре, передаваемой в процедуры. структура хранит состояние, процедуры его меняют или действуют согласно состоянию.
Я когда учу новичков ООП, прихожу примеры именно на процедурах, чтобы они поняли что конструкция class это не ООП.
Значит C, поддерживающий структуры и функции, — это ОО-язык?
Безусловно, только не настолько удобен как языки с синтаксическим сахором для ООП. В своё время первые версии оопшного C++ фактически генерировали код на C.
А бывают тогда не ОО языки?
Нет, он не ориентирован на использование объектной парадигмы. Не мешает, но не помогает.
На этом языке можно писать ОО программы.
А можно писать и не ОО программы.

Но сам по себе язык не ориентирован именно на ОО подход.

Некоторые используют дополнительные инструменты, которые парсят исходники на С и выдают ошибки при нарушении ОО стиля.
ООП — всего лишь способ мыслить. Можно взять тот же brainfuck и написать статью, что я не понимаю его. Есть так же АОП, с ним все хорошо? Напишите топик и на эту тему, можно будет продолжить холивар.
Кстати, у нас в мире php сейчас как раз переход к AOP+MVC, zend framewok 2 как раз чётко в эту сторону движется, остальные фреймворки тоже обзаводятся евентами, обсерверами и прочими AOP like фичами. Так что ждите в ближайшее время кучу топиков об AOP.
Ок, способ мыслить. В чём состоит этот способ? Посмотрите пункт про stateless и stateful в статье, там я как раз и описываю, как пытался «мыслить в ООП».
Извините конечно, но по моему это просто горе от ума. Каждый выбирает то, что ему по душе. Статья интересная, но немножко однобокая.
Ну она и должна быть однобокой — в ней описывается одна конкретная тема. Я в чём-то неправ?
Систему на много проще проектировать в контексте объектов (а не данных и методов, которые будут их обрабатывать). На много проще расширять систему построенную на объектах и определенных связях ибо у каждого свои обязанности. Тот же смысл, что и в жизни.

А на счет собак:
>> теперь было непонятно, следует ли собаке самой себя выгуливать, или для этого нужен специальный ВыгулМенеджер

менеджер для выгуливания нужен, если в нем есть (или будет) смысл. А собака может и сама прогуливаться, ведь живут как-то бездомные собаки=)

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

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

Это утверждение верно как минимум не для всех систем и не для всех проектировщиков.

Я вот уже давно проектирую системы исходя из сценариев использования, а они оперируют как раз данными и процессами, а не объектами.
Все исходит из сценариев использования (обычно называется спецификацией, ТЗ, таской, фичей и т.д.). Ибо система объектов, которые делают ненужные вещи никому ненужна.

В конце концов Вы все переводите в объекты (классы), потому как это дает возможность делегировать обязанности на определенные логические и структурированные части кода
В конце концов Вы все переводите в объекты (классы), потому как это дает возможность делегировать обязанности на определенные логические и структурированные части кода

Вы так говорите, как будто других способов нет.

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

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

Компилятор/интерпретатор. Написанный на каком-то языке.

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

Субъективно.

А статью считаю бредовой отчасти, такое ощущение что у автора накипело просто.

Ощущение неверное, читайте внимательней.
(просто глаз зацепился)

Чтобы сделать один HTTP запрос мне нужно создать объект типа URL, затем объект типа HttpConnection, затем объект типа Request… ну, вы поняли.

Это значит, что у вас неверно декомпонованная предметная область.

А в нормально декомпонованной (например, доступе к БД) есть соединения, команды и параметры, и каждый из них можно использовать повторно, и как только начинаешь это делать, сразу становится понятно, зачем каждый из них в отдельном классе.
Какой смысл в повторном использовании, которое, по идее, должно сократить затраты ресурсов, но при этом ведёт к архитектуре с ещё большими потерями ресурсов?
А оно ведет? Что-то я этого не видел.

Этот разговор, понимаете ли, беспредметен, пока нет конкретных замеров, в которых

using(var conn = new SqlConnection(connString))
{
    /* повторяем 100500 раз */
    using (var cmd = conn.CreateCommand())
    {
         cmd.Execute()
    }
}


будет существенно менее эффективно по ресурсам, нежели

/* повторяем 100500 раз */
sql_cmd_execute(cmdText, connString);


(при одном и том же DAL)
И часто вы 100500 раз в цикле обращаетесь к БД?
Вот примерно то, о чём я говорю. Код обращения к БД на C#:
            string connetionString = null;
            SqlConnection cnn  ;
            SqlCommand cmd ;
            string sql = null;
            SqlDataReader reader ;

            connetionString = "Data Source=ServerName;Initial Catalog=DatabaseName;User ID=UserName;Password=Password";
            sql = "Your SQL Statement Here , like Select * from product";

            cnn = new SqlConnection(connetionString);
            try
            {
                cnn.Open();
                cmd = new SqlCommand(sql, cnn);
                reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    MessageBox.Show(reader.GetValue(0) + " - " + reader.GetValue(1) + " - " + reader.GetValue(2));
                }
                reader.Close();
                cmd.Dispose();
                cnn.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Can not open connection ! ");
            }

Для простого обращения к базе данных понадобилось создать 3 объекта. Ок, 1 можно сохранить (и породить состояние, за которым потом ещё придётся следить, кстати). Но 2 новых объекта всё равно будут создаваться при каждой итерации.
Для сравнения код на Go:
var query string;

        // connect to the database using the information defined above
        db := mysql.Connect(hostname, username, password, database);
        db.SelectDb("gotest");

        // run an update query
        query = "UPDATE `gotest` SET `testfield` = 'Update something'";
        fmt.Println("Executing query: ", query);
        db.Query(query);

        // if the query was successful, view some information
        fmt.Println("Affected rows: ", db.AffectedRows, "InsertId: ", db.InsertId, "\n");

        // run an insert query
        query = "INSERT INTO `gotest` SET `testfield` = 'Insert something', `testfield2` = 12345.123, `testfield3` = NOW()";
        db.Query(query);
        fmt.Println("Executing query: ", query);

        // if the query was successful, view some information
        fmt.Println("Affected rows:", db.AffectedRows, "InsertId:", db.InsertId, "\n");

        query = "SELECT * FROM `gotest`";
        db.Query(query);
        fmt.Println("Executing query: ", query);
        fmt.Println("Num rows: ", db.NumRows());

        for {
                row := db.FetchRow();
                if row == nil {
                        break
                }
                fmt.Printf("(%T) %d => (%T) %s, (%T) %f, (%T) %+v\n", row[0], row[0], row[1], row[1], row[2], row[2], row[3], row[3]);
        }

        // close the connection
        db.Close();

В памяти не создано ни одного объекта. Это хороший пример того, как можно избежать создания новых объектов с очень маленьким сроком жизни. К сожалению, ОО-языки не особо поощрают такой стиль и заставляют писать в стиле программы на C#.
И часто вы 100500 раз в цикле обращаетесь к БД?

100500 — это, конечно, преувеличение, но больше одного раза — очень часто.

А теперь посмотрим ваш код на Go.

Во-первых, для меня он выглядит как объектно-ориентированный, и в нем создается как минимум один объект — db, черех который, собственно, и идут операции с БД).

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

С этой точки зрения дизайн ado.net мне кажется более логичным: для каждой операции у меня есть свой — не важно, объект это или хэндл, важно, что он уникален; более того, результат каждой операции — это отдельный хэндл, и он не появится до тех пор, пока операция не выполнена, а, значит, я просто не могу выполнять с ним никаких операций (типа попытки прочитать из неоткрытого потока).

Ну и в-третьих, как я уже говорил, нужны результаты профилирования, без них этот разговор пустоват. С ними он превратится в классическое архитектурное «удобство против скорости», а без них — просто ни о чем.
db — это структура, а не объект, в том то и дело. Т.е. хранится на стеке, после использования автоматически зачистится (если больше не нужен, конечно).

Давайте профилирование отложим. Во-первых, это куча времени, которого у меня (в т.ч. из-за разросшегося обсуждения) ну вообще нет, а во-вторых, тема большая, и её лучше обсуждать отдельно. Думаю с тем, что выделение объектов в хипе на сколько-то вредит производительности, вы согласитесь. А я именно это и хотел сказать.
db — это структура, а не объект, в том то и дело. Т.е. хранится на стеке, после использования автоматически зачистится (если больше не нужен, конечно).

Эмм. А вы понимаете, да, что то, где хранится объект, к ООП никакого отношения не имеет?

Смотрите, у нас есть несколько логических посылок.

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

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

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

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

Где-то в середине поста, при перечислении реализация ООП в других языках, я пояснил, что дальше речь будет идти конкретно о 2х языках — Java и C#, естественно, в текущих реализациях. Говорить о производительности самой парадигмы я, разумеется, не берусь.

Хотя в случае C# размещение объектов на стеке — это какая-то полумера: всё равно ж функциональность будет пораспихивана по разным объектам, а значит не будет возможности сделать некоторые оптимизации. Но это уже совсем-совсем другой разговор :)
Ресурсов каких?
Сокращает время работы программистов, но добавляет аппаратные затраты.
Памяти и, как следствие, процессорного времени.
Так всегда было. При добавлении нового слоя абстракции программы потребляют больше аппаратных ресурсов. Попробуйте напишите на Ассемблере (или машинным кодом раз уж на то пошло) сложный проект. Мало того что специалистов будет сложно найти, но также будет сложно поддерживать и развивать.

Любая бизнес задача не будет ждать пока Вы напишите супер быстрый код ибо пока Вы это сделаете технологии уйдут далеко вперед и задача перестанет быть ценной с точки зрения бизнеса. Это реальный мир, если Вы не заметили.
Дело не в уровне абстракции, дело в стиле программирования, который пропагандирует ООП, а именно в порождении большого количества быстроживущих объектов.
Здоровский пост. Заставляет задуматься)
Я не знаю что такое человек.
Ну вот, посмотрите: отрезаем ногу и начинаем внимательно изучать. Обратите внимание на то, что нога не человек. Более того, нога используется не только людьми. У собаки тоже есть нога, она к тому же легче и бегает быстрее. И да, нога не составляет и 80% процентов тела. Теперь то же самое проделаем с остальными частями тела и увидим аналогичную картину. В итоге, оказывается, что человек это вовсе не человек, поскольку состоит не из человеков.
Поэтому я и не понимаю, что такое человек.
Для начала определимся, зачем вам знать, что такое человек?
Для того, чтобы в разговоре хотя бы иногда не употреблять слово «животное».
Животное — надкласс человека, как программирование — надкласс ООП. Плохой пример.
А так всегда можно найти признаки — млекопитающее, прямоходящее, разумное и т.д. Вы зашли не с того бока — любое конкретное понятие можно описать набором признаков. Это суть классификации. Если хотите придумать хороший пример, берите абстрактные понятия.
C чего вы решили, что у собаки тоже есть нога? Только потому, что она так называется?
Может быть, на это уже указали ранее, но претензия к ООП «я же не знаю, что делают дети этого класса!» смехотворна. У каждого класса есть свой контракт, он должен выполнять только его, и ничего более. Вы НЕ ДОЛЖНЫ знать, как работает его логика; вы должны знать только контракт. Его дети тоже должны выполнять этот контракт, даже с использованием другой логики.
Ок, конкретный пример, который мне попадался. Плагин для Solr. Документация слабая, примеров всего ничего. А плагин должен затрагивать важные элементы поискового движка. Чтобы не попасть впросак, нужно точно знать, как работают все элементы API, с которыми придётся сталкнуться. И в первую очередь принципы работы класса, от которого наследуется плагин. А это тянет за собой зависимость в виде самого суперкласса, предков суперкласса и всей инфораструктуры, в которой работает суперкласс. Скажу вам по секрету, в Solr код довольно нетривиальный, и понять точно, что именно там происходит, весьма непросто. В итоге высока вероятность выполнить только контракт самого интерфейса и зафейлить неявный контракт, описывающий, как делать не надо.
Ну, мы же тут всё-таки (с подачи ОП) теорию обсуждаем, а не суровую практику.
А почему вы разделяете теорию и практику? Теория — это просто более высокий и общий уровень знания, чем практика.
Потому что в посте и комментариях идёт дискуссия о том, как в принципе надо, а не о том, как в какой-то конкретной библиотеке криво написали.
А кто сказал, что там криво написали? Там написали по всем канонам ООП, и в той ситуации это было действительно лучшее решение. Но проблема лежит в самом принципе наследования, если хотите, теоретической части ООП, которая вылилась в конкретную практическую проблему.
А как насчет андроид?
Вроде как там объекты выходят за рамки приложений — события транслируются во вполне определенные вызовы (вроде уснуть/проснуться) и все приложения это поддерживают. Совершенно не важно какое приложение — не надо знать что внутри, зато важно что любому можно послать событие «заснуть» или «убейся» :)

Конечно — это всего лишь интерфейс. Контракт. А явный он или нет… зависит от продуманности и зыка. Тот же «усни» может длиться минутами, что совсем не предполагалось неявно — выглядит для пользователя как подвисание :) А все потому что не прописано в контракте.

А пример с Solr (не вкурсе что это) не корректен. Потому что он может оказаться не самым лучшим эталоном ООП. Ведь использование ООП само по себе не делает программу лучше :)

Статья на эту тему — www.joelonsoftware.com/articles/LeakyAbstractions.html
Про Solr. Речь шла о «несерьёзности» аргумента про необходимость продумывать поведение наследников класса. Здесь не важно, ООП или что-то ещё, важен конкретный механизм — наследование, и какие побочные эффекты он может за собой тянуть.

Где то на хабре был перевод статьи про текущие абстракции, я там как раз в комментариях высказал свою точку зрения на этот вопрос.
А почему вы пытаетесь научить автора сего топика (с положительным рейтингом, кстати) своей заминусованной статьей?
Это же tac, он знает все и получше вас. А если его статью заминусовали, значит его гениальные идеи просто не доходят до наших глупых голов.
О! Правильно говоришь. Не понимаю, как может не доходить элементарно классические вещи…
Учится хотел автор, у которого не серьезные аргументы в статье. Или если не хотел — зачем тогда писал — пафоса хотелось?
НЛО прилетело и опубликовало эту надпись здесь
Весь этот эпический… разговор в комментах, по-моему, как раз из-за этого и разразился: со стороны кажется, что автор ругает ООП, потому что он не даёт чёткого алгоритма написания программ. Да, это так. По-этому на мой взгляд статья несколько надуманная.
Если быть более точным, ООП не показывает пути наименьших граблей. Делай что хочешь, но по лбу будешь получать сам.
Нет, не показывает. Ровно как и функциональщина, процедурщина и прочие-прочие. Но разве кто-то обещал?
Вот функциональщина как раз показывает его довольно чётко — у вас всего 1, максимум 2 способо сделать что-то, не нарушив основные принципы. Питон хорошо с этим справляется. Даже C++, если придерживаться базовых конструкций языка, достаточно детерминированный язык. Java и C#, увы, таким свойством не обладают (хотя чёрт его знает, может это и к лучшему; но какие-то правила всё равно нужны).
Слово «код» встречается в статье 15 раз, а «проектирование» и однокоренные с ним — 4 раза. Действительно, статья больше о коде, чем о проектировании. ООП облегчает жизнь тем, кто проектирует.

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

Скажите это парню из Oracle, который соптимизировал какой-то механизм на долю миллисекунды и получил за это несколько сотен тысяч долларов. Существуют такие понятия, как высоконагруженные системы и системы реального времени.

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

> Объединение подпрограмм в одном месте? Для этого достаточно модулей.

Ошибка в рассуждении. Во первых, объединение подпрограмм и относящихся к ним данных, тем саммы убирая их из глобальной области доступа. Во-вторых, модулей не достаточно, т.к. между модулями не вводится никаких правил, обеспечивающих согласование интерфейсов модулей.
Во-первых, опять таки, модули. Я не знаю, чем несколько объектов, налепленных в одном пространстве имён в C# лучше, хуже тех же структур и функций, сгруппированных по смыслу в файле модуля. Во-вторых, о каких правилах идёт речь? Интерфейс модуля всегда чётко определён, а приватные функции аккуратно спрятаны в реализации.
>> Я не умею писать эффективные программы. Эффективные программы используют мало памяти — иначе сборщик мусора будет постоянно тормозить выполнение.

Отвечу известными цитатами
Преждевременная оптимизация — корень всех бед. // Тони Хоар
Простота — дух эффективности. // A. Freeman

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

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

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

У меня только что отработала программа, которую я запустил на сервере с 16 ядрами и 32 гигами памяти неделю и один день назад. На верхнем уровне она уже соптимизирована давно, а вот более мелкие оптимизации делать всё сложней и сложней. А по выходнам я играю с алгоритмами компьютерного зрения на C++, и вот там размещение картинки на в памяти сразу же приводит к потере real-time эффекта. Так что производительность тоже время от времени имеет значение.

Простота — дух эффективности. // A. Freeman

Мне в этом смысле больше нравится фраза Эйнштейна — «Make it as simple as possible, but not simpler».
Я считаю что математику не стоит писать на принципах ООП.
Скорей всего для оптимизации таких задач требуется хороший математик, который сможет найти оптимальное решение.
Мне повезло — большинство алгоритмов уже проработаны математиками, а иногда и реализованы в библиотеках. Хотя, конечно, помощь математика никогда не помешает.
«Я не понимаю что такое автомобиль!
Он состоит из трех ключевых парадигм: шасси, кузова и двигателя!
Шасси есть в любом виде транспорта, где надо перемещение.
Кузов везде где надо перемещать полезный груз!
а двигатель — везде где надо перемещать сторонней энергией ТрСр.
так что же такое АВТОМОБИЛЬ, ведь в нем нет ничего уникального!»
Помоему это шизофрения! ))
Наследование настолько опасно и неудобно, что крупные фреймворки (такие как Spring и EJB в Java) отказываются от них, переходя на другие, не объектно-ориентированные средства (например, метапрограммирование).
После этих строк — я еще раз уточнил экспириенс у автора — а кем он работал пять лет в энтерпрайз программировании?!
> я еще раз уточнил экспириенс у автора

Ну так ясно же по тексту статьи — студент, которому нужно помочь в понимании ООП, он же так и пишет…
Junior Developer? =)
Уже не оригинально — выше был пример про человека.
Вы не понимаете, что такое автомобиль, потому что ваше определение кривое. У самолета тоже есть шасси, кузов и двигатель, но это не автомобиль.
То ли вы Шелдон из Теории Большого Взрыва, то ли вы не дочитали до конца! Это был сарказм!
Это был сарказм, бьющий мимо цели
Зачем озвучивать ваше субъективное мнение?!
Спасибо вам за статью!
Отдельно спасибо за собаку и банк. И и да: данные это только данные, а не брат с сестрой.
Слава богу, а я то думал, собаку никто и не заметит! Можно сказаь, ради неё писал статью! :)

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

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

В результате от ООП остаются только интерфейсы :)
Дааааааа… неосиляторы пришли писать на хабр… куда катится мир. Самое обидное что я такие рассуждения не однократно встречаю на собеседовании. Когда собеседую программеров для приема на работу. Мне вот даже интересно 146 (?) плюсанувших пост это срез по таким же маструбирующим теоретикам?
Хорошо, не пойду к вам на собеседование.
Я не могу сказать, что большой специалист. Каждому, как говорится, свое. Кому как удобно, тот так и пишет. Когда я перелез с процедурного программирования на ООП, меня многое очень порадовало. По крайней мере, не было кучи хлама в коде, потому что старался делать все по книжкам. Что касается книг по ООП, то стоит прочитать Гради Буча, «Thinking in Java» и, недавно вышедшую, «Изучаем Java» от HeadFirst, в этих книгах все очень хорошо написано, как про ООП, так и про Java. А что касается сравнения языков программирования, то, на мой взгляд, с ООП хорошо работать только на «чистом-ООП» языке(как называет это автор), потому что на остальных вы так или инче будете отходить от концепции, потому что это позволено(яркий пример — С++)
Ну вот насчёт последнего предложения не соглашусь. ООП хорошо для определённых задач, но круг этих задач не так велик. Программируя на мультипарадигменном языке, таком как C++, вы имеете возможность использовать в каждом конкретном месте именно ту парадигму, которая в нём подходит лучше всего. Подходящий инструмент для подходящих задач.
Но я склонен думать, что раз уж здесь речь идет об ООП, поэтому и задачи решаются соответсвующие. То есть мы не используем ООП для того, что сложить два числа. Круг задач, решаемый кадждой парадигмой, это тема отдельной статьи.
Так а если у нас несколько типов задач, которые желательно решить не прибегая к другим языкам?
Тогда, думаю, стоит исходить из каких-то определенных критериев. К примеру, величина основной задачи, то есть если большая задача требует ООП, а мелкие нет, придется использовать ООП. Или если очень много задач средней величины, а одна большая, и только она требует ООП, то применять парадигму, подходящую для задач средней величины.
Все же относительно. Никто не станет кидаться с ООП, не оценив предварительно его уместность, так же и с другими парадигмами.
Может, заказчик хочет, чтобы код был машинно-независимый, тогда даже не смотря на то, что для решения задачи лучше всего подойдет С или С++, их уже нельзя использовать. Ну и так далее.
>>>Я также не знаю, как правильно декомпозировать функционал. В Python или C++, если мне нужна была маленькая функция для преобразования строки в число, я просто писал её в конце файла. В Java или C# я вынужден выносить её в отдельный класс StringUtils.

ОО способ — добавить ToNumber в string. См смолток и extension methods в C#

>>>В недо-ОО-языках я мог объявить ad hoc обёртку для возврата двух значений из функции (снятую сумму и остаток на счету). В ООП языках мне придётся создать полноценный класс РезультатТранзакции.

Непонятно почему добавление простого класса это плохо. Анонимные типы не противоречат ООП.

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

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

>>>Чтобы сделать один HTTP запрос мне нужно создать объект типа URL, затем объект типа HttpConnection, затем объект типа Request… ну, вы поняли.

Пробовали ли вы подсчитать, сколько байтов занимает каждый такой объект?

>>>В процедурном программировании я бы просто вызвал несколько процедур, передав им созданную на стеке структуру. Скорее всего, в памяти был бы создан всего один объект — для хранения результата. В ООП мне приходится засорять память постоянно.

Стек — это тоже память.
ОО способ — добавить ToNumber в string. См смолток и extension methods в C#

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

Непонятно почему добавление простого класса это плохо. Анонимные типы не противоречат ООП.

Боюсь неправильно понять. Можно пример?

Пробовали ли вы подсчитать, сколько байтов занимает каждый такой объект?

Смотря какой. Обычно 12-20 байт. Вам кажется это мало? Почитайте выше, я там описывал явление высокой детской смертности.

Стек — это тоже память.

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

С одной стороны принадлежит (это то, что можно сделать со строкой — превратить в строку), с другой стороны не принадлежит (описывает только некоторый аспект). Вот смолточники выбирают добавить в строку.

>>> Боюсь неправильно понять. Можно пример?

msdn.microsoft.com/en-us/library/bb397696.aspx

только в C# нельзя

anonymous f()
{
    return {name:"s";value:"v"};
}


>>>Смотря какой. Обычно 12-20 байт. Вам кажется это мало? Почитайте выше, я там описывал явление высокой детской смертности.

Никто не мешает создавать поменьше реквестов. К тому же посмотрите на любой современный язык — там сборка мусора, а в хаскеле к тому же ленивость. С другой стороны, к ващшим услугам C++ и object pascal без GC.

>>>Стек — это тоже память.

>>>Ок, куча. Стек автоматически зачищает память. Это не стоит нисколько. А вот объекты в памяти уже нужно как-то убирать.

Стек не автоматически убирает мусор. Он только убирает то, что в области видимости и только с того момента как эта видимость пропадает. То есть, например.

BigSTructure structure;
very_long_function(structure)
void very_long_function(BigSTructure &s)
{
  send_structure(s);
  // начиная отсюда s - мусор потому, точ не используется
  long_running_code();
}


structure будет висеть пока не закончится very_long_function();

msdn.microsoft.com/en-us/library/bb397696.aspx

Да, пора обновить свои знания по C# :)

Никто не мешает создавать поменьше реквестов. К тому же посмотрите на любой современный язык — там сборка мусора, а в хаскеле к тому же ленивость. С другой стороны, к ващшим услугам C++ и object pascal без GC.

Меня из последних Go привлекает. Там с объектами/структурами вообще интересно: компилятор сам решает, где разместить структуру. По возможности размещает на стеке, если объект используется вне функции или просто очень большой, то на хипе.

structure будет висеть пока не закончится very_long_function();

Ну это уже вырожденный случай. При желании можно разместить в памяти и занулить после `send_structure(s)`.

Там с объектами/структурами вообще интересно: компилятор сам решает, где разместить структуру. По возможности размещает на стеке, если объект используется вне функции или просто очень большой, то на хипе.

Вы не поверите, в C# то же самое. У Липперта была по этому поводу статья.
Насколько я знаю, на стеке только структуры располагаются. Другие обхекты располагаются в хипах которых много и зависят от поколения объекта и его размера.
Это не так. Размещение объектов не определено стандартом, определяется компилятором и может быть изменено в любой момент.
Есть ли хоть одна реализация, размещающая обычные обхекты в стеке?
Я таких не знаю.
Ну вот о том то и речь, что в Go эту идею уже реализовали :) Правда в Go нет разделения на структуры и объекты — даже если что-то размещается в памяти, то это всё равно структура, а значит логика обработки чуть попроще (без boxing/unboxing по крайней мере).
Красиво про собачек и счета :)
Наглядно.

А если задуматься в этих примерах кто главный? Главное действующее лицо, принимающее решение.
Собака, еда или «кушать»?
Не так важно — Собака.скушает(еду) или Еда.Употребится(собакой). Перестановка мест не принципиальна ;)
Главные в этом не они.

А тот кто
1) знает о существовании Собаки
2) знает о существовании Еды
3) знает о существовании действия «кушать».

И может это все вызвать в правильном сочетании :)

Т.е тот код из которого идет этот вызов. Допустим это будет объект Человек.

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

Другой вариант Человек.ВремяСейчас(Н часов) — мы сообщаем некое событие окружения, а не просим конкретное действие. А логика принятия решения (если Н = 8 или 20, то кормить) — спрятана внутри.

В этом случае главным остается человек.

Но… кажется это все не совсем ООП. :) Это скорее те модели, что требуются в реальной жизни, а чем они реализуются не так важно. Так называемая бизнес логика… Какой модуль/объект лишь исполнитель, а какой принимает решения.

А что до ООП — удобно при написании кода получать список действий, который применим к конкретному объекту. Ctrl+Tab. :) В отличии от функций… где это делается через «str....» «ltoi» и подобные договоренности.

Т.е структурирование namespace по большому счету. Модули этого не дают. И это то, что используется из ООП чаще всего :)
После попытки прочтения осталась одна мысль:
Верните открытую регистрацию на Хабре!
Зачем?
>Я не умею писать эффективные программы. Эффективные программы используют мало памяти — иначе сборщик мусора будет постоянно тормозить выполнение. Но чтобы совершить простейшую операцию в объектно-ориентированных языках приходится создавать дюжину объектов. Чтобы сделать один HTTP запрос мне нужно создать объект типа URL, затем объект типа HttpConnection, затем объект типа Request… ну, вы поняли. В процедурном программировании я бы просто вызвал несколько процедур, передав им созданную на стеке структуру. Скорее всего, в памяти был бы создан всего один объект — для хранения результата. В ООП мне приходится засорять память постоянно.

В принципе теоретически можно же добиться такого, чтобы все эти объекты на этапе компиляции существовали, но в её процессе превращались в те самые несколько структур на стеке с парой вызовов процедур. В этом нет ничего невозможного.
Невозможного нет, согласен. Но это будет оооочень некрасиво :)
«В процедурном программировании я бы просто вызвал несколько процедур, передав им созданную на стеке структуру. Скорее всего, в памяти был бы создан всего один объект — для хранения результата. В ООП мне приходится засорять память постоянно.»
Уже даже и URL не нужен? Интересно.
Результат — сложная штука. HTTP stauts, хедеры, куки, тело (если есть) — уверены, что хотите это всё впихнуть в один объект?

Что касается «приходится засорять память» — URL и Connection можно и нужно реюзать.
Если уж очень напрягает, можно и HTTP Client написать так, чтоб поменьше создавать ненужных объектов, но вы параноите по поводу «я вызвал new лишний раз == всё, моя программа не эффективна!»

URL — в смысле объект типа URL или строка адреса? Строка может передаваться извне или висеть в пуле строк. А объект URL — да, можно и не создавать, в принципе.

А с респонсом есть другие варианты? Или вы предлагаете разбить данные респонса на несколько частей и положить в разные объекты, не имеющие к этим данным отношения?

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

Простым языком — скрыть, отделить, сгруппировать куски выполняемого кода в логические блоки, создав в результате понятную программисту структуру приложения, вот и ооп)

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

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

По моему очень здорово иметь удобный способ управлять, например:

Люди->ВыбратьЧеловека('Вася')->ОтправитьЗаВодкой()

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

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

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

Функции просто и легко, с ооп нужно быть на два шага впереди, зато в итоге работы уменьшиться.

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

И не забывать документировать)
Про паттерны с вами не соглашусь. Если это правильно применять, то это — хорошая штука. Конечно, если городить их, где не попадя, то вреда больше, чем пользы. Да и, на самом деле, хороший программист сам применяет паттерны к приложениям, если это требуется, и зачастую не знает, что это паттерн, но знать их нужно, на всякий случай.
Про паттерны и GoF там выше было обсуждение. Лично у меня названия паттернов появляются обычно уже после проектирования системы.

Насколько я понимаю, ваше отношение к не-ООП сформировано работой с C и C++. Попробуйте для интереса взглянуть на языки с хорошей модульной системой. Oberon будет идеальным — он маленький и простой, а модульная система просто прекрасна.
Люди->ВыбратьЧеловека('Вася')->ОтправитьЗаВодкой()

Намного ли отличается от:

(отправить-за "водка" (выбрать :имя "Вася" (все-пиплы)))

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

Не совсем понял. С реализацией на классах ничего инклудить не надо? Или распихивать по файлам/папкам не придется?
Люди->ВыбратьЧеловека('Вася')->ОтправитьЗаВодкой()
Намного ли отличается от:
(отправить-за «водка» (выбрать: имя «Вася» (все-пиплы))


Это смотря что вы хотите сделать — избавиться от Васи или пополнить запас водки =)
Не понял комментарий, но, с ссылкой на завершающий смайлик, сочту за шутку.

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

Настоящие функции, в математическом смысле, как раз и славятся тем, что используя минимальный набор элементов языка можно составлять, как из кирпичиков, огромные конструкции. А вместе с грамотной системой типов эти конструкции будут весьма прочные. А уж какие возможности это открывает для рефакторинга!
<offtop>Вот это простыня! Застал тред на 5й сотне комментариев</offtop>
Это не простыня, это бурления :) Сами догадайтесь чего :)
А тем временем, пошла 8я сотня!
Я тоже не понимал ООП. По пять раз перечитывал книжки и не понимал, в чем разница между объектами, классами, интерфейсами и структурами.

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

Всякие нюансы типа наследования и полиморфизма относятся к классам, а не к объектам. Сюда же относится интерфейсы, неймспейсы и прочая нечисть. И вообще это имеет отношение к проектированию, а не к программированию.

Вон, в 1С классов нет, неймспейсов и интерфейсов тоже нет. Там чистое ООП.
Автор, мне кажется, Вы слишком формально привязались к «признакам ООП».
По большому счету, если не привязываться к формализму, ООП-подход возможен и активно применяется даже в старом добром Си.
С другой стороны даже в Java можно программировать «процедурно», если записать все в одном огромном классе.
Вопрос применения ООП — не в использовании «признаков ООП» в своей программе, а в методах проектирования и построения кода.

ООП — это в первую очередь манипуляция совокупностью данных как целым (объектом). Причем, совокупность данных для правильного доступа к ним и управления ими должна предоставлять набор методов (интерфейс), возможно, не один.
Необходимость наличия полиморфизма, абстракции, инкапсуляции, наследования как непременных свойств ООП языка вдалбливается для упрощения понимания ООП начинающими. Мне кажется, подобный формализм только путает и приводит к заблуждению, что само использование, например, Java делает код следующим парадигме ООП. В реальности парадигма ООП не требует обязательного наличия ВСЕХ перечисленных свойств, больше того, только наличие этих свойств не делает программу основанной на ООП («раздутые классы», getters/setters, раскрывающие структуру объекта, и т.п.).

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

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


C:
struct Animal {
   void (*walk)() = NULL; 
} dog, cat, giraffe; 
...
dog->walk();
cat->walk();
giraffe->walk();


Haskell:
type Animal = Cat | Dog | Giraffe

walk Cat = ...
walk Dog = ...
walk Giraffe = ...
Поскольку walk() в данном случае это не метод объекта, а банальная ссылка на функцию, то выгуливать она будет что угодно, кроме структуры, в которой она находится. Ибо нихрена она о структуре не знает и узнать не может.
ОК:
struct Animal {
   void (*walkMe)(Animal a) = NULL; 
} dog, cat, giraffe; 
...
void walk(Animal a) {
   a->walk(a);
}
А так кислятина получается =)
В смысле? Это почти то, как реализовано ООП в популярных языках, за исключением того, что указатели на функции хранятся в отдельной таблице, а не в каждом объекте.
В «классическом» ООП нет нужды дублировать ссылку на объект в качестве параметра метода.
Так, давайте разберёмся. Мы о чём щас говорим? Был вопрос, как реализовать полиморфизм в функциональных и процедурных языках. Я показал как. Про отличие от релизаций в «классических» языках я тоже сказал. Если хотите полное соответствие — сделайте структуры CatClass, DogClass и т.д. в глобальной области и сохраняйте указатели на функции туда. Факт в том, что диспетчеризацию по типу можно сделать в любом языке.
Я хочу сказать, что реализовать-то можно, но без должной поддержки со стороны компилятора очень кислый полиморфизм получается. Декларация «метода» оторвана от контекста, требуются лишние телодвижения по инициализации ссылок «методов», некомфортный вызов методов. Более того, сам полиморфизм (переопределение методов) не будет работать в Си, поскольку там с типизацией строго. Можно, конечно, замутить с принудительной типизацией, но тем самым мы будем постоянно держать пистолет нацеленным в ногу.

Переопределение методов как раз таки будет работать замечательно — просто заменяете один указатель другим. А про лишние телодвижения, так чего вы ожидали? Если какой-то язык поддерживает какую-то парадигму, то естественно, что в нём использование средств этой парадигмы будет проще. Единственное известное мне исключение — это Lisp, но там вы работаете с голым синтаксическим деревом, поэтому и «синтаксис» можно подстроить под что угодно.
Я ожидаю от языка удобства и продуктивности реализации моих задач. Любую задачу можно реализовать на ассемблере, но во многих случаях это будет долго и мучительно.

ООП вполне облегчает работу со сложными задачами. Но сама реализация ООП в разных языках сильно различается по комфорту в плане работы с классами.
Я не понимаю, что вы пытаетесь доказать. Что писать в ОО стиле на ОО языках проще, чем на не ОО языках? Ну это очевидно, по-моему.
Вы не поняли!
Вопрос был не о языках, а о парадигмах. Вы слишком привязываетесь к языку и смешиваете язык и парадигму в своих рассуждениях.
Я продолжаю утверждать, что использование ООП-языка не гарантирует наличия ООП парадигмы. И использование ООП-парадигмы возможно вне языков, считающихся ООП-языками.

Мой же вопрос был о том, как в процедурной парадигме реализовать привязку данных и действий с ними. Вы привели некорректный с точки зрения процедурного подхода ответ. :)
В процедурном подходе надо завести функцию «гулять», в которую передается тип животного и для каждого животного заводить метод выгула. Причем придется переделывать функцию каждый раз, когда список «поддерживаемых животных» расширяется.
В ООП-парадигме для добавления нового животного Вам достаточно завести новый тип, наследующий интерфейс «животное» и правильно определить методы интерфейса.
Вы своим примером показываете, что средства Си позволяют разработать ООП-программу.
Я, собственно, только «за».
Показательные примеры в огромном количестве на сайте Khronos Group. Практически все SDK (OpenGL, OpenCL, etc.) — реализуют парадигму ООП. Средствами Си.
Так блин, то, что вы называете ООП стилем — это и есть привязка методов к данным (если быть точным, то к типам). Естественно, привязать методы к данным не привязывая методы к данным невозможно. Это можно назвать ООП, но по факту такая техника использовалась задолго до появления ООП.

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

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

По поводу «задачи». Я не предлагал ТЗ, а задал простой вопрос: как без использования ООП подхода реализовать выгул любого животного?
В ООП каждый новый вид животных (тип объектов) будет иметь конкретную реализацию функции выгула.
В процедурном стиле заводится одна функция выгула, которая «знает», как выгуливать каждый из известных видов животных и, возможно, умеет выполнять некий выгул по умолчанию. Функцию переделывать придется, если при введении нового вида животного метод его выгула отличен от метода выгула по умолчанию.
Я, если что, именно про процедурную парадигму, а не про Ваш ООП-код на процедурном языке.
Так, давайте разбираться. Привязка к данным — это очень общий термин. Замыкания (closures), например, тоже могут привязываться к дынным, но объектами в смысле ООП от этого не становятся. То, о чём идёт речь, и что вы меня просите реализовать в ООП принято называть полиморфизмом, в более общем случае — диспетчеризация вызова по типу: в зависимости от типа объекта выбирается нужная функция, в которую передаётся объект и параметры вызова.
Диспетчеризация по типу есть на многих языках, в том числе ни разу не ООПэшных — смотрите пример на Haskell сверху. Там это pattern matching, к ООП не имеет никакого отношения (для сравнения, в Haskell есть также классы типов, которые уже реализует ОО подход — свободное расширение любыми типами). Но если вам хочется называть диспетчеризацию по типу ООП подходом, ради б-га.

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

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

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

В отпуск уезжаю, поэтому, чтобы подытожить то, что я говорил (и не говорил :))

1) ООП подразумевает объединение данных и методов для работы с ними и оформление в виде единого целого — объекта.
2) приемы, внесенные в ООП, могли появиться до выделения его в отдельную парадигму, но именно с оформлением парадигмы ООП и появлением языков, предоставляющих удобные средства реализации этой парадигмы, ООП получило распространение и популярность.
3) использование ООП-языка не гарантирует наличия парадигмы ООП.
4) парадигма ООП может применяться в не ООП языках.
5) наличие всех формальных «признаков ООП» — не обязательно для следования парадигме ООП.

Мне кажется, Вы уже должны были понять, о чем я говорю. Вы слишком формально относитесь к признакам и пытаетесь поставить знак равенства между ООП парадигмой и ООП языком. Это неправильно.
В любом случае, как говорил мой преподаватель по ТОЭ, я кончил (с)
Ну, как я понял, выгул каждого животного отдельным методом предлагает, собственно, выделение отдельного метода на каждый тип животного. А тип уже можно задать и динамически, через отдельное поле некоторой общей структуры. Впрочем это не так важно.

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

Так вот, как и где именно хранить свяки данные-метод — это уже технические детали. Методы можно сохранять в виде поля сразу в структуру, можно создать отдельную таблицу (hash map) или дерево (tree map), где будет хранится такое отображение, можно сохранять в общего предка. Вариантов много. Но по вашей логике всё равно получится, что это ООП, потому что не используется switch-case. Т.е. switch-case — это плохо, а всё, кроме switch-case — это ООП. Ну, если вы используете такую терминологию, то конечно, красиво в не ООП это реализовать нельзя, просто потому, что ООП, в рамках данной терминологии, включает все красивые решения :)

Хорошо вам отдохнуть, и не думайте о плохом. ООП никуда не денется, а реальная жизнь всё-равно важней :)
А это уже не процедурный подход. ;)
На Си, как я уже сказал, можно применять парадигму ООП, что Вы и продемонстрировали.
В процедурном подходе «мухи — отдельно, котлеты — отдельно».
Так это ж классический пример реализации ООП на C.
Читайте тред под этим комментарием — там я довольно подробно расписал ситуацию.
Расскажете, как в процедурном или функциональном подходе создать обобщенный «выгуливающий» метод для «домашних животных»?

Python знаешь? Там у каждого метода обязательно первым параметром идет self. Примитивно, но эффективно.

То же самое в Сишной библиотеке ffmpeg (и многих других) — первым параметром функций идет «контекст». По сути то же самое ООП, только синтаксис другой.

Считаю, что ООП — на самом деле КОП (классово-ориентированное программирование). Потому что объекты там сугубо вторичны.
а что вы думаете про язык self?
Ничего не думаю, я на нем ничего не писал. Но вообще концепцию слотов вместо методов считаю перспективной — это и thread-safe, и упрощение MVC.

Писал на ObjectiveC (который тоже потомок smalltalk) — тот же самый С++ по сути, только синтаксис забавнее.
Без разницы, что там идет первым параметром. В С++ можно считать, что всегда есть параметр this, который передается неявно.
Речь о том, что это уже не процедурное проектирование.
Как только вы начинаете рассматривать данные в совокупности с действиями над ними, вы начинаете мыслить категориями объектов.
А средства, предоставляемые языком, в данном случае вторичны.
что уж скрывать не забавнее, а дибильнее...:)
и что же, мне теперь перейти обратно на C?
Да нет, почему же. Просто будьте осторожны, когда читаете о том, как нужно писать программы в ООП. Как сказал Татхагата, «Не верьте моим словам лишь потому, что их сказал Будда. Проверяйте всё сами.»
Ну, на Си писать неудобно :)
Но можно, как показывают библиотеки OpenMAX AL, OpenCL, OpenGL, которые написаны на Си, но используют парадигму ООП.
Apple CoreFoundation так же полностью написан на C. При этом там все в стиле ООП.
Столько комментов, что сижу играюсь с полосочкой Scroll To Top :D
Думаю, нас всех спасут интерфейсы как конструкции языка и возможность делать друзей не для классов, а только для интерфейсов.
Не интерфейсы, а контракты.
Предлагаю наградить автора орденом сетевого тролля 2-й степени. А если комменты за тысячу зашкалят, то и 1-й.
Если долго высушивать ООП над медленным огнём, останется одна динамическая диспетчеризация — как можно более позднее связывание. Добавить к этому отсутствие разделяемой памяти, и лучшим ОО-языком современности станет Erlang.

А в качестве теоретической базы к ООП следовало бы давать теорию категорий.
Статья замечательная. Не думали ее на английский перевести и выложить на Hacker News или Reddit?
Спасибо за отзыв. Честно говоря, перевод статьи — это довольно загрузная задача, а мне бы хотелось написать ещё пару отдельных технических статей. Хотя, конечно, было бы интересно посмотреть, как на это отреагируют западные разработчики. В общем, будет видно :)
ВыгулМенеджер — естественная вещь в свете того, что по мере уточнения модели выясняется, что в процессе выгула участников оказывается больше, чем только хозяин и собака.
Вот ведь нет такого что можно либо только Алгоритмический подход или только Объектно ориентированный. В простецких программах, когда сложность небольшая, можно и попроще сделать. Когда сложность увеличивается, можно подключать какие-то принципы из о-о проектирования. Нет ли каких-либо книг по тому, когда и какие принципы надо подключать? Хотелось бы почитать умных и опытных людей…
Подождите. Stateless — это, фактически, переизобретение процедурного программирования под ООП. Кто хочет писать в процедурном стиле, так и делает — объявляет объект с public-полями и без единого метода, потом объявляет класс с public-методами и без единого поля. И получаете желанное:

СобачныйМенеджер.выгулять(собаку)
БанковскийМенеджер.снятьс(аккаунт, сумма)

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

Публикации

Истории