【问题标题】:Reducing the number of arguments to a constructor减少构造函数的参数数量
【发布时间】:2012-01-30 03:00:44
【问题描述】:

我正在阅读“清洁代码”并且无法弄清楚如何将我的一些函数(通常是构造函数)保持在 3 个参数的最大值。

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

例如,我有一个“MovablePatch”类。它允许用户在窗口中拖动一个正方形。它需要几个参数,包括 Radius、Color、Renderer、InitialPosition 和 Visibility。目前我从我的 GUI 中收集所有这些,然后调用:

MovablePatch(int radius, Renderer* renderer, Color color,  Position initial, bool visibility)

这些只是我在这堂课中需要的一些东西。谁能建议我如何打包这些信息以传递给构造函数?我没有看到任何明显的“将其分成更小的类”出现在这里。

【问题讨论】:

  • 查找构建器模式。
  • 您必须遵循该指南吗?否则,我几乎会说在不明显如何应用它的地方忽略它。海事组织,如果遵循准则的修改不直观,那么重构代码以确保无论如何都遵循准则通常是不值得的。对于不直观的解决方案,很容易导致更复杂(因此容易出错)的代码甚至没有更好的可读性(一个例子是上面提到的小型构造函数,然后是 mutator 函数调用或构建器模式,这两者都可以很容易地忘记设置一些值)。
  • 使用简单的struct 将相关属性作为一个参数传递,从我出生之前就已经完成了,我很确定。

标签: oop design-patterns language-agnostic


【解决方案1】:

你可以有

MovablePatch(Renderer* renderer, CircleAppearance circleAppearance)

CircleAppearance 收集其他信息的位置。

但是,简洁的代码和其他概括介绍好的代码应该是什么样子的书籍的目标是 80% 的代码。 您的代码似乎比典型的 LoB(业务线)代码“更接近金属”。因此,您可能会遇到某些编码理念不适用的地方。

最重要的部分是您正在考虑它并努力保持事物整洁! :)

【讨论】:

    【解决方案2】:

    Named Parameter Idiom 在这里很有用。在你的情况下,你可能有

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

    然后你就这样使用它

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

    过于简化,但我认为它明白了这一点。如果需要某些参数,可以将它们包含在 PatchBuilder 构造函数中:

    class PatchBuilder
    {
    public:
        PatchBuilder(const Foo& required) : _foo(required) { }
        ...
    };
    

    显然,如果所有参数都需要,则此模式会退化为原始问题,在这种情况下,命名参数习语不适用。关键是,这不是一个万能的解决方案,正如亚当在下面的评论中所描述的那样,这样做会产生额外的成本和一些开销。

    【讨论】:

    • 我冒昧地将return *this 语句添加到PatchBuilder 的方法中;) ...
    • 这个新的 fluent builder 固定是危险的。您需要进行运行时检查以查看是否已填充所有内容。我不会推荐这个。不必要的复杂性,维护成本很高。
    • @AdamDymitruk 我已经根据您的建议用一些措辞更新了答案。
    • 我必须同意 Adam Dymitruk 的观点。不仅无法确保在编译时初始化所有值(并且必要的复制和运行时检查可能会产生一些开销),而且它也不是自我记录的方式,因为它没有明确哪些值需要设置。如果每个值都是不同的函数参数,那么查看函数定义会告诉我必须传递什么。对于构建者来说,必须查看所有方法的定义(可能还不够,因为参数可能是可选的=>查看文档),这使得它更难使用。
    • @Grizzly:一种方法可以确保在编译时初始化所有值。只是 C++ 在表达类型方面并不是最简洁或最易读的,所以几乎可以肯定它不值得。
    【解决方案3】:

    你传入的一些东西可以被抽象成一个更大的结构。例如,visibilitycolorradius 可以放入您定义的对象中。然后,可以将这个类的一个实例,称为ColoredCircle,传递给MovablePatch 的构造函数。 ColoredCircle 不关心它在哪里或使用什么渲染器,但 MovablePatch 关心。

    我的主要观点是,从面向对象的角度来看,radius 并不是真正的整数,而是半径。你想避免这些长的构造函数列表,因为理解这些东西的上下文是令人生畏的。如果你将它们收集到一个更大的类中,就像你已经拥有的 ColorPosition 一样,你可以传入更少的参数并使其更容易理解。

    【讨论】:

    • 这不只会转移问题吗?您还必须以某种方式初始化ColoredCircle 类,并且还剩下四个参数...
    • @MartinStettner,是的,您必须先初始化 ColoredCircle,但这应该是其中的四个参数。这意味着MovablePatch 只需要ColoredCircleRenderer*
    • 应该将Position 存储在补丁中还是圈子中?我认为补丁有位置,而不是圆圈。
    • 这至少将渲染器与圆的特征区分开来。那挺好的。如果需要,您可以稍后组织圈子属性。
    • 还要记住,通过将ColoredCircle 作为一个单独的结构,它可以有自己的构造函数来提供默认值,所以如果程序员实际上不想指定他们不需要的所有内容不会让 ctor 变得一团糟。
    【解决方案4】:

    不要把“你的构造函数中的参数不能超过 3 个”这样的格言从表面上看。如果你有一点点机会让一个对象不可变,那就让它吧;如果它是不可变的,则意味着它将有一个带有 50 个参数的构造函数,就这样吧;大胆试试吧;甚至不要考虑两次。

    即使对象将是可变的,您仍然应该向其构造函数传递尽可能多的参数,以便在构造时立即处于有效且有意义的状态。在我的书中,绝对不允许在调用任何其他方法之前必须知道哪些是必须调用的魔法 mutator 方法(有时甚至以正确的顺序),否则会受到段错误的惩罚。

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

    【讨论】:

    • 为此 +1。不变性比小参数列表更有益。
    【解决方案5】:

    一个不错的选择是使用 Builder 模式,其中每个“setter”方法都返回自己的实例,您可以根据需要链接这些方法。

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

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

    请参阅here 以查看一些示例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-16
      • 2019-07-28
      • 2020-04-10
      • 2020-05-14
      • 1970-01-01
      • 2017-03-14
      相关资源
      最近更新 更多