Члены Public Data против Геттеров, Сеттеров

В настоящее время я работаю в Qt и так C++. У меня есть классы, которые имеют частные члены данных и публичные функции-члены. У меня есть публичные методы получения и установки для членов данных, доступных в классе.

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

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

15 ответов

Решение

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

Личные данные являются частными, поэтому вы можете заменить реализацию в любое время (и можете выполнить полное перестроение, но это другая проблема). Как только вы выпустите джинна из бутылки, вы обнаружите, что его невозможно вставить обратно.

РЕДАКТИРОВАТЬ: После комментария я сделал на другой ответ.

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

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

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

Нет, это даже не одно и то же.

Существуют разные уровни защиты / скрытия реализации, которые могут быть достигнуты с помощью разных подходов к интерфейсу класса:


1. Публичный член данных:

  • обеспечивает как чтение, так и запись (если не постоянный) доступ к элементу данных
  • раскрывает тот факт, что объект данных физически существует и физически является членом этого класса (позволяет создавать указатели типа указатель на элемент на этот элемент данных)
  • обеспечивает lvalue доступ к элементу данных (позволяет создавать обычные указатели на элемент)


2. Метод, который возвращает ссылку на часть данных (возможно, на частный элемент данных):

  • обеспечивает как чтение, так и запись (если не постоянный) доступ к данным
  • раскрывает тот факт, что объект данных физически существует, но не показывает, что он физически является членом этого класса (не позволяет создавать указатели типа указатель на элемент на данные)
  • обеспечивает lvalue доступ к данным (позволяет создавать обычные указатели на них)


3. Методы получения и / или установки (возможно, доступ к частному элементу данных):

  • обеспечивает как чтение, так и / или запись в собственность
  • не раскрывает тот факт, что объект данных существует физически, не говоря уже о физическом представлении в этом классе (не позволяет создавать указатели типа указатель на член для этих данных или любые другие указатели по этому вопросу)
  • не предоставляет доступ к данным lvalue (не позволяет создавать обычные указатели на них)

Метод получения / установки даже не раскрывает тот факт, что свойство реализуется физическим объектом. Т.е. за парой получатель / установщик может отсутствовать физический элемент данных.

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

Конечно, есть варианты каждого подхода. Например, метод получения может вернуть константную ссылку на данные, что поместит их где-то между (2) и (3).

Если у вас есть геттеры и сеттеры для каждого из ваших элементов данных, нет смысла делать данные приватными. Вот почему иметь геттеры и сеттеры для каждого элемента данных - плохая идея. Рассмотрим класс std::string - он (вероятно) имеет ОДИН геттер, функцию size() и вообще не имеет сеттеров.

Или рассмотрим BankAccount объект - мы должны иметь SetBalance() сеттер, чтобы изменить текущий баланс? Нет, большинство банков не поблагодарит вас за такую ​​вещь. Вместо этого мы хотим что-то вроде ApplyTransaction( Transaction & tx ),

Сделайте данные общедоступными. В (довольно маловероятном) случае, когда вам когда-нибудь понадобится логика "getter" или "setter", вы можете изменить тип данных на прокси-класс, который перегружает operator= и / или operator T (где T= любой тип, который вы используете сейчас) для реализации необходимой логики.

Редактировать: идея о том, что управление доступом к данным представляет собой инкапсуляцию, в основном ложна. Инкапсуляция заключается в том, чтобы скрыть детали реализации (в общем случае!), Не контролируя доступ к данным.

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

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

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

Методы получения и установки позволяют вам применять логику к вводу / выводу от закрытых членов, тем самым контролируя доступ к данным (инкапсуляция для тех, кто знает их термины ОО).

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

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

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

Если вы абсолютно уверены, что ваша логика проста, и вам никогда не нужно делать что-то еще при чтении / записи переменной, лучше держать данные открытыми. В случае C++ я предпочитаю использовать struct вместо class, чтобы подчеркнуть тот факт, что данные являются публичными.

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

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

Помимо проблем инкапсуляции (которые являются достаточной причиной), очень легко установить точку останова, когда переменная установлена ​​/ доступна, когда у вас есть getters/setters.

Причины использования открытых полей, а не методов получения и установки, включают:

  1. Там нет незаконных ценностей.
  2. Ожидается, что клиент отредактирует его.
  3. Чтобы иметь возможность писать такие вещи, как object.XY = Z.
  4. Чтобы дать твердое обещание, что значение - это просто значение и с ним не связано никаких побочных эффектов (и не будет в будущем).

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

(Из десяти вопросов о программировании на основе стоимости.)

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

В качестве ссылки, прочитайте рекомендации C++ (C.131)

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

Кроме того (в интересах Нила), во время отладки иногда полезно иметь удобное место для зависания отладочных отпечатков и других действий, когда определенный элемент данных читается или записывается. С геттерами и сеттерами это легко. С публичными членами данных, это огромная боль в задней части.

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

Я предлагаю, чтобы у вас не было общедоступных членов данных (кроме структур POD). Я также не рекомендую иметь геттеры и сеттеры для всех ваших членов данных. Скорее, определите чистый публичный интерфейс для вашего класса. Это может включать методы, которые получают и / или устанавливают значения свойств, и эти свойства могут быть реализованы как переменные-члены. Но не делайте геттеры и сеттеры для всех ваших членов.

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

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

Учтите следующее:

double premium;
double tax;

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

double myPremium = class.premium;

Ваши характеристики только что изменились, и премиум с точки зрения пользователя должен быть premium + tax

Вы должны будете изменить везде, что это premium значение используется в вашем коде, и добавьте tax к этому.

Если вместо этого вы реализовали это так:

double premium;
double tax;

double GetPremium(){return premium;};

Весь ваш код будет использовать GetPremium() и ваш tax изменение будет одной строкой:

double premium;
double tax;

double GetPremium(){return premium + tax;};

Методы получения и установки существуют главным образом для того, чтобы мы могли контролировать, как члены выбираются и как они устанавливаются. Методы получения и установки не существуют только как способ доступа к конкретному члену, но для того, чтобы до того, как мы попытаемся установить элемент, что он, возможно, соответствует определенным условиям, или, если мы его получим, мы могли бы контролировать, что мы возвращаем копию этого члена. член в случае не примитивного типа. В целом, вы должны попытаться использовать g/s'ers, когда вы хотите определить, как должен взаимодействовать элемент данных, без них это может привести к тому, что этот элемент будет использоваться в режиме adhoc.

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

Другие вопросы по тегам