当要访问的对象被多次封装时,如何正确使用setter?

How to use setters properly when the object to be accessed is encapsulated more than once?

我经常被这个问题困扰,找不到任何明确的解决方案。我想我知道 getters/setters.

的动机

先验信息:

在实现现实生活中的数据时,通常会将数据封装在不止一层。例如:

// 1st stage data types ------------------------------
struct Cartesian
{
    int32_t x;
    int32_t y;
    int32_t z;
}

struct GeoLocation
{
    double_t latitude;
    double_t longitude;
    int32_t altitude;
}

// 2nd stage data types ------------------------------
struct Drone
{
    Cartesian baseOffset; // m
    Cartesian velocity; // m/s
}

struct Plane
{
    GeoLocation location; // semicircle
    Cartesian velocity; // knots
}

// 3rd stage data types ------------------------------
struct Swarm
{
    Plane base;
    Drone member[10];
}

在 C++ 中,我使用 classes 而不是结构(因为为什么不呢?)。当通过某些通信渠道接收到有关 Swarm[3].member[8].velocity.x 的数据时,问题就出现了。意识到一个系统中可以有多个群体。

要求:

根据 MISRA C++ 规则,函数不能 return 对任何 class 成员的非常量引用,因为如果没有 class' [=62=,则不应更改成员].

问题:

当我使用 getter 和 setter 时,我不能说“Swarm[3].member[8].velocity.x”;相反,我可以用几种方式来表达这一点:

1. 这不允许作为 get() 函数 returns const 引用并且不能调用 set()。

Swarm[3].getMember(8).getVelocity().setX(5); (or set("X", 5))

2. 这个方法把所有的负担都带进了 Swarm class。虽然对于调用 Swarm 的人来说,代码似乎更短 class,但如果发生变化,代码和后台维护工作非常繁重。

Swarm[3].setMemberVelocity(8,X,5)

3.这种方法有点介于两者之间,但这里的问题是你可能会牺牲效率,因为每次新数据首先到达你创建一个临时变量,得到它, 填充并设置它。

Cartesian tempVelocity = Swarm[3].getMember(8).getVelocity();

tempVelocity.x = 5;

Swarm[3].setMemberVelocity(8, tempVelocity);

这 3 种方法中哪一种最好?或者有什么我可以使用的替代品吗?

提前致谢。

如果它成批到达(如所有群的块),我将 methods/members 将块委托给子对象。 假设您刚刚收到 string update。您为第一个群拆分数据并调用 swarm[0].update(chunk)。该方法将进行自己的验证,然后将块拆分为每个成员的信息,并使用较小的部分对成员调用更新。最终你会一直到 Cartesian 这将能够更新 X 和其他一切。

我可能不完全了解 MISRA。但是,它似乎对灵活的设计(而且根本不是 c++ish)适得其反。假设我发现我需要:

struct SuperSwarm {
  Swarm swarms[10];
};

然后按照您的选项 2,我需要为所有内部聚合实现大量 setters/getters,因为在您的情况下听起来有点像您需要能够单独设置所有数据。如果您需要更改 Drone 中的某些内容,则会发生同样的事情,那么所有内容都需要更新。对于良好且灵活的设计,您可以看到这真的是一场噩梦。遵守您制定的规则并没有真正好的选择。这就是为什么您通常 return 非常量引用并在不需要特殊处理时使用它的原因。在某些方面,这可以让您的代码面向未来,而不仅仅是一个 public 成员变量。

您可以 'cheat'(并使用您的选项 3)并且在设计方面仍然灵活的一种方法是代理所有内容 - 例如 std::shared_ptr。当你 return a std::shared_ptr 时,它看起来像是一个副本而且它确实是 - 但它实际上只是同一个指向对象的代理(这类似于其他一些东西在引擎盖下的工作方式OO 编程语言)。不过,您可能需要更合适的代理 class。但又何必呢?为什么不直接使用结构并说它是一个 数据结构 而不是 class 承担(另一个)责任。毕竟那样的话,你的意图就表达的很清楚了。

显然需要简单的旧数据、标准布局和简单可复制的数据。这种数据使得它的二进制布局更容易实现,它是可移植的并且与其他编程语言兼容。 MISRA 要求使用关键字 struct.

声明此类数据

作为副作用,此类数据还可以编写如下代码:

swarms[swarmNo].drones[droneNo].velocity.x = 5;

那种深链导航仍然被认为是代码味道,它也有名字"train wreck pattern"。然而,它至少没有任何不需要的 getset() 膨胀来输入。

其余所有数据都应遵循面向对象的原则。蜂群本身有责任通过指挥其无人机进行机动,而无人机本身有责任遵循(或拒绝)此类命令。没有什么应该能够简单地从外部设置一些 属性 的东西。

MISRA 要求使用关键字 class 声明此类数据。 public 成员函数不应返回非静态数据成员的私有和非 const 句柄。