将函数调用转发给成员是不好的做法吗?

Is forwarding function calls to a member bad practice?

本文关键字:转发 函数调用 成员      更新时间:2023-10-16

在我最近编写的代码中,我被迫直接访问一个对象的成员来调用它的函数,然而,我觉得这样做是错误的,因为它似乎违反了封装和得墨忒耳定律。然而,我能想到的唯一好的替代方法是在类中为我可能想要调用的成员的每个函数编写自己的函数,这将非常繁琐和冗余。例子:

class Object
{
    public:
        void setNum(int x)
        {
            num = x;
        }
    private:
        int num;
};
class Object2
{
    public:
        Object obj;
};
int main()
{
    Object2 obj2;
    obj2.obj.setNum(5);
}

class Object
{
    public:
        void setNum(int x)
        {
            num = x;
        }
    private:
        int num;
};
class Object2
{
    public:
        void setNum(int x)
        {
            obj.setNum(x);
        }
    private:
        Object obj;
};
int main()
{
    Object2 obj2;
    obj2.setNum(5);
}

Object2setNum的呼叫会被转发到Object中相同的函数。这样的设计被认为是糟糕的实践吗?直接访问obj会更好吗?

我也可以让Object2继承Object,但在这种情况下,我将继承的类不是设计为基类,将向Object2暴露受保护的成员,并且似乎不适合开始,因为它不是is-a关系,组合将是首选。

我的具体情况是:我正在使用SFML制作一款游戏,有一个类Ship,当然需要一个精灵来代表它在世界中。任何时候我想设置或获得船的位置,旋转等。我必须直接访问它的精灵或在Ship中编写冗余转发功能。问题是,做这两件事中的任何一件看起来都像是一种代码气味:要么违反封装和得墨忒耳定律,要么编写冗余代码。

这里的最佳实践是什么?我是不是过于挑剔写简单的转发函数?或者在这种情况下直接访问Ship的精灵真的没有错吗?

这个问题:c++向前方法调用嵌入对象没有继承实际上正是我想问的,然而,两个答案都没有给出一个好的解决方案。一种不被容忍,显然有很差的封装,另一种简单地使用getter,如果有的话,似乎是安慰剂,没有人解决我的主要问题,那就是什么是最优雅和可接受的解决方案?

最佳解决方案高度依赖于封装的底层语义。您需要尽可能地解耦您的代码。我将在示例中描述它:

  1. 你有一个Ship类,它有一个精灵。你可能想要分离游戏逻辑和渲染。所以Ships只知道它有一个精灵对象来处理渲染。在这种情况下,你把责任分开了,这很好。所以简单的getter是一个很好的解决方案。
  2. 但是如果绝对坐标和旋转必须存储在精灵中,那么事情就会发生变化:游戏逻辑通常需要这两个元素,所以它们必须在Ship和sprite中保持一致。实现这一目标的最佳方法是让Ship的setPosition和setRotation方法也能够设置精灵的位置和旋转。这样你就简化了与Ship一起工作的代码,但却牺牲了Ship的复杂性。考虑到飞船可以从多个地方被操纵,这是值得的。注意:你可能仍然希望通过getter来呈现Sprite。你可能想要阻止除了Ship之外的任何人设置精灵的位置和旋转,如果这不会使你的代码膨胀太多的话。
  3. 让我们想象Ship类主要用于渲染。在这种情况下,你可能想要隐藏外部类,你使用图形精灵(因为如果你改变渲染引擎,它将是很好的,如果你不需要重写任何东西,除了渲染代码)。在这种情况下,你会想通过Ship代理所有的setPosition和setRotation调用来隐藏精灵的存在。

在这些情况下都不使用继承,因为继承意味着子对象是它的祖先的变体。你可以说战舰是Ship的变体。但Ship并不是Sprite的变体,它们太不同了,意味着不同的东西。

所以:如果封装的类太具体,不应该在外部可见,或者必须与主对象一致地操作,那么编写一堆代理方法是一个很好的解决方案。否则,这些方法只会使代码膨胀,最好提供一种获取嵌套对象的方法。但在这种情况下,我选择getter方法而不是公共属性

不管经典的java oop学派怎么想,永远不要忘记DRY (Don't Repeat Yourself)也被认为是一个很好的实践。

虽然OOP只是c++可以支持的众多编程范例之一,但自从第一个汇编器获得宏以来,DRY就是所有编程的本质,而且早在OOP发明者远离他们自己父母的想法和愿望之前,这一点就已经成立了。

尽管我的不公平的经历是……如果尊重良好的oop实践迫使您编写重复代码的无用样板,则意味着

  • 您正在使用的语言基本上是破碎为您想要实现的目的,不支持范式正确或…
  • OOP是破碎为您试图达到的目的。而在c++中,OOP很可能是真正的坏范式。

对于您的具体问题,委托(这就是该模式的通常命名方式)在以下情况下是有意义的:

  • 委派的方式可以更改或
  • 委派是隐藏成员接口的一部分。

在这种情况下,您有一个函数,该函数调用从固定成员可访问的固定方法。

是只针对这个特定的示例,还是在您的实现中总是这样设计的?

如果是这样,委派不增加语义值,如果不只是减少a.b.m()a.m()。如果你写的超过……假设三个"什么都不做,只是转发"的函数你是在浪费时间。

如果b有50个方法,而你把它设为私有,只委托其中的5个,那么它是完全有意义的。