Должен ли я использовать общедоступные или частные переменные?

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

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

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


person Marek Čačko    schedule 18.01.2013    source источник
comment
Ответ OO будет заключаться в использовании частных переменных. Но модель видимости в C++ довольно несовершенна (чуть меньше в C++11), и частные члены могут вызвать очень неожиданные проблемы, если вы не дадите им искаженные имена.   -  person Marc Glisse    schedule 18.01.2013
comment
@MarcGlisse Что?   -  person Collin    schedule 18.01.2013
comment
Почти точный обман: stackoverflow.com/questions/1596432/   -  person Jerry Coffin    schedule 18.01.2013
comment
До C++11 наличие перегрузки, использующей T::x в своей сигнатуре, вызывало серьезную ошибку, если вы вызывали другую перегрузку для типа с закрытым членом x. Но даже в C++11 вы все еще получаете такие проблемы, как: gcc.gnu. org/bugzilla/show_bug.cgi?id=55713, где вместо того, чтобы игнорировать частные члены (или базы), компилятор настаивает на выдаче ошибки. Конечно, есть примеры и похуже. Было слышно, как несколько членов комитета называли контроль доступа C++ неработоспособным, хотя я думаю, что это могло быть по разным причинам.   -  person Marc Glisse    schedule 18.01.2013


Ответы (7)


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

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

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

Насколько это возможно, запретите геттеры/сеттеры и сделайте свойства private. Это следует принципу сокрытия информации - вам не нужно заботиться о том, какие свойства имеет класс. Он должен быть автономным. Конечно, на практике это неосуществимо, а если и возможно, то дизайн, следующий этому, будет более загроможденным и трудным в обслуживании, чем тот, который этого не делает.

Это, конечно, эмпирическое правило - например, я бы просто использовал struct (эквивалентно class с публичным доступом), скажем, для простого класса точек:

struct Point2D
{
   double x;
   double y;
};
person Luchian Grigore    schedule 18.01.2013
comment
Я бы сказал, что сеттер/геттер создает дополнительную точку доступа, помимо прямого доступа к переменной. - person sth; 18.01.2013
comment
@sth, но прямой доступ ограничен классом, так что это что-то. (если это то, что вы имели в виду, то да) - person Luchian Grigore; 18.01.2013
comment
+1 за Максимально банить геттеры/сеттеры. Я всегда заявляю, что сеттеры — это зло (я имею в виду функции. Я не против собак). - person Angew is no longer proud of SO; 18.01.2013
comment
@LuchianGrigore: все равно доступ предоставляется публике в любом случае, поэтому фактически вы создали только два способа использования переменной внутри класса ... Я действительно не думаю, что ограничение одного из методов доступа к классу помогает, если вы в свою очередь пересоздайте способ получить все равно доступ извне все равно. - person sth; 18.01.2013
comment
Вы также должны упомянуть, что private члены с public сеттерами должны быть предпочтительнее, чем public члены, потому что это единственный способ контролировать, как изменяется член. Это может быть (например) полезно для поддержки восходящей совместимости. Представьте, что новая версия вашего Point2D должна обрабатывать только значения от -100,0 до +100,0. Не существует (простого) способа реализовать это с помощью приведенной выше структуры без изменения кода клиентов, использующих эту структуру. - person Carsten; 18.01.2013
comment
@Aschratt, это уже не Point2D, не так ли? Это RestrictedPoint2D. :) - person Luchian Grigore; 18.01.2013
comment
@LuchianGrigore Подразумевает подкласс Point2D, который затем попытается изменить интерфейс для Point2D. Вы никогда не знаете, как ваши классы будут использоваться в дикой природе, использование геттеров/сеттеров дает возможность реализовать новую функциональность, не нарушая интерфейс. - person Chris; 18.01.2013
comment
@ Крис, это была шутка. Это компромисс — никогда нельзя быть уверенным, что ничего не изменится. - person Luchian Grigore; 18.01.2013
comment
Я думаю, что с геттерами все в порядке, если эта информация имеет смысл быть видимой. И я думаю, что с сеттерами все в порядке, пока они обеспечивают соблюдение контракта, подразумеваемого интерфейсом. Будучи закрытым полем, оно автоматически не является деталью реализации. Мы должны быть осторожны, давая людям черно-белые ответы. Я думаю, что понятно, что общий стиль Java для геттеров и сеттеров для всего, вероятно, плох. - person Tim Seguine; 30.09.2013

Поскольку вы говорите, что знаете теорию, а другие ответы копались в значении общедоступных/частных, геттеров и сеттеров, я хотел бы сосредоточиться на том, почему использовать аксессоры вместо создания общедоступных атрибутов (данные-члены в С++) .

Представьте, что у вас есть класс Truck в логистическом проекте:

class Truck {
public:
    double capacity;

    // lots of more things...
};

Если вы из Северной Америки, вы, вероятно, будете использовать галлоны для представления вместимости ваших грузовиков. Представьте, что ваш проект закончен, он отлично работает, хотя и сделано много прямых применений Truck::capacity. На самом деле, ваш проект становится успешным, поэтому какая-то европейская фирма просит вас адаптировать ваш проект к ним; к сожалению, проект теперь должен использовать метрическую систему, поэтому для емкости следует использовать литры вместо галлонов.

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

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

С подходом, показанным выше, это будет означать много работы, вам придется отслеживать все случаи использования Truck::capacity и решать, что с ними делать. Это, вероятно, будет означать изменение файлов по всей кодовой базе. Предположим, в качестве альтернативы, что вы выбрали более theoretic подход.

class Truck {
public:
    double getCapacity() const
        { return capacity; }

    // lots of more things...
private:
    double capacity;
};

Возможное альтернативное изменение не требует модификации интерфейса класса:

class Truck {
public:
    double getCapacity() const
        { if ( Configuration::Measure == Gallons ) {
            return capacity;
          } else {
             return ( capacity * 3.78 );
          }
        }


    // lots of more things...
private:
    double capacity;
};

(Пожалуйста, примите во внимание, что есть много способов сделать это, это только один вариант, и это только пример)

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

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

Надеюсь это поможет.

person Baltasarq    schedule 18.01.2013
comment
Я знаю, что это игрушечный пример, и в основном согласен с выводами, но, пожалуйста, если вы работаете над проектом, включающим несколько модулей, не делайте этого. Вместо этого храните все внутри, используя единицы СИ, и преобразуйте единицы измерения только при взаимодействии с пользователем (на уровне представления, если вы делаете что-то удаленно в стиле MVC). - person ; 24.05.2017

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

Это зависит от роли вашего класса и того, что он предлагает.

  • Все методы и члены, составляющие внутреннюю работу класса, должны быть сделаны приватными.
  • Все, что класс предлагает внешнему миру, должно быть общедоступным.
  • Члены и методы, которые могут быть расширены в специализации этого класса, могут быть объявлены как защищенные.
person HAL    schedule 18.01.2013

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

С другой стороны, с точки зрения C++, это также может быть недостатком, если класс делает много неожиданных вещей, когда вы просто хотите получить/установить значение. Людям нравится знать, приводит ли какой-либо доступ к огромным накладным расходам или является ли он простым и эффективным. Когда вы получаете доступ к общедоступной переменной, вы точно знаете, что получаете, когда вы используете геттер/сеттер, вы понятия не имеете.

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

Код C++ обычно не использует геттеры/сеттеры, когда они не обеспечивают реальной выгоды. Если вы разрабатываете проект из 1 000 000 строк с большим количеством модулей, которые должны быть как можно более независимыми, это может иметь смысл, но для большинства кода нормального размера, который вы пишете изо дня в день, они излишни.

person sth    schedule 18.01.2013

Есть некоторые типы данных, единственной целью которых является хранение точно определенных данных. Обычно они могут быть записаны в виде структур с открытыми элементами данных. Кроме того, класс должен определять абстракцию. Публичные переменные или тривиальные сеттеры и геттеры предполагают, что дизайн не был достаточно продуман, что приводит к скоплению слабых абстракций, которые почти ничего не абстрагируют. Вместо того чтобы думать о данных, подумайте о поведении: этот класс должен выполнять X, Y и Z. Исходя из этого, решите, какие внутренние данные необходимы для поддержки желаемого поведения. Поначалу это нелегко, но постоянно напоминайте себе, что важно поведение, а не данные.

person Pete Becker    schedule 18.01.2013

Общедоступные переменные обычно не рекомендуются, и лучшая форма — сделать все переменные приватными и обращаться к ним с помощью геттеров и сеттеров:

private int var;

public int getVar() {
  return var;
}

public void setVar(int _var) {
  var = _var;
}

Современные IDE, такие как Eclipse и другие, помогают вам в этом, предоставляя такие функции, как «Реализовать геттеры и сеттеры» и «Инкапсулировать поле» (которые заменяют все прямые обращения к переменным соответствующими вызовами геттеров и сеттеров).

person Stefan Seidel    schedule 18.01.2013
comment
Почему лучше сделать переменные приватными и предоставить геттеры и сеттеры? Вызов этого процесса Encapsulate Field вводит в заблуждение; это просто замена одного синтаксиса для доступа более подробным синтаксисом для доступа. (Обратите внимание, вопрос помечен как C++.) - person CB Bailey; 18.01.2013
comment
геттеры и сеттеры предлагают ложное ощущение инкапсуляции. - person Luchian Grigore; 18.01.2013

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

Еще одним преимуществом геттеров и сеттеров является то, что если вы используете IDE (например, Eclipse или Netbeans), вы можете использовать функциональные возможности IDE для поиска каждого места в кодовой базе, где вызывается функция. Они обеспечивают видимость того, где часть данных в этом конкретном классе используется или изменяется. Кроме того, вы можете легко сделать доступ к переменным-членам потокобезопасным, используя внутренний мьютекс. Функции получения/установки будут захватывать этот мьютекс перед доступом к переменной или ее изменением.

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

person codemaster91    schedule 18.01.2013