减少构造函数的参数数量

Dav*_*ria 42 language-agnostic oop design-patterns

我正在阅读"清洁代码",并且无法弄清楚如何将我的一些功能(通常是构造函数)保持为最多3个参数.

通常我的对象需要大量的信息才能工作 - 我是否应该创建一个小构造函数,然后使用mutator函数为它们提供所有信息?这似乎没有比使用一个大的构造函数更好.

举个例子,我有一个"MovablePatch"类.它允许用户在窗口中拖动方块.它需要几个参数,包括Radius,Color,Renderer,InitialPosition和Visibility.目前我从我的GUI收集所有这些,然后调用:

MovablePatch(int radius, Renderer* renderer, Color color,  Position initial, bool visibility)
Run Code Online (Sandbox Code Playgroud)

这些只是我在这堂课中需要的一些东西.任何人都可以建议我如何打包这些信息传递给构造函数?我没有看到任何明显的"打破小班"在这里出现.

Ada*_*ruk 31

你可以有

MovablePatch(Renderer* renderer, CircleAppearance circleAppearance)
Run Code Online (Sandbox Code Playgroud)

CircleAppearance收集其他信息.

但是,干净的代码和其他关于优秀代码应该是什么样子的书籍,目标是80%的代码.您的代码似乎比典型的LoB(业务线)品种"更接近金属".因此,您可能会遇到某些编码理想不适用的地方.

最重要的部分是你正在考虑它并试图让事情变得干净整洁!:)


Mik*_*kis 29

不要把"你的建造者中不要超过3个参数"这样的格言视为面值.如果你有一点机会使一个对象不可变,那就去做吧; 如果它是不可变的意味着它将有一个包含50个参数的构造函数,那就这样吧; 去吧; 甚至不考虑它两次.

即使对象是可变的,仍然应该根据需要传递其构造函数,以便在构造时立即处于有效且有意义的状态.在我的书中,绝对不允许必须知道哪些是必须被调用的神奇的mutator方法(有时甚至以正确的顺序),然​​后才能调用任何其他方法,在段错误的惩罚下.

话虽如此,如果您真的想减少构造函数或任何函数的参数数量,只需将此方法传递给它可以调用的接口,以便从中获取所需的东西以便工作.

  • +1为此.不可变性比小参数列表更有益. (7认同)

Don*_*ner 21

你传递的一些东西可以被抽象成一个更大的构造.例如visibility,colorradius,可能是有意义的放入您定义的对象.然后,这个类的一个实例,称之为ColoredCircle,可以传递给构造函数MovablePatch.ColoredCircle并不关心它在哪里或者它正在使用什么渲染器,但是MovablePatch可以.

我的主要观点是,从OO的角度来看,radius它不是一个整数,它是一个半径.您希望避免使用这些长构造函数列表,因为理解这些内容的上下文是令人畏惧的.如果它们收集到一个更大的类,有点喜欢你已经有ColorPosition,您可以在通过更少的参数,使之更容易理解.

  • 还要记住,通过将`ColoredCircle`作为一个单独的结构,它可以有自己的构造函数来提供默认值,所以如果程序员实际上并不想指定它们不必要的所有东西,如果它没有成为一个大的混乱构造函数. (3认同)
  • 这不仅仅改变了问题吗?您还必须以某种方式初始化`ColoredCircle`类,并且仍然有四个参数... (2认同)
  • @MartinStettner,是的,你必须首先初始化`ColoredCircle`,但这应该是四个参数.这意味着`MovablePatch`只会采用`ColoredCircle`和`Renderer*`. (2认同)

Sam*_*ler 12

命名参数成语在这里很有用.在你的情况下,你可能有

class PatchBuilder
{
public:
    PatchBuilder() { }
    PatchBuilder& radius(int r) { _radius = r; return *this; }
    PatchBuilder& renderer(Renderer* r) { _renderer = r; return *this; }
    PatchBuilder& color(const Color& c) { _color = c; return *this; }
    PatchBuilder& initial(const Position& p) { _position = p; return *this; }
    PatchBuilder& visibility(bool v) { _visibility = v; return *this; }

private:
    friend class MovablePatch;
    int _radius;
    Renderer* _renderer;
    Color _color;
    Position _position;
    bool _visibility;
};

class MovablePatch
{
public:
    MovablePatch( const PatchBuilder& b ) :
        _radius( b._radius );
        _renderer( b._renderer );
        _color( b._color );
        _position( b._position );
        _visibility( b._visibility );
    {

    }

private:
    int _radius;
    Renderer* _renderer;
    Color _color;
    Position _position;
    bool _visibility;
};
Run Code Online (Sandbox Code Playgroud)

然后你就这样使用它

int
main()
{
    MovablePatch foo = PatchBuilder().
        radius( 1.3 ).
        renderer( asdf ).
        color( asdf ).
        position( asdf ).
        visibility( true )
     ;
}
Run Code Online (Sandbox Code Playgroud)

过度简化,但我认为它得到了重点.如果需要某些参数,它们可以包含在PatchBuilder构造函数中:

class PatchBuilder
{
public:
    PatchBuilder(const Foo& required) : _foo(required) { }
    ...
};
Run Code Online (Sandbox Code Playgroud)

显然,如果需要所有参数,这种模式会退化为原始问题,在这种情况下,命名参数习惯用法不适用.关键是,这不是一个适合所有解决方案,并且正如亚当在下面的评论中描述的那样,还有额外的成本和一些开销.

  • 这种新的流畅的建造者固定是危险的.您需要进行运行时检查以查看是否已填充所有内容.我不会推荐这个.不必要的复杂性将需要维持成本. (11认同)
  • 我不得不同意Adam Dymitruk.不仅没有办法确保在编译时所有值都被初始化(并且必要的复制和运行时检查可能会产生一些开销),但它也不是自我记录的方式,它不清楚哪些值需要设定.如果每个值都是不同的函数参数,那么查看函数定义将告诉我必须传递的内容.对于构建器,必须查看所有方法的定义(可能仍然不够,因为参数可能是可选的=>查看文档),使其更难使用. (3认同)
  • @Grizzly:有**是一种确保在编译时所有值都被初始化的方法.只是C++在表达类型方面不是最简洁或最易读的,所以几乎肯定不值得. (3认同)

Bec*_*ari 8

一个好的选择是使用Builder模式,其中每个"setter"方法返回自己的实例,您可以根据需要链接方法.

在您的情况下,您将获得一个新的MovablePatchBuilder类.

该方法非常有用,您可以在许多不同的框架和语言中找到它.

请参阅此处查看一些示例.