代码之家  ›  专栏  ›  技术社区  ›  Aquatic

如何使用重载构造函数重构类

  •  3
  • Aquatic  · 技术社区  · 16 年前

    我有一个带有重载构造函数(C)的类,它可以用几种方式初始化,并且一些参数是可选的-因此结果是-有一堆令人困惑的构造函数

    new Object(StrA, StrB, ObjA)
    new Object(StrA, StgB, ObjB, StrC)
    new Object(StrA, StrB, ObjA, StrD)
    new Object(StrA, StrB, ObjB, StrC, StrD)
    new Object(StrA, StrB, StrE, ObjA)
    new Object(StrA, StrB, StrE, ObjB)
    new Object(StrA, StrB, StrE, ObjA, StrC)
    new Object(StrA, StrB, StrE, ObjB, StrC, StrD)
    

    我看到了两种改善情况的方法 a)创建一个结构以保存可选参数

    new Config(StrA, StrB, StrD, StrE) 
    new Object(Config, ObjA)
    new Object(Config, ObjB, StrC)
    

    b)将可选参数设置为属性

    A = new Object(ObjA)
    A.StrA = some;
    A.StrB = some;
    A.StrD = some;
    A.StrE = some;
    

    哪条路最好?

    这样的代码重构是必要的吗?前一个代码维护者说,“虽然使用了IntelliSense,但构造函数的复杂性并不重要——总是可以检查提示并选择正确的一个。”

    10 回复  |  直到 16 年前
        1
  •  4
  •   Pavel Minaev    16 年前

    是的,我将重构这个。IntelliSense只是很有帮助,它盯着一个5参数的构造函数,它使用3个看似随机的字符串,试图找出哪个字符串意味着什么不会提高代码的可读性。在VB(或C 4.0)中,我仍然使用构造函数,但使用命名参数。对于C 3.0,我将创建一个单独的类来保存初始化信息:

    class ObjectSettings
    {
        public string StrA { get; set; }
        public string StrB { get; set; }
        ...
        public string ObjA { get; set; }
        public string ObjB { get; set; }
    }
    

    然后在构造函数中采用该类型的单个参数:

    class Object
    {
        public Object(ObjectSettings settings);
    }
    

    并在调用时使用对象初始值设定项:

    new Object(new ObjectSettings { StrA = ..., StrB = ..., ObjA = ... })
    

    与仅具有 Object 这个模式能保证 对象 将在构造后立即正确初始化。对于对象本身的属性,它将处于无效状态,直到客户机正确地设置它们为止,并且您几乎必须在每次调用时都验证这一点。

    此模式实际上在.NET FCL中使用-请参见 XmlReader / XmlReaderSettings 举个例子。

        2
  •  3
  •   Jon Skeet    16 年前

    另一种选择是暂时保留,然后在C 4带有可选参数和命名参数时将其全部压缩——只要您不需要从可能没有这些特性的任何其他语言调用代码。

    我会亲自创建一个类型的控股 全部的 参数,除非某些重载涉及不同类型(例如 XmlReader.Create 可以应付 TextReader 或者 Stream )不过,我不会把它变成一个结构——只要把它变成一个类,就可以了。给它一个自动实现的属性负载,然后可以使用对象初始值设定项以可读的方式设置参数。

        3
  •  2
  •   Robin    16 年前

    不知道这是否与你的具体情况有关,但是…有时,当我遇到大量风格稍有不同的构造函数时,结果就是所构造的类试图做的太多了。为不同的情况创建子类意味着每个类的构造函数更少,对正在构造的内容的定义也更清晰。如果某些参数是完全可选的,或者只有在其他参数也是设置的情况下,这才是最相关的。

        4
  •  2
  •   chakrit Dutchie432    16 年前

    无论什么原因,当构造一个对象变得复杂时,总是可以选择 the Factory Pattern

    在这种情况下,使用工厂方法模式有点像选项b)

    var factory = new ObjFactory();
    factory.StrA = some;
    factory.StrB = some;
    
    var obj = factory.Create();
    

    但是这样做的好处是, 如果要再次更改构造函数,您所需要做的就是更改工厂实现。 依赖于对象实现的代码在需要更改时不需要更改太多。

    改变类中注入依赖项的方式并不总是一个好的选择。 特别是 当您在处理遗留代码库时。

    如果使用选项b),这意味着对象上的所有方法 应该 在被调用之前检查分配属性的正确组合,否则可能会发生奇怪的事情。但是,如果使用工厂模式,您仍然可以保留旧的构造函数和类实现,而无需在对象中引入新的代码。

        5
  •  1
  •   Nick Lewis    16 年前

    如果它们是可选的,则可能不需要在构造函数内设置(可能)。在这种情况下,除了简单的构造函数之外,您可以删除所有的构造函数,并将它们调用为

    new Object(ParamA) {PropB = ParamB, PropC = ParamC, PropD = ParamD}
    

    这只能用于设置公共的、可设置的属性,但是如果这些参数是对应的,那么在我看来这是一个更好的解决方案。我把构造函数的参数看作是a)正确构造对象所必需的,或者b)在事实发生后是不变的。任何其他内容都应设置为属性。

        6
  •  0
  •   Charlie    16 年前

    任何一个都不比另一个好;事实上,根据参数的类型和用法,您可以同时使用这两者的组合。就个人而言,我更喜欢使用更多的属性(因此选项B),但是对于C 4.0,我们将内置可选参数!你可能只想等到那时。

        7
  •  0
  •   jeroenh    16 年前

    我将使可选参数简单化属性。C 3.5中的类型初始值设定项语法使其非常可读,例如:

    A = new Object(ObjA)
    {
        StrA = someA,
        StrB = someB,
        StrD = someD,
        StrE = someE
    }
    
        8
  •  0
  •   Matt Howells    16 年前

    我会考虑重构 Introduce Parameter Object 或使用 Factory .

    如果一个类的构造函数变得复杂,那么它就打破了单一的责任原则,因为对象有责任正确地创建自己,并完成它设计要做的工作。

        9
  •  0
  •   Mark Heath    16 年前

    如果将构造函数参数移动到属性,请记住,这将允许对象的使用者在任何时候更改该属性。这可能并不总是可能的,这取决于对象的工作方式。

    尽管如此,对于真正可选的参数,或者可以给定合理的默认值,我倾向于使用属性。这会使您的对象更容易用于“普通”情况。

        10
  •  0
  •   Ray    16 年前

    这要看情况而定。

    为什么使用参数而不是属性而不是配置?这要看情况而定。
    ARG的凝聚力如何?
    他们有合理的违约行为吗?
    他们互相依赖吗?等等…

    最后,您的ctor()的用户最简单/完整/适当的期望是什么?