Пользователь
0,0
рейтинг
17 июля 2012 в 07:51

Разработка → Я не знаю ООП

Я не умею программировать на объектно-ориентированных языках. Не научился. После 5 лет промышленного программирования на Java я всё ещё не знаю, как создать хорошую систему в объектно-ориентированном стиле. Просто не понимаю.

Я пытался научиться, честно. Я изучал паттерны, читал код open source проектов, пытался строить в голове стройные концепции, но так и не понял принципы создания качественных объектно-ориентированных программ. Возможно кто-то другой их понял, но не я.

И вот несколько вещей, которые вызывают у меня непонимание.

Я не знаю, что такое ООП


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

Принято считать, что объектно-ориентированное программирование строится на 4 основных принципах (когда я был мал, их было всего 3, но ведь тогда и деревья были большими). Эти принципы:

  • Абстракция
  • Инкапсуляция
  • Наследование
  • Полиморфизм

Смахивает на свод правил, не так ли? Значит вот оно, те самые правила, которым нужно следовать в 95% случаев? Хмм, давайте посмотрим поближе.

Абстракция

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

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

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

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

Главный козырь инкапсуляции в сокрытии реализации. Клиентский код видит только интерфейс, и только на него может рассчитывать. Это развязывает руки разработчикам, которые могут решить изменить реализацию. И это действительно круто. Но вопрос опять же в том, причём тут ООП? Все вышеперечисленные парадигмы подразумевают сокрытие реализации. Программируя на C вы выделяете интерфейс в header-файлы, Oberon позволяет делать поля и методы локальными для модуля, наконец, абстракция во многих языках строится просто посредствам подпрограмм, которые также инкапсулируют реализацию. Более того, объектно-ориентированные языки сами зачастую нарушают правило инкапсуляции, предоставляя доступ к данным через специальные методы — getters и setters в Java, properties в C# и т.д. (В комментариях выяснили, что некоторые объекты в языках программирования не являются объектами с точки зрения ООП: data transfer objects отвечают исключительно за перенос данных, и поэтому не являются полноценными сущностями ООП, и, следовательно, для них нет необходимости сохранять инкапсуляцию. С другой стороны, методы доступа лучше сохранять для поддержания гибкости архитектуры. Вот так всё непросто.) Более того, некоторые объектно-ориентированные языки, такие как Python, вообще не пытаются что-то скрыть, а расчитывают исключительно на разумность разработчиков, использующих этот код.

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

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

Значит, вот оно — то, что выделяет ООП как парадигму среди других? Хмм… если так, то почему мы так редко используем его в реальном коде? Помните, я говорил про 95% кода, подчиняющихся правилам доминирующей парадигмы? Я ведь не шутил. В функцинальном программировании не меньше 95% кода использует неизменяемые данные и функции без side-эффектов. В модульном практически весь код логично расфасован по модулям. Преверженцы структурного программирования, следуя заветам Дейкстры, стараются разбивать все части программы на небольшие части. Наследование используется гораздо реже. Может быть в 10% кода, может быть в 50%, в отдельных случаях (например, при наследовании от классов фреймворка) — в 70%, но не больше. Потому что в большинстве ситуаций это просто не нужно.

Более того, наследование опасно для хорошего дизайна. Настолько опасно, что Банда Четырех (казалось бы, проповедники ООП) в своей книге рекомендуют при возможности заменять его на делегирование. Наследование в том виде, в котором оно существует в популярных ныне языках ведёт к хрупкому дизайну. Унаследовавшись от одного предка, класс уже не может наследоваться от других. Изменение предка так же становится опасным. Существуют, конечно, модификаторы private/protected, но и они требуют неслабых экстрасенсорных способностей для угадывания, как класс может измениться и как его может использовать клиентский код. Наследование настолько опасно и неудобно, что крупные фреймворки (такие как Spring и EJB в Java) отказываются от них, переходя на другие, не объектно-ориентированные средства (например, метапрограммирование). Последствия настолько непредсказуемы, что некоторые библиотеки (такие как Guava) прописывает своим классам модификаторы, запрещающие наследование, а в новом языке Go было решено вообще отказаться от иерархии наследования.

Полиморфизм

Пожалуй, полиморфизм — это лучшее, что есть в объектно-ориентированном программировании. Благодаря полиморфизму объект типа Person при выводе выглядит как «Шандоркин Адам Имполитович», а объект типа Point — как "[84.23 12.61]". Именно он позволяет написать «Mat1 * Mat2» и получить произведение матриц, аналогично произведению обычных чисел. Без него не получилось бы и считывать данные из входного потока, не заботясь о том, приходят они из сети, файла или строки в памяти. Везде, где есть интерфейсы, подразумевается и полиморфизм.

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

(Впрочем, это моё мнение. Вы всегда можете не согласиться.)

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

Чьё ООП круче?


Из предыдущей части видно, что языки программирования могут сильно отличаться по способу реализации объектно-ориентированного программирования. Если взять совокупность всех реализаций ООП во всех языках, то вероятнее всего вы не найдёте вообще ни одной общей для всех черты. Чтобы как-то ограничить этот зоопарк и внести ясность в рассуждения, я остановлюсь только одной группе — чисто объекто-ориентированные языки, а именно Java и C#. Термин «чисто объектно-ориентированный» в данном случае означает, что язык не поддерживает другие парадигмы или реализует их через всё то же ООП. Python или Ruby, например, не буду являться чистыми, т.к. вы вполне можете написать полноценную программу на них без единого объявления класса.

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

Smalltalk. В отличие от своих современных коллег, этот язык имел динамическую типизацию и использовал message-passing style для реализации ООП. Вместо вызовов методов объекты посылали друг другу сообщения, а если получатель не мог обработать то, что пришло, он просто пересылал сообщение кому-то ещё.

Common Lisp. Изначально CL придерживался такой же парадигмы. Затем разработчики решили, что писать `(send obj 'some-message)` — это слишком долго, и преобразовали нотацию в вызов метода — `(some-method obj)`. На сегодняшний день Common Lisp имеет развитую систему объектно-ориентированного программирования (CLOS) с поддержкой множественного наследования, мультиметодов и метаклассов. Отличительной чертой является то, что ООП в CL крутится не вокруг объектов, а вокруг обобщённых функций.

Clojure. Clojure имеет целых 2 системы объектно-ориентированного программирования — одну, унаследованную от Java, и вторую, основанную на мультиметодах и более похожую на CLOS.

R. Этот язык для статистического анализа данных также имеет 2 системы объектно-ориентированного программирования — S3 и S4. Обе унаследованы от языка S (что не удивительно, учитывая, что R — это open source реализация коммерческого S). S4 по большей части соотвествует реализациям ООП в современных мейнстримовых языках. S3 является более легковесным вариантом, элементарно реализуемым средствами самого языка: создаётся одна общая функция, диспетчеризирующая запросы по атрибуту «class» полученного объекта.

JavaScript. По идеологии похож на Smalltalk, хотя и использует другой синтаксис. Вместо наследования использует прототипирование: если искомого свойства или вызванного метода в самом объекте нет, то запрос передаётся объекту-прототипу (свойство prototype всех объектов JavaScript). Интересным является факт, что поведение всех объектов класса можно поменять, заменив один из методов прототипа (очень красиво, например, выглядит добавление метода `.toBASE64` для класса строки).

Python. В целом придерживается той же концепции, что и мейнcтримовые языки, но кроме этого поддерживает передачу поиска атрибута другому объекту, как в JavaScript или Smalltalk.

Haskell. В Haskell вообще нет состояния, а значит и объектов в обычном понимании. Тем не менее, своеобразное ООП там всё-таки есть: типы данных (types) могут принадлежать одному или более классам типов (type classes). Например, практически все типы в Haskell состоят в классе Eq (отвечает за операции сравнения 2-х объектов), а все числа дополнительно в классах Num (операции над числами) и Ord (операции <, <=, >=, >). В менстримовых языках типам соответствуют классы (данных), а классам типов — интерфейсы.

Stateful или Stateless?


Но вернёмся к более распространённым системам объектно-ориентированного программирования. Чего я никогда не мог понять, так это отношения объектов с внутренним состоянием. До изучения ООП всё было просто и прозрачно: есть структуры, хранящие несколько связанных данных, есть процедуры (функции), их обрабатывающие. выгулять(собаку), снятьс(аккаунт, сумма). Потом пришли объекты, и это было тоже ничего (хотя читать программы стало гораздо сложней — моя собака выгуливала [кого?], а аккаунт снимал деньги [откуда?]). Затем я узнал про сокрытие данных. Я всё ещё мог выгулять собаку, но вот посмотреть состав её пищи уже не мог. Пища не выполняла никаких действий (наверное, можно было написать, что пища.съесть(собака), но я всё-таки предпочитаю, чтобы моя собака ела пищу, а не наоборот). Пища — это просто данные, а мне (и моей собаке) нужно было просто получить к ним доступ. Всё просто. Но в рамки парадигмы влезть было уже невозможно, как в старые джинсы конца 90-х.

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

Не успел я насладиться просветлением, как увидил в интернетах слово stateless (готов поклясться, оно было окружено сиянием, а над буквами t и l висел нимб). Короткое изучение литературы открыло чудесный мир прозрачного потока управления и простой многопоточности без необходимости отслеживать согласованность объекта. Конечно, мне сразу захотелось прикоснуться к этому чудесному миру. Однако это означало полный отказ от любых правил — теперь было непонятно, следует ли собаке самой себя выгуливать, или для этого нужен специальный ВыгулМенеджер; нужен ли аккаунт, или со всей работой справится Банк, а если так, то должен он списывать деньги статически или динамически и т.д. Количество вариантов использования возрасло экспоненциально, и все варианты в будущем могли привести к необходимости серьёзного рефакторинга.

Я до сих пор не знаю, когда объект следует сделать stateless, когда stateful, а когда просто контейнером данных. Иногда это очевидно, но чаще всего нет.

Типизация: статическая или динамическая?


Еща одна вещь, с которой я не могу определиться относительно таких языков, как C# и Java, это являются они статически или динамически типизированными. Наверное большинство людей воскликнет «Что за глупость! Конечно статически типизированными! Типы проверяются во время компиляции!». Но действительно ли всё так просто? Правда ли, что программист, прописывая в параметрах метода тип X может быть уверен, что в него всегда будут передаваться объекты именно типа X? Верно — не может, т.к. в метод X можно будет передать параметр типа X или его наследника. Казалось бы, ну и что? Наследники класса X всё равно будут иметь те же методы, что и X. Методы методами, а вот логика работы может оказаться совершенно другой. Самый распространённый случай, это когда дочерний класс оказывается соптимизированным под другие нужды, чем X, а наш метод может рассчитывать именно на ту оптимизацию (если вам такой сценарий кажется нереалистичным, попробуйте написать плагин к какой-нибудь развитой open source библиотеке — либо вы потратите несколько недель на разбор архитектуры и алгоритмов библиотеки, либо будете просто наугад вызывать методы с подходящей сигнатурой). В итоге программа работает, однако скорость работы падает на порядок. Хотя с точки зрения компилятора всё корректно. Показательно, что Scala, которую называют наследницей Java, во многих местах по умолчанию разрешает передавать только аргументы именно указанного типа, хотя это поведение и можно изменить.

Другая проблема — это значение null, которое может быть передано практически вместо любого объекта в Java и вместо любого Nullable объекта в C#. null принадлежит сразу всем типам, и в то же время не принадлежит ни одному. null не имеет ни полей, ни методов, поэтому любое обращение к нему (кроме проверки на null) приводит к ошибке. Вроде бы все к этому привыкли, но для сравнения Haskell (да и та же Scala) заставлют использовать специальные типы (Maybe в Haskell, Option в Scala) для обёртки функций, которые в других языках могли бы вернуть null. В итоге про Haskell часто говорят «скомпилировать программу на нём сложно, но если всё-таки получилось, значит скорее всего она работает корректно».

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

Какая разница, как называется такая типизация, если все правила всё равно известны? Разница в том, с какой стороны подходить к проектированию архитектуры. Существует давний спор, как строить систему: делать много типов и мало функций, или мало типов и много функций? Первый подход активно используется в Haskell, второй в Lisp. В современных объектно-ориентированных языках используется что-то среднее. Я не хочу сказать, что это плохо — наверное у него есть свои плюсы (в конце концов не стоит забывать, что за Java и C# стоят мультиязыковые платформы), но каждый раз приступая к новому проекту я задумываюсь, с чего начать проектирования — с типов или с функционала.

И ещё...


Я не знаю, как моделировать задачу. Считается, что ООП позволяет отображать в программе объекты реального мира. Однако в реальности у меня есть собака (с двумя ушами, четырмя лапами и ошейником) и счёт в банке (с менеджером, клерками и обеденным перерывом), а в программе — ВыгулМенеджер, СчётФабрика… ну, вы поняли. И дело не в том, что в программе есть вспомогательные классы, не отражающие объекты реального мира. Дело в том, что поток управления изменяется. ВыгулМенеджер лишает меня удовольствия от прогулки с собакой, а деньги я получаю от бездушного БанкСчёта (эй, где та милая девушка, у которой я менял деньги на прошлой неделе?).

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

Я также не знаю, как правильно декомпозировать функционал. В Python или C++, если мне нужна была маленькая функция для преобразования строки в число, я просто писал её в конце файла. В Java или C# я вынужден выносить её в отдельный класс StringUtils. В недо-ОО-языках я мог объявить ad hoc обёртку для возврата двух значений из функции (снятую сумму и остаток на счету). В ООП языках мне придётся создать полноценный класс РезультатТранзакции. И для нового человека на проекте (или даже меня самого через неделю) этот класс будет выглядеть точно таким же важным и фундаментальным в архитектуре системы. 150 файлов, и все одинаково важные и фундаментальные — о да, прозрачная архитектура, прекрасные уровни абстракции.

Я не умею писать эффективные программы. Эффективные программы используют мало памяти — иначе сборщик мусора будет постоянно тормозить выполнение. Но чтобы совершить простейшую операцию в объектно-ориентированных языках приходится создавать дюжину объектов. Чтобы сделать один HTTP запрос мне нужно создать объект типа URL, затем объект типа HttpConnection, затем объект типа Request… ну, вы поняли. В процедурном программировании я бы просто вызвал несколько процедур, передав им созданную на стеке структуру. Скорее всего, в памяти был бы создан всего один объект — для хранения результата. В ООП мне приходится засорять память постоянно.

Возможно, ООП — это действительно красивая и элегантная парадигма. Возможно, я просто недостаточно умён, чтобы понять её. Наверное, есть кто-то, кто может создать действительно красивую программу на объектно-ориентированном языке. Ну что ж, мне остаётся только позавидовать им.
@ffriend
карма
208,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

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

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

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

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

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

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

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

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

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

              За ссылки спасибо, чуть позже почитаю.
            • +3
              Прочитал, ещё раз спасибо. В принципе, и Симман, и Бейли, на которого Симман ссылается, описывают те же идеи, которые использую в своей работе и я. Однако, ни моя, ни даже их терминология не является общепринятой в мире ООП. Бейли называет сокрытие информации инкапсуляцией, я использую более общий термин — поддержание согласованности объекта. Мне кажется, это более точный и понятный термин, чем «заключение в капсулу». Симман говорит, что 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 (читать как: натягивают понятие инкапсуляции туда, где оно не показательно). Это разброс терминологии, вызванный (а может и вызвавший) отсутствием чёткого определения и строгих принципов. О чём, собственно, и статья.
              • +7
                Извините что влезу, но…

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        Расскажите это разработчикам библиотек. Узнайте что с ними сделают пользователи библиотек за такое, когда после очередного update надо будет переделывать свой проект.
        • +1
          > глядя со стороны интерфейса класса невозможно сказать auto это или есть там некая логика
          А пользователю интерфейса это и не важно. Auto — это скорее как сахар для разработчика.
          • 0
            Да. Просто про auto property я упомянул потому что в исходном сообщении был упор на то, что обращение к property с кодом вида «return this._myValue» является раскрытием реализации.
        • 0
          Расскажите это разработчикам библиотек. Узнайте что с ними сделают пользователи библиотек за такое, когда после очередного update надо будет переделывать свой проект.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            private name
            public getName(): return this.name

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                  Причем в документации был описан порядок, но от этого легче не становилось
                                  • 0
                                    Почему теряет? Инкапсуляция — защита содержимого от случайного изменения в каком-то смысле. Каким методом защищать — выбор разработчика :)
                                    • 0
                                      тогда что же тут некоторые личности заливали, что истинное ООП сделает ваш код идеальным? если даже такие вещи как инкапсуляция — это не свойство ООП, а свойство решения разработчика?
                                      • 0
                                        Это принцип ООП. Хочешь чтобы твой код был ООП — обеспечь инкапсуляцию. Средствами языка или ещё как — не суть. ООП-код можно писать хоть на ассемблере.
                              • +1
                                Кстати,
                                Some languages like Smalltalk and Ruby only allow access via object methods


                                en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29
                        • 0
                          sectus, я обдумал пнукт «е» еще раз. Пожалуй он не такой однозначный. Если в этом пункте подробно расписать, как метод не следует использовать — это в какой-то мере решает проблему.

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

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

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

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

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

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

                  Для примера можно еще привести автомобиль. Вот вы садитесь в него, у него двигатель, карбюратор, номерной знак и еще кучу всего. Но для вождения вам все это неважно. Важно, что есть интерфейс к нему — руль и педали. Так это видит ООП. А процедурное это видит, как коробку с деталями(двигателем и т.д.), лежащие в одном месте и коробку с управлением, в которую надо засунуть первую коробку, чтобы что-то с ней сделать.
                • +2
                  Простите, я не очень понял, как это относится к JavaBeans?
                  • 0
                    Это не к JavaBeans. Это к вашей реализации Person. А утверждение «на практике есть два типа объектов» — спорное, потому что верно не везде. Где-то это конечно так, но много где еще — нет. Есть объекты, которые каким-то образом что-то делают и для этого хранят внутри состояние. Возьмите StringBuffer — отличный класс, который призван конкатенировать строки потоко-безопасно. Что там внутри — без разницы, главное известен его контракт. Но при этом он несет в себе данные — все строки, что ему переданы, но форма хранения этих данных неизвестна.
                    • 0
                      Ну это как раз первый тип объектов — функциональный. Ну, или, если быть точным, то можно выделить 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, конечно же.
            • 0
              Смотрите на инкапсуляцию как «пользователь» класса, а не как его «создатель», возможно это внесет ясность

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

                А что будете делать, когда кто-то попытается присвоить некорректное значение в созданный Вами сеттер? Исключения кидать?
    • +4
      Псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо 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.
      • +1
        Совершенно с вами согласен.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                      ======

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                      Если класс уже ушел в продакшн, то уже ничего не поменяешь. А если и поменяешь — это будет другой класс, другой релиз.
                                      • 0
                                        Нет, инкапсуляция к этому не имеет отношения. 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# строго соответствуют обоим этим пунктам. Ограничение доступа есть? Есть, пользователь класса не может получить доступ к филду. Упрощение связывание данных с методами, работающими над этими данными? Тоже есть.

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

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

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

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

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

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

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

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

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

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

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

                                        Как это не поменяешь? Да сразу после выхода в продакшн изменения учащаются, по сравнению со стадией разработки. Стабильным остаётся только интерфейс, но реализация — пилится и точится под весьма динамичные требования, пока не будет достигнут приемлемый статус и не будут выловлены все баги и бутылочные горлышки на реальных задачах.
                              • 0
                                Извините, но я нихрена не понял. :) Почему нарушена инкапсуляция? Вы знаете что у персоны есть имя, так? Так. Но вы знаете как это имя вы получаете? Нет. Реализация скрыта? Да. В чем проблема?
                                • 0
                                  Ничего что я лезу? :)
                                • 0
                                  Проблема не в получении имени, а в его замене. Ну, вместо имени подаст пользователь в setName, скажем, xss-инъекцию, и все, у админа увели пароль :)
                                  • 0
                                    Ну так вот как раз и функция setName() позволит мне реализовать проверку. Если ее не будет, а будет торчать тупо Name, то тогда я проверить не смогу изменения. Вот и все.

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

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

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

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

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


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

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

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

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


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

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

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

                          Это — никак.
                          • 0
                            Какую надо реализацию, чтоб нарушило?
                            • 0
                              Она приведена в исходном комменте, который мы обсуждаем: habrahabr.ru/post/147927/#comment_4990497
                              • 0
                                Там не приведена реализация getName
                                • 0
                                  В самом начале же сказано:

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

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

                                    Name getName()
                                    {
                                         return name;
                                    }
                                    


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

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

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

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

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

                                              > Псевдоскрытие прямого доступа через пачку геттеров-сеттеров, которые делают тупо return this.foo и this.foo = foo, на самом деле, ничем не лучше
                                          • 0
                                            Вот вам три совершенно одинаковых подхода, .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. И ничего что ни один пример ничего больше не делает как возвращает значение или присваивает. Фишка в том, что если я захочу отследить момент изменения данных, то я легко это сделаю.
                                            • 0
                                              > И ничего что ни один пример ничего больше не делает как возвращает значение или присваивает.

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

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

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


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

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


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

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

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

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

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

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

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


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

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

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

                                        • 0
                                          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.
                                          • 0
                                            Ладно мужики, я спать, жена уже сны видит. :) В целом, статья зачетная потому что на поговорить разводит, но вредная. Еще начнут думать что ООП зло и не надо никому. А геттеры и сеттеры инкапсуляцию нарушают… Брррр… До сих пор глаза кровью наливаются.
                                        • 0
                                          > Через сеттер, это не напрямую.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                                      ======

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                                                          Да.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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