Соглашения для методов доступа (getters и seters) в С++

Несколько вопросов о методах доступа на С++ были заданы на SO, но никто не смог удовлетворить мое любопытство по этой проблеме.

Я стараюсь избегать аксессуаров, когда это возможно, потому что, подобно Stroustrup и другим известным программистам, я считаю класс со многими из них признаком плохого OO. В С++ я могу в большинстве случаев добавить дополнительную ответственность к классу или использовать ключевое слово friend, чтобы избежать их. Однако в некоторых случаях вам действительно нужен доступ к определенным членам класса.

Существует несколько возможностей:

1. Не используйте аксессоры вообще

Мы можем просто сделать соответствующие переменные-члены общедоступными. Это не работает в Java, но, похоже, все в порядке с сообществом С++. Тем не менее, я немного обеспокоен случаем, когда явная копия или ссылка только на чтение (const) на объект, который должен быть возвращен, преувеличен?

2. Использовать методы get/set в стиле Java

Я не уверен, что это вообще из Java, но я имею в виду это:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

3. Используйте целевые методы get/set для C-стиля

Это немного странно, но, видимо, все чаще:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

Чтобы это сработало, вам нужно будет найти другое имя для вашей переменной-члена. Некоторые люди добавляют символ подчеркивания, другие - "m_". Мне тоже не нравится.

Какой стиль вы используете и почему?

Ответ 1

С моей точки зрения, сидя с 4 миллионами строк кода С++ (и только одного проекта) с точки зрения обслуживания, я бы сказал:

  • Хорошо, чтобы не использовать геттеры/сеттеры, если члены неизменяемы (т.е. const) или просто без зависимостей (например, класс точек с элементами X и Y).

  • Если член private, то это также нормально пропустить геттеры/сеттеры. Я также считаю члены внутреннего pimpl -classes как private, если блок .cpp невелик.

  • Если член public или protected (protected так же плох, как public) и не const, не просто или имеет зависимости, то использовать геттеры/сеттеры.

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

Я предпочитаю стиль альтернативы 2. как тот более доступный для поиска (ключевой компонент в написании поддерживаемого кода).

Ответ 2

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

    Конечно, на самом деле встроенные явные getters или сеттеры создают так же основную зависимость от реализации класса. Это просто уменьшает семантическую зависимость. Вам все равно придется перекомпилировать все, если вы измените их.

  • Это мой стиль по умолчанию, когда я использую методы доступа.

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

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

Ответ 3

2) является лучшей ИМО, потому что это делает ваши намерения более ясными. set_amount(10) более значим, чем amount(10), а как хороший побочный эффект позволяет член с именем amount.

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

Ответ 4

  • Это хороший стиль, если мы хотим просто представить данные pure.

  • Мне это не нравится:), потому что get_/set_ действительно не нужно, когда мы можем перегрузить их на С++.

  • STL использует этот стиль, например std::streamString::str и std::ios_base::flags, за исключением случаев, когда его следует избегать! когда? Когда имя метода конфликтует с другим именем типа, используется стиль get_/set_, например std::string::get_allocator из-за std::allocator.

Ответ 5

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

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

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

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

с. Это четко документирует такой экстерьер доступа через понятный интерфейс класса

Основная идея:

a) Перепроектируйте, если возможно, еще,

б) Рефактор такой, что

  • Доступ к состоянию класса осуществляется через хорошо известный индивидуалистический Интерфейс

  • Должна быть возможность сконфигурировать какие-то действия и не делать к каждому из таких интерфейсов, например. все доступ от внешнего объекта GOOD должен быть разрешен, доступ из внешняя сущность BAD должна быть запрещен и внешний объект ОК должен быть разрешен, но не установлен (например)

Ответ 6

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

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

Ответ 7

Дополнительная возможность может быть:

int& amount();

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

str.length() = 5; // Ok string is a very bad example :)

Иногда это может быть только хороший выбор:

image(point) = 255;  

Еще одна возможность снова использовать функциональную нотацию для изменения объекта.

edit::change_amount(obj, val)

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

Ответ 8

Позвольте мне рассказать вам об одной дополнительной возможности, которая кажется наиболее освященной.

Необходимо прочитать и изменить

Просто объявите эту переменную public:

class Worker {
public:
    int wage = 5000;
}

worker.wage = 8000;
cout << worker.wage << endl;

Нужно просто прочитать

class Worker {
    int _wage = 5000;
public:
    inline int wage() {
        return _wage;
    }
}

worker.wage = 8000; // error !!
cout << worker.wage() << endl;

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

Ответ 9

Я видел идеализацию классов вместо целых типов для обозначения значимых данных.

Что-то вроде этого ниже, как правило, не использует свойства С++:

struct particle {
    float mass;
    float acceleration;
    float velocity;
} p;

Почему? Поскольку результат p.mass * p.acceleration является поплавком, а не силой, как ожидалось.

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

struct amount
{
    int value;

    amount() : value( 0 ) {}
    amount( int value0 ) : value( value0 ) {}
    operator int()& { return value; }
    operator int()const& { return value; }
    amount& operator = ( int const newvalue )
    {
        value = newvalue;
        return *this;
    }
};

Вы можете получить доступ к значению в объеме неявно оператором int. Кроме того:

struct wage
{
    amount balance;

    operator amount()& { return balance; }
    operator amount()const& { return balance; }
    wage& operator = ( amount const&  newbalance )
    {
        balance = newbalance;
        return *this;
    }
};

Использование Getter/Setter:

void wage_test()
{
    wage worker;
    (amount&)worker = 100; // if you like this, can remove = operator
    worker = amount(105);  // an alternative if the first one is too weird
    int value = (amount)worker; // getting amount is more clear
}

Это другой подход, это не значит, что это хорошо или плохо, но другое.