Подтвердить что ты не робот

Использование getter/setter против "tell, not ask"?

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


Представьте себе класс Warrior с атрибутами health и armor:

class Warrior {
    unsigned int m_health;
    unsigned int m_armor;
};

Теперь кто-то атакует моего воина специальной атакой, которая уменьшает его броню на 5 секунд. С помощью setter это будет так:

void Attacker::attack(Warrior *target)
{
    target->setHealth(target->getHealth() - m_damage);
    target->setArmor(target->getArmor() - 20);
    // wait 5 seconds
    target->setArmor(target->getArmor() + 20);
}

И скажите, не спрашивайте, как это будет выглядеть (поправьте меня, если я ошибаюсь):

void Attacker::attack(Warrior *target)
{
    target->hurt(m_damage);
    target->reduceArmor(20);
    // wait 5 seconds
    target->increaseArmor(20);
}

Теперь второй, очевидно, выглядит лучше, но я не могу найти реальных преимуществ этого. Вам по-прежнему требуется такое же количество методов (increase/decrease vs set/get), и вы теряете выгоду от запроса, если вам когда-нибудь понадобится спросить. Например, как бы вы установили здоровье воинов до 100? Как вы выясните, следует ли использовать heal или hurt, и сколько здоровья вам нужно для лечения или лечения?

Кроме того, я вижу, что сеттеры и геттеры используются некоторыми из лучших программистов в мире. Большинство API-интерфейсов используют его, и он все время используется в std lib:

for (i = 0; i < vector.size(); i++) {
    my_func(i);
}
// vs.
vector.execForElements(my_func);

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

Почему я должен использовать его (должен ли я?), когда все становится проще с помощью getters и seters?

4b9b3361

Ответ 1

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

Вы ошибаетесь. Дело в том, чтобы заменить getVariable и setVariable на значимую операцию: inflictDamage, например. Замена getVariable на increaseVariable просто дает вам разные более неясные имена для getter и setter.

Где это имеет значение. Например, вам не нужно предоставлять сеттер/приемник для отслеживания брони и здоровья по-разному, один inflictDamage может обрабатываться классом, пытаясь заблокировать (и повредить экран в процессе), а затем нанести урон на персонаже, если экран недостаточен или ваш алгоритм требует этого. В то же время вы можете добавить более сложную логику в одном месте.

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

Устраните игру и добавьте огневое оружие, тогда вы можете иметь inflictFireDamage (или передать урон от огня в качестве другого аргумента функции inflictDamage). Warrior может выяснить, зависит ли она от заклинания огнестойкости и игнорировать урон от огня, вместо того, чтобы все другие объекты в программе пытались выяснить, как их действие повлияет на остальных.

Ответ 2

Ну, если это так, зачем вообще беспокоиться с геттерами и сеттерами? У вас могут быть только открытые поля.

void Attacker::attack(Warrior *target)
{
    target->health -= m_damage;
    target->armor -= 20;
    // wait 5 seconds
    target->armor += 20;
}

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

Цитата:

Самая большая опасность здесь заключается в том, что, запрашивая данные от объекта, вы получают только данные. Вы не получаете объект - не в большом смысл. Даже если вещь, полученная вами от запроса, является объектом структурно (например, String), это уже не объект семантически. Он больше не связан с объектом своего владельца. Да просто так вы получили строку, содержимое которой было "RED", вы не можете задать строку что это значит. Это фамилия владельца? Цвет автомобиля? текущее состояние тахометра? Объект знает эти вещи, данных нет.

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

target->setHealth(target->getArmor() - m_damage);

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

Кроме того, вы получили это неправильно в std lib здесь. Getters и seters используются только в std::complex и что из-за отсутствия языка (у С++ не было ссылок тогда). Напротив, наоборот. Стандартная библиотека С++ поощряет использование алгоритмов, чтобы сказать, что делать в контейнерах.

std::for_each(begin(v), end(v), my_func);
std::copy(begin(v), end(v), begin(u));

Ответ 3

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

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

Использование этой формы дает вам гораздо больший контроль над интерфейсом Warrior: вы можете определить операции, которые разрешены, и вы можете изменить их реализацию, не переписывая весь код, который их вызывает.

Ответ 4

На мой взгляд, оба кода делают то же самое. Разница заключается в выразительности каждого из них. Первый (seters anad getters) может быть более выразительным, чем второй (скажите, не спрашивайте).

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

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

На самом деле, геттеры и сеттеры преобладают, но обычно, чтобы понять идею сказать, не просите вместе с ним. Другими словами, некоторые API имеют getters и seters, а также методы tell, не спрашивают идею.