Объектно-ориентированное дизайнерское предложение - PullRequest
15 голосов
/ 06 апреля 2010

Вот мой код:

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;
}

Мне нужно вызвать все функции-члены Gun над объектом Soldier.Что-то вроде:

soldier.gun.fire();

или

soldier.getGun().load(15);

Так какой из них лучший дизайн?Скрытие объекта оружия как частного члена и доступ к нему с помощью функции getGun ().Или сделать его публичным членом?Или я могу заключить в капсулу все эти функции, что усложнит реализацию:

soldier.loadGun(15); // calls Gun.load()
soldier.fire(); // calls Gun.fire()

Так что, по вашему мнению, лучше?

Ответы [ 9 ]

21 голосов
/ 06 апреля 2010

Я бы сказал, выбери второй вариант:

soldier.loadGun(15); // calls Gun.load()
soldier.fire(); // calls Gun.fire()

Поначалу это более сложная работа, но по мере усложнения системы вы можете обнаружить, что солдат захочет делать другие вещи до и после стрельбы из своего оружия (возможно, проверьте, достаточно ли у него боеприпасов, а затем закричите «Умереть!» "перед стрельбой, и бормочите" это должно быть больно "после, и проверьте, нужно ли им перезагрузить). Он также скрывает от пользователей класса Солдат ненужные детали того, как именно стреляет пистолет.

11 голосов
/ 06 апреля 2010

Прежде всего, вы нарушите закон Деметры , получив доступ к Gun извне Soldier класса.

Я бы рассмотрел методы, подобные этим:

soldier.ArmWeapon(...);
soldier.Attack(...);

Таким образом, вы также можете реализовать свой кулак, нож, гранату, бейсбольную биту, лазерного кота и т. Д.

7 голосов
/ 06 апреля 2010

Закон Деметры сказал бы инкапсулировать функции.

http://en.wikipedia.org/wiki/Law_of_Demeter

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

Edit: нашел соответствующую статью по ссылке в Википедии: http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf Пример газетчика очень и очень похож на пример солдата, который вы публикуете.

5 голосов
/ 06 апреля 2010

Действительно, многое зависит от того, какой уровень контроля вы хотите иметь.

Чтобы смоделировать реальный мир, вы можете даже захотеть полностью инкапсулировать объект оружия и просто иметь soldier.attack ()метод.Затем метод soldier.attack () должен увидеть, несет ли солдат оружие, и в каком состоянии он находится, и запустить или перезарядить его при необходимости.Или, возможно, бросить пистолет в цель и убежать, если для какой-либо операции было недостаточно боеприпасов ...

3 голосов
/ 06 апреля 2010

Если вы выставляете оружие, вы разрешаете вещи, выходящие за рамки функций-членов Gun, что, вероятно, не очень хорошая идея:

soldier.gun = anotherGun; // where did you drop your old gun?

Если вы используете getGun (), вызовы выглядят немного уродливо, но вы можете добавлять функции в Gun без модификации Soldier.

Если вы инкапсулируете функции (которые я рекомендую), вы можете модифицировать Gun или вводить другие (производные) классы Gun, не изменяя интерфейс на Soldier.

2 голосов
/ 06 апреля 2010

Обычно мое решение основано на характере класса контейнера (в данном случае, Солдат).Либо это полностью POD, либо нет.Если это не POD, я делаю все элементы данных приватными и предоставляю методы доступа.Класс является POD, только если у него нет инвариантов (т. Е. Нет никакого способа, которым внешний субъект может сделать свое состояние несогласованным, изменяя его члены).Ваш класс солдат выглядит для меня как не POD, поэтому я бы выбрал опцию метода доступа.Если он вернет константную ссылку или регулярную ссылку, это ваше собственное решение, основанное на поведении огня () и других методов (если они изменяют состояние оружия).

Кстати, Бьярн Страуструп говоритНемного об этой проблеме на его сайте: http://www.artima.com/intv/goldilocks3.html

Заметка: я знаю, что это не совсем то, что вы спросили, но я бы посоветовал вам также рассмотреть многие упоминания, сделанные в других ответах на закон Деметры: раскрыть методы действия (которые воздействуют на оружие) вместо всего объекта оружия через метод получения.Поскольку у солдата "есть" пистолет (он находится в его руке, и он нажимает на курок), для меня более естественно, что другие актеры "просят" солдата стрелять.Я знаю, что это может быть утомительно, если у оружия есть много способов действовать, но, возможно, они также могут быть сгруппированы в действия более высокого уровня, которые выставляет солдат.

1 голос
/ 06 апреля 2010

Предоставьте getGun () или просто gun ().

Представьте себе, что однажды вам может понадобиться усложнить этот метод:

Gun* getGun() {
  if (!out_of_bullets_) {
    return &gun_;
  } else {
    PullPieceFromAnkle();
    return &secret_gun_;
  }
}

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

const Gun &getGun() const { return gun_; }
1 голос
/ 06 апреля 2010

Нет золотого правила, которое применяется 100% времени. Это действительно суждение в зависимости от ваших потребностей.

Это зависит от того, какую функциональность вы хотите скрыть / запретить оружию от доступа к Solider.

Если вы хотите иметь доступ только к ружью только для чтения, вы можете вернуть постоянную ссылку своему члену.

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

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

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

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

0 голосов
/ 06 апреля 2010

Инкапсулируйте функции, чтобы обеспечить согласованный интерфейс, даже если вы позже измените логику. Соглашения об именах зависят от вас, но я обычно не использую «getFoo ()», а просто «foo ()» в качестве метода доступа и «setFoo ()» в качестве средства установки.

  • возвращать ссылку на const, когда вы можете (Эффективный элемент C ++ №3).
  • Предпочитает использовать константы, перечисления и строки в жестко закодированных числах (элемент № 4)
  • предоставляет уникальные соглашения об именах для ваших частных членов, чтобы отличать их от аргументов
  • Используйте значения без знака, если они имеют смысл перемещать ошибки для компиляции времени
  • Когда постоянные значения, например максимумы, применяются ко всему классу. Сделайте их статичными.
  • Если вы планируете наследовать, убедитесь, что ваши деструкторы виртуальные
  • инициализировать всех членов в здравом уме по умолчанию

Вот так классы выглядят после этого. 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;
}
...