假设您有一个名为Customer的类,其中包含以下字段:
我们还要说,根据您的业务逻辑,所有Customer对象都必须定义这四个属性。
现在,我们可以通过强制构造函数指定每个属性来轻松地完成此操作。但是,当您被迫向Customer对象添加更多必需字段时,很容易看出它会如何失控。
我已经看到了在构造函数中加入20多个参数的类,使用它们只是一种痛苦。但是,或者,如果您不需要这些字段,则可能会遇到未定义信息的风险,或者更糟糕的是,如果您依赖调用代码来指定这些属性,则会引发对象引用错误。
有没有替代方案,或者你只需要决定X的构造函数参数是否太多,你不能忍受?
样式很重要,在我看来,如果有一个包含20多个参数的构造函数,那么应该改变设计。提供合理的默认值。
我同意Boojiboy提到的7项限制。除此之外,可能值得查看匿名(或专用)类型,IDictionary或通过主键间接到另一个数据源。
我用自己的构造/验证逻辑将类似的字段封装到它自己的对象中。
比如说,如果你有的话
我会创建一个存储电话和地址的课程,以及指定其“家”或“商务”电话/地址的标签。然后将4个字段简化为一个数组。
ContactInfo cinfos = new ContactInfo[] {
new ContactInfo("home", "+123456789", "123 ABC Avenue"),
new ContactInfo("biz", "+987654321", "789 ZYX Avenue")
};
Customer c = new Customer("john", "doe", cinfos);
这应该让它看起来不像意大利面条。
当然,如果你有很多领域,你必须有一些你可以提取出来的模式,这将成为一个很好的单位功能。并使代码更易读。
以下也是可能的解决方案:
CustomerFactory
类,帮助我构建Customer
s只需使用默认参数。在支持默认方法参数(例如PHP)的语言中,您可以在方法签名中执行此操作:
public function doSomethingWith($this = val1, $this = val2, $this = val3)
还有其他方法可以创建默认值,例如支持方法重载的语言。
当然,如果您认为适合这样做,也可以在声明字段时设置默认值。
它实际上只取决于您是否适合设置这些默认值,或者是否应始终在构造中指出您的对象。这真的是一个只有你能做出的决定。
除非它超过1个参数,否则我总是使用数组或对象作为构造函数参数,并依赖于错误检查以确保所需的参数存在。
在你的情况下,坚持使用构造函数。该信息属于客户,4个字段都可以。
如果您有许多必需字段和可选字段,则构造函数不是最佳解决方案。正如@boojiboy所说,它很难阅读,而且编写客户端代码也很困难。
@contagious建议使用可选属性的默认模式和setter。这要求字段是可变的,但这是一个小问题。
有效Java 2上的Joshua Block说在这种情况下你应该考虑一个构建器。从这本书中摘取的一个例子:
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// required parameters
private final int servingSize;
private final int servings;
// optional parameters
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val)
{ calories = val; return this; }
public Builder fat(int val)
{ fat = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
soduim = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
然后像这样使用它:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
上面的例子来自Effective Java 2
这不仅适用于构造函数。引用肯特贝克在Implementation Patterns:
setOuterBounds(x, y, width, height);
setInnerBounds(x + 2, y + 2, width - 4, height - 4);
将矩形显式化为对象可以更好地解释代码:
setOuterBounds(bounds);
setInnerBounds(bounds.expand(-2));
我认为“纯OOP”的答案是,如果在未初始化某些成员时该类上的操作无效,则必须由构造函数设置这些成员。总是存在可以使用默认值的情况,但我会假设我们没有考虑这种情况。修复API时这是一种很好的方法,因为在API公开后更改单个允许的构造函数对于您和代码的所有用户来说都是一场噩梦。
在C#中,我对设计指南的理解是,这不一定是处理这种情况的唯一方法。特别是对于WPF对象,您会发现.NET类倾向于支持无参数构造函数,并且如果在调用方法之前数据尚未初始化为所需状态,则会抛出异常。这可能主要针对基于组件的设计;我无法想出一个以这种方式运行的.NET类的具体示例。在您的情况下,它肯定会导致测试负担增加,以确保类永远不会保存到数据存储,除非已经验证了属性。老实说,因为这个我更喜欢“构造函数设置所需的属性”方法,如果你的API是固定的或不公开的。
我确信的一件事是,可能有无数的方法可以解决这个问题,每个方法都会引入自己的一系列问题。最好的办法是学习尽可能多的模式,并选择最适合的工作。 (这不是答案吗?)
我认为你的问题更多的是关于类的设计而不是构造函数中的参数数量。如果我需要20个数据(参数)来成功初始化一个对象,我可能会考虑拆分该类。
Steve Mcconnell在Code Complete中写道,人们无法一次保留更多的东西,所以这就是我试图留下的数字。
如果你有许多不可说的参数,那么只需将它们打包成struct / POD类,最好声明为你正在构建的类的内部类。这样,您仍然可以在调用构造函数的代码合理可读时使用字段。
我认为这一切都取决于具体情况。对于像您的示例,客户类这样的东西,我不会冒险在需要时将数据定义为未定义的机会。另一方面,传递一个struct会清除参数列表,但是你仍然需要在struct中定义很多东西。
我认为最简单的方法是为每个值找到可接受的默认值。在这种情况下,每个字段看起来都需要构造,因此可能会重载函数调用,以便在调用中未定义某些内容时将其设置为默认值。
然后,为每个属性创建getter和setter函数,以便可以更改默认值。
Java实现:
public static void setEmail(String newEmail){
this.email = newEmail;
}
public static String getEmail(){
return this.email;
}
这也是保持全局变量安全的好方法。