Корректный дизайн ООП без методов считывания?

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

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

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

10
задан kane77 1 April 2010 в 10:19
поделиться

9 ответов

Аллен Холуб произвел фурор своим сообщением «Почему методы получения и установки зло » еще в 2003 году.

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

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

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

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

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

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

11
ответ дан 3 December 2019 в 16:29
поделиться

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

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

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

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

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

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

int getSomeField();

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

1
ответ дан 3 December 2019 в 16:29
поделиться

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

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

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

1
ответ дан 3 December 2019 в 16:29
поделиться

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

Люди Python этого не делают. Тем не менее, они все еще занимаются объектно-ориентированным программированием. Ясно, что привередливые геттеры и сеттеры не важны.

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

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

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

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

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

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

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

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

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

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

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

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

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

4
ответ дан 3 December 2019 в 16:29
поделиться

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

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

1
ответ дан 3 December 2019 в 16:29
поделиться

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

0
ответ дан 3 December 2019 в 16:29
поделиться

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

4
ответ дан 3 December 2019 в 16:29
поделиться

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

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

2
ответ дан 3 December 2019 в 16:29
поделиться

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

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

1
ответ дан 3 December 2019 в 16:29
поделиться