我有一个带有重载构造函数的类(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,构造函数的复杂性并不重要 - 总是可以检查提示并选择正确的”
答案 0 :(得分:4)
是的,我会重构这个。 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
中拥有属性相比,这种模式的主要优点是,此模式可确保Object
在构造后立即正确初始化。对于对象本身的属性,它将处于无效状态,直到客户端正确设置它们,并且您必须在几乎每次调用时验证它。
此模式实际上在.NET FCL中使用 - 请参阅XmlReader
/ XmlReaderSettings
作为示例。
答案 1 :(得分:3)
另一种选择是暂时保留,然后在C#4出现可选参数和命名参数时将其全部压缩 - 只要您不需要从任何其他语言调用代码有这些功能。
我个人创建一个包含所有参数的类型,除非某些重载涉及不同的类型(例如XmlReader.Create
可以处理TextReader
或者Stream
)。我不会把它变成一个结构 - 只要把它变成一个类,它就没事了。给它一堆自动实现的属性,然后你可以使用对象初始化器以可读的方式设置参数。
答案 2 :(得分:2)
当构建对象因任何原因变得复杂时,总是可以选择 the Factory Pattern
在这种情况下,使用工厂方法模式有点像选项b)
var factory = new ObjFactory();
factory.StrA = some;
factory.StrB = some;
var obj = factory.Create();
但是这样做的好处是,如果构造函数再次被更改,那么您需要做的就是更改工厂实现。依赖于对象实现的代码如果需要改变则不需要改变很多。
更改类中注入依赖项的方式并不总是一个好的选择。 尤其是,当您使用遗留代码库时。
如果使用选项b),这意味着对象上的所有方法都应在被调用之前检查已分配属性的正确组合,否则可能会发生奇怪的事情。但是如果你使用工厂模式,你仍然可以保留旧的构造函数和类实现,而不会在对象中引入新的代码。
答案 3 :(得分:2)
不知道它是否与你的具体情况有关,但是...有时当我遇到大量略有不同风味的构造函数时,结果就是正在构建的类正在尝试许多。为不同的情况创建子类意味着每个类的构造函数更少,并且对正在构造的内容有更清晰的定义。如果某些参数完全是可选的,或者只有在设置了其他参数时才有意义,这是最相关的。
答案 4 :(得分:1)
如果它们是可选的,它们可能不需要在构造函数中设置(可能)。在这种情况下,您可以删除除简单构造函数之外的所有构造函数,并将它们称为
new Object(ParamA) {PropB = ParamB, PropC = ParamC, PropD = ParamD}
这只能用于设置公共的可设置属性,但如果这些参数对应,那么在我看来这是一个更好的解决方案。我认为构造函数的参数是a)正确构造对象所必需的东西,或b)事后不可更改的东西。其他任何东西都应该被设置为一个属性。
答案 5 :(得分:0)
两者都没有比另一个更好;实际上,您可以根据参数的类型和用法使用两者的组合。就个人而言,我更喜欢使用更多属性(因此选项B),但使用C#4.0,我们将内置可选参数!你可能只想等到那时。
答案 6 :(得分:0)
我会将可选参数设为简单属性。 C#3.5中的类型初始化程序语法使其非常易读,例如:
A = new Object(ObjA)
{
StrA = someA,
StrB = someB,
StrD = someD,
StrE = someE
}
答案 7 :(得分:0)
我会考虑重构为Introduce Parameter Object或使用Factory。
如果一个类的构造函数变得复杂,那么它就违反了单一责任原则,因为该对象有责任正确地创建自己并完成它旨在完成的工作。
答案 8 :(得分:0)
如果将构造函数参数移动为属性,请记住,这将允许对象的使用者在任何时候更改该属性。这可能并非总是可行,具体取决于对象的工作方式。
话虽如此,我倾向于选择真正可选参数的属性,或者可以给出合理的默认值。这往往会使您的对象更容易用于“正常”情况。
答案 9 :(得分:0)
为什么在配置上使用参数而不是属性? ......这取决于
args的凝聚力如何?
他们有合理的违约吗?
他们互相依赖吗?......等等......
最终,对于您的ctor()用户,最简单/最完整/最合适的期望是什么?