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

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

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

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

Ответ 1

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

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

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

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

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

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

Крайний вид нецелесообразен.

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

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

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

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

int getSomeField();

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

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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