Вот мой код:
class Soldier {
public:
Soldier(const string &name, const Gun &gun);
string getName();
private:
Gun gun;
string name;
};
class Gun {
public:
void fire();
void load(int bullets);
int getBullets();
private:
int bullets;
}
Я должен назвать все функции членства Оружия по объекту Солдата. Что-то как:
soldier.gun.fire();
или
soldier.getGun().load(15);
Таким образом, какой является лучшим дизайном? Сокрытие оружия возражает как член парламента, не занимающий официального поста и доступ это с getGun () функция. Или создание его общедоступный участник? Или я могу инкапсулировать все эти функции, сделал бы реализацию тяжелее:
soldier.loadGun(15); // calls Gun.load()
soldier.fire(); // calls Gun.fire()
Таким образом, то, какой, Вы думаете, является лучшим?
Я бы посоветовал выбрать второй вариант:
soldier.loadGun(15); // calls Gun.load()
soldier.fire(); // calls Gun.fire()
Изначально это больше работы, но по мере того, как система становится более сложной, вы можете обнаружить, что солдат захочет делать другие вещи до и после стрельбы из оружия (возможно, проверьте если у них достаточно боеприпасов, а затем кричите «Смерть присоски !!» перед выстрелом и бормочите «это должно быть больно» после, и проверьте, нужна ли им перезарядка). Он также скрывает от пользователей класса Солдат ненужные подробности того, как именно стреляют из пистолета.
Во-первых, вы нарушите Закон Деметры , получив доступ к Gun
извне Класс солдат
.
Вместо этого я бы рассмотрел такие методы:
soldier.ArmWeapon(...);
soldier.Attack(...);
Таким образом, вы также могли бы использовать свой кулак, нож, гранату, бейсбольную биту, лазерного кота и т. Д.
Инкапсулируйте функции, чтобы обеспечить согласованный пользовательский интерфейс, даже если позже вы измените логику. Соглашение об именах зависит от вас, но обычно я не использую «getFoo ()», а просто «foo ()» в качестве средств доступа и «setFoo ()» в качестве установщиков.
Вот как выглядят классы после этого. CodePad
#include <iostream>
#include <string>
#include <stdint.h>
using namespace std;
class Gun
{
public:
Gun() : _bullets(0) {}
virtual ~Gun() {}
void fire() {cout << "bang bang" << endl; _bullets--;}
void load(const uint16_t bullets) {_bullets = bullets;}
const int bullets() const {return _bullets;}
static const uint16_t MAX_BULLETS = 17;
protected:
int _bullets;
};
class Soldier
{
public:
Soldier(const string &name, const Gun &gun) : _name(name), _gun(gun) {}
virtual ~Soldier() {}
const string& name() const;
Gun& gun() {return _gun;}
protected:
string _name;
Gun _gun;
};
int main (int argc, char const *argv[])
{
Gun gun; // initialize
string name("Foo");
Soldier soldier(name, gun);
soldier.gun().load(Gun::MAX_BULLETS);
for(size_t i = 0; i < Gun::MAX_BULLETS; ++i)
{
soldier.gun().fire();
cout << "I have " << soldier.gun().bullets() << " left!" << endl;
}
return 0;
}
В самом деле, это во многом зависит от того, какой контроль вы хотите иметь.
Чтобы смоделировать реальный мир, вы можете даже полностью инкапсулировать огнестрельный объект и просто использовать метод soldier.attack (). Затем метод soldier.attack () будет видеть, есть ли у солдата оружие и в каком состоянии находится оружие, и при необходимости стреляет или перезаряжает его. Или, возможно, бросить оружие в цель и убежать, если боеприпасов недостаточно для любой операции ...
Если вы раскрываете gun, вы позволяете вещи за пределами функций-членов Gun, что, вероятно, не очень хорошая идея:
soldier.gun = anotherGun; // where did you drop your old gun?
Если вы используете getGun(), вызовы выглядят немного уродливо, но вы можете добавить функции к Gun без изменения Soldier.
Если вы инкапсулируете функции (что я рекомендую), вы можете модифицировать Gun или вводить другие (производные) классы Gun без изменения интерфейса Soldier.
Предоставьте "getGun ()" или просто "gun ()".
Представьте, что однажды вам может понадобиться сделать этот метод более сложным:
Gun* getGun() {
if (!out_of_bullets_) {
return &gun_;
} else {
PullPieceFromAnkle();
return &secret_gun_;
}
}
Кроме того, вы можете захотеть предоставить константный метод доступа, чтобы люди могли использовать константную пушку на константном солдате:
const Gun &getGun() const { return gun_; }
Обычно мое решение основывается на природе класса контейнера (в данном случае, Soldier). Либо он полностью является POD, либо нет. Если это не POD, я делаю все члены данных приватными и предоставляю методы-доступники. Класс является POD, только если у него нет инвариантов (т.е. внешний агент не может сделать его состояние непоследовательным, изменяя его члены). Ваш класс солдата больше похож на не-POD, поэтому я бы выбрал вариант с методом доступа. Будет ли он возвращать const-ссылку или обычную ссылку - это ваше собственное решение, основанное на поведении fire() и других методов (изменяют ли они состояние оружия или нет).
BTW, Bjarne Stroustrup немного рассказывает об этом вопросе на своем сайте: http://www.artima.com/intv/goldilocks3.html
В качестве примечания: я знаю, что это не совсем то, о чем вы спрашивали, но я бы посоветовал вам также рассмотреть многочисленные упоминания, сделанные в других ответах на закон Деметры: раскрывать методы действия (которые действуют на оружие) вместо всего объекта оружия через метод getter. Поскольку у солдата "есть" оружие (оно в его руке, и он нажимает на курок), мне кажется более естественным, что другие действующие лица "просят" солдата выстрелить. Я знаю, что это может быть утомительно, если у оружия много методов, но, возможно, их можно сгруппировать в более высокоуровневые действия, которые солдат раскрывает.
Не существует золотого правила, которое применялось бы в 100% случаев. Это действительно суждение, зависящее от ваших потребностей.
Это зависит от того, сколько функций вы хотите скрыть / запретить для оружия от доступа к Solider.
Если вы хотите иметь доступ только для чтения к Gun, вы можете вернуть константную ссылку на свой собственный член.
Если вы хотите предоставить только определенные функции, вы можете создать функции-оболочки. Если вы не хотите, чтобы пользователь пытался изменить настройки оружия через Soldier, сделайте функции оболочки.
В общем, я рассматриваю Gun как собственный объект, и если вы не против раскрыть всю функциональность Gun и не возражаете, чтобы что-то было изменено с помощью объекта Soldier, просто сделайте его общедоступным.
Вам, вероятно, не нужна копия оружия, поэтому, если вы создаете метод GetGun (), убедитесь, что вы не возвращаете копию оружия.
Если вы хотите, чтобы ваш код был простым, поручите солдату иметь дело с оружием. Должен ли ваш другой код работать напрямую с оружием? Или солдат всегда умеет работать / перезаряжать свое собственное ружье?
Согласно закону Деметры, функции заключаются в инкапсуляцию.
http://en.wikipedia.org/wiki/Law_of_Demeter
Таким образом, если вам нужно какое-то взаимодействие между солдатом и оружием, у вас есть место для вставки кода.
Изменить: нашел соответствующую статью по ссылке в Википедии: http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter. pdf Пример с газетчиком очень, очень похож на пример с солдатом, который вы публикуете.