Популярны ли функции get и set у программистов на C ++? - PullRequest
32 голосов
/ 10 апреля 2009

Изначально я из мира C # и изучаю C ++. Мне было интересно узнать о функциях get и set в C ++. В C # они довольно популярны, а такие инструменты, как Visual Studio, способствуют их использованию, делая их очень простыми и быстрыми в реализации. Однако в мире C ++ это не так.

Вот код C # 2.0:

public class Foo
{
    private string bar;

    public string Bar
    {
        get { return bar; }
        set { bar = value; }
    }
}

Или в C # 3.0:

public class Foo { get; set; }

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

Теперь, поскольку я так привык к этому, я чувствую, что должен перенести эту привычку на свой код C ++, но действительно ли это необходимо? Я не вижу, как это делается так часто, как с C #.

В любом случае, вот C ++ из того, что я собираю:

class Foo
{
public:
    std::string GetBar() const; // Thanks for the tip Earwicker.
    void SetBar(std::string bar);
private:
    std::string bar;
}

const std::string Foo::GetBar()
{
    return bar;
}

void Foo::SetBar(std::string bar)
{
    // Also, I always wonder if using 'this->' is good practice.
    this->bar = bar;
}

Теперь, для меня это похоже на большую работу ног; Учитывая использование инструментов Visual Studio, реализация C # заняла бы буквально секунды, а ввод C ++ занял у меня намного больше времени - я чувствую, что это не стоит усилий, особенно когда альтернатива имеет 5 строк:

class Foo
{
public:
    std::string Bar;
}

Из того, что я понял, вот преимущества:

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

И недостатки:

  • Печатает целую вечность, действительно ли это стоит усилий? Вообще говоря. В некоторых случаях преимущества оправдывают свои усилия, но я имею в виду, говоря словами «хорошая практика», правда?

Ответ:

Почему я выбрал ответ с меньшим количеством голосов ? На самом деле я был очень близок к выбору ответа veefu ; однако мое личное мнение (которое, по-видимому, является спорным) заключается в том, что ответ на вопрос о вареном пудинге.

Ответ, который я выбрал, с другой стороны, кажется, спорит обе стороны; Я думаю, что геттеры и сеттеры являются злом, если используются чрезмерно (я имею в виду, когда это не нужно и нарушит бизнес-модель), но почему бы нам не иметь функцию с именем GetBalance()?

Конечно, это будет гораздо более универсальным, чем PrintBalance(); Что, если я хотел показать это пользователю иначе, чем того хотел класс? Теперь, в некотором смысле, GetBalance() может быть недостаточно релевантным, чтобы утверждать, что «геттеры и сеттеры хороши», потому что у него нет (или, возможно, не должно ) иметь сопровождающий сеттер, и говорить о что, функция с именем SetBalance(float f) может быть плохой (на мой взгляд), потому что это подразумевает, что для реализации функции необходимо манипулировать учетной записью вне класса, что не очень хорошая вещь.

Ответы [ 14 ]

34 голосов
/ 10 апреля 2009

Я бы сказал, что предоставление доступа более важно в C ++, чем в C #.

C ++ не имеет встроенной поддержки свойств. В C # вы можете изменить открытое поле на свойство в основном без изменения кода пользователя. В C ++ это сложнее .

Для меньшего набора текста вы можете реализовать тривиальные методы установки / получения как встроенные методы:

class Foo
{
public:
    const std::string& bar() const { return _bar; } 
    void bar(const std::string& bar) { _bar = bar; } 
private:
    std::string _bar;
};

И не забывайте, что добытчики и сеттеры несколько злы.

33 голосов
/ 10 апреля 2009

Рискуя спорить, я вернусь к противоположной точке зрения, с которой я впервые столкнулся при чтении "Holub on Patterns". Это была очень сложная точка зрения, но после размышления она имела смысл:

Геттеры и сеттеры злые

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

Представьте, что ваше поле 'std :: string Foo :: bar' должно измениться с std :: string на другой класс строки, который, скажем, лучше оптимизирован или поддерживает другой набор символов. Вам нужно изменить поле личных данных, метод получения, метод установки и весь клиентский код этого класса, который вызывает эти методы получения и установки.

Вместо того, чтобы проектировать свои классы для «предоставления данных» и «получения данных», проектируйте их для «выполнения операций» или «предоставления услуг». Спросите себя, почему вы пишете функцию «GetBar». Что вы делаете с этими данными? Возможно, вы отображаете эти данные или выполняете их обработку. Этот процесс лучше раскрыть как метод Foo?

Это не значит, что геттеры и сеттеры не имеют своей цели. В C # я считаю, что фундаментальная причина их использования - это взаимодействие с IDE-дизайном Visual Studio GUI, но если вы пишете их на C ++, вероятно, лучше сделать шаг назад, посмотреть на свой дизайн и посмотреть, есть ли что-то. отсутствует.

Я попытаюсь смоделировать пример, чтобы проиллюстрировать.

// A class that represents a user's bank account
class Account {
  private:
    int balance_; // in cents, lets say 
  public:
    const int& GetBalance() { return balance_; }
    void SetBalance(int b) { balance_ = b; }
};

class Deposit {
  private:
    int ammount_;
  public:
    const int& GetAmount() { return ammount_; }
    void SetAmmount(int a) { _balance = a; }
};

void DoStuffWithAccount () {
  Account a;
  // print account balance
  int balance = a.GetBalance();
  std::cout << balance;

  // deposit some money into account
  Deposit d(10000);
  a.SetBalance( a.GetBalance() + d.GetValue());
}

Это не займет много времени, чтобы увидеть, что это очень плохо разработано.

  1. Целые числа - ужасный тип данных валюты
  2. Депозит должен быть функцией Счета

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

Итак, давайте пропустим этот код и посмотрим, что мы можем улучшить

// A class that represents a user's bank account
class Account {
  private:
    float balance_;
  public:
    void Deposit(float b) { balance_ += b; }
    void Withdraw(float w) { balance_ -= w; }
    void DisplayDeposit(std::ostream &o) { o << balance_; }
};

void DoStuffWithAccount () {
  Account a;
  // print account balance
  a.DisplayBalance(std::cout);

  // deposit some money into account
  float depositAmt = 1000.00;
  a.Deposit(depositAmt);
  a.DisplayBalance(std::cout);
}

«Поплавок» - это шаг в правильном направлении. Конечно, вы могли бы изменить внутренний тип на «float» и при этом поддерживать идиому getter / setter:

class Account {
  private:
    // int balance_; // old implementation
    float balance_; 
  public:
    // support the old interface
    const int& GetBalance() { return (int) balance_; }
    void SetBalance(int b) { balance_ = b; }
    // provide a new interface for the float type
    const float& GetBalance() { return balance_; } // not legal! how to expose getter for float as well as int??
    void SetBalance(float b) { balance_ = b; }
};

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

Класс, подобный Учетной записи, вероятно, не лучший пример, так как «получение» баланса учетной записи является естественной операцией для Учетной записи. Однако в целом вы должны быть осторожны с геттерами и сеттерами. Не приучайте писать геттеры и сеттеры для каждого члена данных. Это довольно легко разоблачить и заблокировать себя в реализации, если вы не будете осторожны.

11 голосов
/ 10 апреля 2009

В вашем примере:

class Foo
{
public:
    const std::string GetBar(); // Should this be const, not sure?

Вы, вероятно, имеете в виду это:

std::string GetBar() const;

Помещение const в конце означает «Эта функция не изменяет экземпляр Foo, к которому она вызывается», поэтому она помечает его как чистый метод получения.

Чистые методы получения часто встречаются в C ++. Примером std::ostringstream является функция str(). Стандартная библиотека часто следует шаблону использования одного и того же имени функции для пары функций получения / установки - str снова является примером.

Что касается того, стоит ли печатать слишком много и стоит ли это того - это кажется странным вопросом! Если вам нужно предоставить клиентам доступ к некоторой информации, предоставьте получатель. Если нет, то не надо.

6 голосов
/ 10 апреля 2009

На самом деле нет строгого соглашения, как в C # или Java. Многие программисты на C ++ просто сделали бы переменную общедоступной, чтобы избавить себя от проблем.

Как уже говорили другие ответы, вам не нужно часто устанавливать и, в некоторой степени, получать методы.

Но если и когда вы делаете их, нет необходимости печатать больше, чем необходимо:

class Foo
{
public:
    std::string Bar() const { return bar; }
    void Bar(const std::string& bar) { this->bar = bar; }
private:
    std::string bar;
};

Объявление встроенных функций в классе экономит набор текста и подсказывает компилятору, что функции должны быть встроенными. И это не намного больше печатания, чем эквиваленты C #. Стоит отметить, что я удалил префиксы get / set. Вместо этого у нас просто есть две перегрузки Bar (). Это довольно часто встречается в C ++ (в конце концов, если он не принимает никаких аргументов, мы знаем, что это получатель, а если он принимает аргумент, это установщик. Нам не нужно имя, чтобы сказать нам это) экономит немного больше печатать.

6 голосов
/ 10 апреля 2009

[править] Кажется, мне нужно подчеркнуть, что сеттеры должны проверять параметры и применять инварианты, поэтому они обычно не так просты, как здесь. [/ Править]


Не со всеми, потому что для лишнего набора текста. Теперь я использую их гораздо чаще, когда Visual Assist дает мне «инкапсулированное поле».

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

Некоторые заметки:

константность: Да, геттер должен быть постоянным. Однако возвращать значение const бесполезно, если вы возвращаете значение. Для потенциально сложных возвращаемых значений вы можете использовать const & Хотя:

std::string const & GetBar() const { return bar; } 

Цепочка сеттера: Многие разработчики любят модифицировать сеттер так:

Foo & SetBar(std::string const & bar) { this->bar = bar; return *this; }

Что позволяет вызывать несколько сеттеров как таковые:

Foo foo;
foo.SetBar("Hello").SetBaz("world!");

Хотя это не всегда хорошо воспринимается как хорошая вещь.

__ declspec (свойство) : Visual C ++ предоставляет это нестандартное расширение, чтобы вызывающие абоненты могли снова использовать синтаксис свойства. Это немного увеличивает нагрузку на класс, но делает код вызывающего абонента более дружелюбным.


Итак, в заключение, немного больше работы, но несколько решений, которые нужно принять в C ++. Типичный;)

4 голосов
/ 10 апреля 2009

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

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

#define GETTER(T,member) const T& Get##member() const { return member; }
#define SETTER(T,member) void Set##member(const T & value) { member = value; }

class Foo
{
public:
    GETTER(std::string, bar)
    SETTER(std::string, bar)
private:
    std::string bar;
}
3 голосов
/ 11 апреля 2009

Аргументы против Get / Set с точки зрения дизайна API в примере с банковскими операциями очевидны. Не открывайте поля или свойства, если они позволят пользователям нарушать ваши бизнес-правила.

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

Автоматические свойства в c # очень просты в использовании, и существует множество сценариев (привязка данных, сериализация и т. Д.), Которые не работают с полями, но требуют свойств.

3 голосов
/ 10 апреля 2009

Получение и настройка элементов данных как членов данных: Плохой .
Получение и настройка элементов абстракции: Хорошо .

1 голос
/ 17 марта 2015

Методы Get и Set полезны, если у вас есть ограничения в значении переменной. Например, во многих математических моделях существует ограничение на сохранение определенной переменной с плавающей точкой в ​​диапазоне [0,1]. В этом случае, Get и Set (специально Set) могут сыграть приятную роль:

class Foo{
public:
    float bar() const { return _bar; } 
    void bar(const float& new_bar) { _bar = ((new_bar <= 1) && (new_bar >= 0))?new_bar:_bar; } // Keeps inside [0,1]
private:
    float _bar;     // must be in range [0,1]
};

Кроме того, некоторые свойства должны быть пересчитаны перед чтением. В этих случаях может потребоваться много ненужного компьютерного времени для пересчета каждого цикла. Таким образом, способ оптимизировать его - это пересчитать только при чтении. Для этого перегрузите метод Get, чтобы обновить переменную перед ее чтением.

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

1 голос
/ 10 апреля 2009

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

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

Когда вы контролируете обе стороны интерфейса, определение интерфейса не кажется такой большой проблемой. Однако, когда вы хотите изменить детали реализации, и это вызывает перекомпиляцию клиентского кода, как это часто бывает в C ++, вы хотите максимально уменьшить это. Таким образом, pImpl и get / set будут больше использоваться в публичных API, чтобы избежать такого ущерба.

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