Comments 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 создается, с помощью ключевого слова 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 это или есть там некая логика
А пользователю интерфейса это и не важно. Auto — это скорее как сахар для разработчика.
Да. Просто про 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.

Под инкапсуляцией обычно понимают вообще сокрытие деталей реализации а не только состояния объекта.
Например порядок вызова приватных методов в классе тоже может требовать согласованности.
В тоже время поля класса могут быть публичными, открывая к нему доступ. И это не является нарушением инкапсуляции если предусмотрено в архитектуре класса.
Ну и чего там относящегося к вопросу, по ссылке, которую Вы дали?
Прочитал — всякие мысли про 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 поля могут не нарушать инкапсуляцию, потому что мы можем в любой момент заменить прямой доступ к полю на сколь угодно сложную логику свойства, а клиенты объекта этого не заметят.
Давайте попробуем прояснить непонятные мне моменты.

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. Это к вашей реализации 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 внутри реализован вот так то (что как бы плохо), поэтому и надо посмотреть как пользователь — тогда тебе не важно что там внутри.

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

Происходят они тогда, когда в классе, который например, представляет собой настройки, прочитанные из конфига, вместо десятка публичных полей пишут двадцатку get-set методов.
И тогда класс занимает не свои законные 30 строчек (5 на заголовок, по 2 на каждое поле (атрибут с ключом в конфиге и собственно объявление поля), плюс 5 на всякие скобки), а например 400. Так получается, если написать на каждое поле геттер-сеттер, да на них еще явадоков. И потом смотришь на эту простыню и думаешь «ну зачем это было нужно делать?»
Затем, что если вы решите изменить гетер с
return this.field;

на
проверяем поле, меняем поле
return this.field;


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

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

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

Поэтому я бы сделал статический класс с тремя методами:
float getAngle( Point xy )
float getDistance( Point xy )
void setPolarCoords( Point xy, float r, float phi )

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

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

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

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

// думая о конфиге я вспоминаю web.config из asp.net

А что будете делать, когда кто-то попытается присвоить некорректное значение в созданный Вами сеттер? Исключения кидать?
Псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо 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 можно подменить не меняя вызывающий код. Корявая абстракция лучше чем никакой.
С точки зрения инкапсуляции в этом классе — ничем не лучше. Подменить же можно только в подклассе, а в родительском так и останется нарушенная инкапсуляция.
Еще раз. То что 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».
Если класс уже ушел в продакшн, то уже ничего не поменяешь

Как это не поменяешь? Да сразу после выхода в продакшн изменения учащаются, по сравнению со стадией разработки. Стабильным остаётся только интерфейс, но реализация — пилится и точится под весьма динамичные требования, пока не будет достигнут приемлемый статус и не будут выловлены все баги и бутылочные горлышки на реальных задачах.
Извините, но я нихрена не понял. :) Почему нарушена инкапсуляция? Вы знаете что у персоны есть имя, так? Так. Но вы знаете как это имя вы получаете? Нет. Реализация скрыта? Да. В чем проблема?
Проблема не в получении имени, а в его замене. Ну, вместо имени подаст пользователь в 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)

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

Это — никак.
В самом начале же сказано:

> Псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо 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 проще вернуться к плану А — большим жирным комментом «НЕ ИСПОЛЬЗОВАТЬ НАПРЯМУЮ» :)
Если вы уберёте из класса 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% записей должно вести к коренной переделке программы?