15

这是我的代码:

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 的所有成员函数。就像是:

soldier.gun.fire();

或者

soldier.getGun().load(15);

那么哪一个是更好的设计呢?将 gun 对象隐藏为私有成员并使用 getGun() 函数访问它。还是让它成为公共成员?或者我可以封装所有这些功能会使实现更加困难:

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

那么你认为哪一个是最好的呢?

4

9 回答 9

21

我会说你的第二个选择:

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

最初它的工作量更大,但随着系统变得越来越复杂,您可能会发现士兵在开枪前后会想做其他事情(也许检查他们是否有足够的弹药,然后在开火前尖叫“吸盘!” ,然后喃喃地说“那肯定很痛”,然后检查他们是否需要重新加载)。它还向 Soldier 类的用户隐藏了关于枪是如何精确开火的不必要的细节。

于 2010-04-06T14:56:55.760 回答
11

首先,如果您从课堂外访问,您将违反得墨忒耳法则。GunSoldier

我会考虑这样的方法:

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

这样你也可以实现你的拳头、刀、手榴弹、棒球棒、激光猫等。

于 2010-04-06T15:02:26.857 回答
7

得墨忒耳法则会说封装这些功能。

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

这样,如果你想在士兵和枪之间进行某种类型的交互,你就有了插入代码的空间。

编辑:从维基百科链接中找到相关文章: http ://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf 报童的例子非常非常类似于您发布的士兵示例。

于 2010-04-06T14:57:02.080 回答
5

事实上,这在很大程度上取决于您想要拥有多少控制权。

为了模拟现实世界,您甚至可能想要完全封装枪支对象,并且只需要一个士兵.attack() 方法。然后,士兵.attack() 方法将查看士兵是否携带枪支,枪支的状态如何,并根据需要开火或重新装弹。或者可能将枪扔向目标并逃跑,如果任何一次行动都没有足够的弹药......

于 2010-04-06T15:00:50.887 回答
3

如果你暴露 gun,你就允许 Gun 的成员函数之外的东西,这可能不是一个好主意:

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

如果使用 getGun(),调用看起来有点难看,但你可以在不修改 Soldier 的情况下向 Gun 添加函数。

如果你封装了函数(我推荐),你可以修改 Gun 或引入 Gun 的其他(派生)类,而无需更改与 Soldier 的接口。

于 2010-04-06T15:03:42.673 回答
2

通常我的决定是基于容器类(在本例中为 Soldier)的性质。它要么完全是 POD,要么不是。如果它不是 POD,我将所有数据成员设为私有并提供访问器方法。仅当类没有不变量时,该类才是 POD(即,外部参与者无法通过修改其成员来使其状态不一致)。你的士兵类在我看来更像一个非 POD,所以我会去访问器方法选项。它是否会返回 const 引用或常规引用是您自己的决定,具体取决于 fire() 和其他方法的行为(如果它们修改了 gun 的状态)。

顺便说一句,Bjarne Stroustrup 在他的网站上谈到了这个问题: http ://www.artima.com/intv/goldilocks3.html

旁注:我知道这不正是你问的,但我建议你也考虑在其他答案中提到的得墨忒耳定律:暴露动作方法(对枪起作用)而不是整个枪对象通过getter方法。既然士兵“有”枪(枪在他手里,他扣动扳机),其他演员“要求”士兵开火对我来说似乎更自然。我知道如果枪有很多方法可以采取行动,这可能会很乏味,但也许这些也可以归类为士兵暴露的更高级别的行动。

于 2010-04-06T15:02:50.397 回答
1

没有黄金法则适用于 100% 的时间。这实际上是一个判断电话,具体取决于您的需求。

这取决于您想要隐藏/禁止枪支访问 Solider 的功能。

如果您只想拥有对 Gun 的只读访问权限,则可以将 const 引用返回给您自己的成员。

如果您只想公开某些功能,您可以制作包装函数。如果您不希望用户尝试通过 Soldier 更改 Gun 设置,请制作包装函数。

不过,一般来说,我将 Gun 视为它自己的对象,如果您不介意公开 Gun 的所有功能,也不介意允许通过 Soldier 对象进行更改,只需将其公开即可。

您可能不想要枪支的副本,因此如果您创建 GetGun() 方法,请确保您没有返回枪支的副本。

如果你想让你的代码简单,那么让士兵负责处理枪支。您的其他代码是否需要直接与枪一起使用?还是士兵总是知道如何工作/重新装填自己的枪?

于 2010-04-06T14:53:00.207 回答
1

提供“getGun()”或简单的“gun()”。

想象一下,有一天您可能需要使该方法更复杂:

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

此外,您可能希望提供一个 const 访问器,以便人们可以在 const 士兵上使用 const gun:

const Gun &getGun() const { return gun_; }
于 2010-04-06T14:56:47.547 回答
0

封装函数以提供一致的 UI,即使您稍后更改逻辑。命名约定由你决定,但我通常不使用“getFoo()”,而只使用“foo()”作为访问器,“setFoo()”作为设置器。

  • 尽可能返回对 const 的引用(有效的 C++ 项目 #3)。
  • 更喜欢使用 consts、枚举和内联而不是使用硬编码的数字(Item #4)
  • 为您的私有成员提供独特的命名约定,以将它们与参数区分开来
  • 在有意义的地方使用无符号值将错误转移到编译时间
  • 当 const 值(如最大值)应用于整个类时。使它们成为静态的。
  • 如果您打算继承,请确保您的析构函数是虚拟的
  • 将所有成员初始化为正常的默认值

这就是这些课程之后的样子。键盘

#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;
}
于 2010-04-06T15:32:11.773 回答