В отношении вашего описания и закона Деметры. Используя пример из Википедии, говорят, что он предпочитает:
void exercise(const Dog& d) { return d.walk(); }
вместо:
void exercise(const Dog& d) { return d.legs.move(); }
Потому что собака должна знать, как ходить, а не двигать ногами. Применив это к вашей программе D & D, мы получили бы что-то вроде:
bool hit_orc_with_long_sword(const Character& c, int DCValue) {
return c.D20Abilities.Strength() + d20.roll() > DCValue;
}
Предпочитают:
skill_check (c, MeleeAttack (LongSword (), Orc ());
bool skill_check(const Character& c, const Action& a) {
return c.attempt(a);
}
//.... in class character:
bool Character::attempt(const Action& a)
{
return a.check_against(D20Abilities);
}
//.... in class Action:
bool Action::check_against(const D20Abilities& a) {
return GetRelevantStat(a) + d20.roll() > DCValue;
}
Таким образом, вместо доступа через группы свойств, Персонажи знают, как выполнять Действия, а Действия знают, какова вероятность их успеха с определенной статистикой. Это разъединяет интерфейс. Код потребителя (например, описанный выше hit_orc_with_long_sword) не должен «сообщать» персонажу, как проверить его силу, он обрабатывается взаимодействием классов, каждый из которых обрабатывает четко определенный домен.
Однако приведенный пример кода выглядит так, как будто имеет несколько проблем. Когда вы используете друга для выполнения чего-либо, кроме реализации определенных операторов, я обнаружил, что это указывает на проблему проектирования. Вы не должны разбрасывать внутреннее состояние от одного объекта к другому; объекты должны знать, как выполнять набор действий из своего внутреннего состояния и аргументов, передаваемых им в качестве функций.