Правильный дизайн ООП без геттеров?

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

но как это сделать правильно? Например, отображение / рендеринг объекта, который является классом "данные" - скажем, человек, у которого есть имя и дата рождения. Должен ли класс иметь метод для отображения объекта, где некоторый визуализатор будет передан в качестве аргумента? Разве это не нарушает принцип, что класс должен иметь только одну цель (в данном случае состояние хранилища), поэтому он не должен заботиться о представлении этого объекта.

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

9 ответов


Ален Голуб сделал большой всплеск с "почему методы getter и setter являются злом" еще в 2003 году.

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

но возьмите Мистера Голуба с солью.

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

посмотрите на C#: они фактически добавили синтаксический сахар в язык, чтобы упростить операции get/set. Либо это подтверждает чье-то мнение о Microsoft как империи зла, либо противоречит заявлению г-на Голуба.

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

крайняя точка зрения не практично.


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

люди Python этого не делают. Тем не менее, они все еще занимаются программированием OO. Ясно, что суетливые геттеры и сеттеры не важны.

они распространены из-за ограничений в C++ и Java. Но они не кажутся существенными.

Python люди используют properties иногда для создания функций геттера и сеттера, которые выглядят как простой атрибут.

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

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

в C++ (и Java), где вы не удается увидеть источник, может быть трудно понять интерфейс, поэтому вам нужно много подсказок. частные методы, явные геттеры и сеттеры, и т. д.

в Python, где вы можете видеть весь источник, тривиально понять интерфейс. Нам не нужно давать так много подсказок. Как мы говорим: "используй источник, люк"и "мы все здесь взрослые."Мы все можем видеть источник, нам не нужно быть суетливыми о нагромождении геттеров и сеттеров, чтобы предоставить еще больше подсказок о том, как API завод.

например, отображение / рендеринг объекта, который является классом "данные" - скажем, человек, который имеет имя и дату рождения. Должен ли класс иметь метод для отображения объекта, где некоторый визуализатор будет передан в качестве аргумента?

хорошая идея.

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

вот почему рендер отдельно. Ваш дизайн довольно приятный.

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

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

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


Если у вас есть геттеры и сеттеры, у тебя нет заключения. И они не нужны. Рассмотрим класс std:: string. Это довольно сложное внутреннее представление, но не имеет геттеров или сеттеров, и только один элемент представления (вероятно) подвергается просто возвращению его значения (т. е. размера()). Это то, к чему вы должны стремиться.


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

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


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

назначение частных членов в классе

IMHO он содержит много того, что по праву можно назвать "лучшими практиками ООП".

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

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

пока все хорошо. Однако я не согласен с тем, что предоставление такого геттера

int getSomeField();

по своей сути компрометирует ваш дизайн класса. Что ж, это так,если вы не разработали свой интерфейс класса хорошо. Тогда, конечно, может случиться так, что вы слишком поздно поймете, что поле должно быть long, а не int, и изменение его нарушило бы 1000 мест в клиентском коде. IMHO в таком случае дизайнер должен виноват, а не бедный добытчик.


в некоторых языках, таких как C++, есть понятие friend. Используя эту концепцию, вы можете сделать детали реализации класса видимыми только для подмножества других классов (или даже функций). Когда вы используете Get/Set без разбора, вы даете всем доступ ко всему.

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


Предположим, у вас есть много классов сущностей в ваших проектах, и предположим, что у них есть базовый класс, такой как Data. Добавление различных методов getter и setter для конкретных реализаций загрязнит клиентский код, который использует эти сущности, такие как множество dynamic_casts, для вызова необходимых методов getter и setter.

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

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


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

http://www.mockobjects.com/files/endotesting.pdf


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