例如,我有一个有两个字符串的类,其中一个必须设置,另一个可以为null:
public class SomeClass{
private String s1;
private String s2;
...
}
我可以按如下方式定义构造函数:
public SomeClass(String s1, String s2){
if(s1 == null && s2 == null) throw new SomeKindOfException("can't both be null");
this.s1 = s1;
this.s2 = s2;
}
我宁愿做一些事情:
public SomeClass(String s1){
this.s1 = s1;
}
public SomeClass(String s2){
this.s2 = s2;
}
这显然无法工作,因为它定义了两个采用相同数量和类型参数的方法。
所以,我想做点什么:
public SomeClass(SomeTypeOfString s1){
this.s1 = s1;
}
public SomeClass(AnotherTypeOfString s2){
this.s2 = s2;
}
这有一个额外的好处,即String的“类型”可以验证它们的内容(例如,SomeTypeOfString必须是6个字符长,而AnotherTypeOfString必须长度在4到8个字符之间,并且只包含字母数字字符。
此外,它更清楚,在调用函数时,应该传入哪些数据。
首先,这个概念听起来合理吗?
在实现方面,您无法扩展String。你可以把它包起来。 String的“类型”本质上是String的类型,因此让它们扩展抽象包装类是有意义的。这可以实现如下:
public abstract class StringWrapper{
private final String string;
public StringWrapper(String string){
this.string = string;
}
public String getString(){
return string;
}
}
public class SomeTypeOfString extends StringWrapper{
public SomeTypeOfString(String string){
super(string);
}
}
SomeTypeOfString类的代码看起来相当愚蠢,因为它只是定义了一个调用超类构造函数的构造函数......但假设你还在那里添加了一些验证,对我来说这看起来很合理。 此实施听起来合理吗?
有没有人会想到更好的概念来解决基本问题,或者更好的实施来概括我所概述的概念?
答案 0 :(得分:5)
您可以使用构建器。即。
public SomeClassBuilder {
String s1;
String s2;
public SomeClassBuilder someString1(String s) {
this.s1 = s;
return this;
}
public SomeClassBuilder someString2(String s) {
this.s2 = s;
return this;
}
public SomeClass build() {
SomeClass sc = new SomeClass();
// if one of s1 or s2 must be null, or you have some other sort of validation, you could do that here, e.g.
if (s1 != null && s2 != null) throw new RuntimeException("not what I expected");
else if (s1 == null && s2 == null) throw new RuntimeException("at least set one value");
sc.setS1(s1);
sc.setS2(s2);
return sc;
}
}
用法是:
SomeClass sc = new SomeClassBuilder().someString1("xxx").someString2("yyy").build();
或者:
SomeClass sc = new SomeClassBuilder().someString2("abc").build();
只有两个构造函数参数的好处相对较小,但随着输入的增加,可读性的好处也会增加。
答案 1 :(得分:3)
域特定的方法对我来说似乎很复杂,为什么不简单地定义不同的构造函数:
public class SomeClass {
private final String mandatoryString;
private String optionalString;
...
public SomeClass(final String mandatoryString) {
this(mandatoryString, null);
}
public SomeClass(final String mandatoryString, final String optionalString) {
this.mandatoryString = mandatoryString;
this.optionalString = optionalString;
}
}
编辑:价值对象示例
public class SomeClass {
public SomeClass(ValueObject vo) {
if (vo == null) {
// exception...
} else if (vo.getS1() == null && vo.getS2() == null) {
// exception...
}
// proceed...
}
}
public class ValueObject {
private String s1;
private String s2;
public ValueObject(String s1, String s2) {
this.s1 = s1;
this.s2 = s2;
}
// getters here...
}
使用值对象或域特定对象将数据收集与相应的逻辑分离。定义良好的(哑)业务对象(ValueObject)维护应用程序的状态, SomeClass 负责验证和处理给定的业务对象。
编辑2 :其他答案中提出的 Builder 模式也可能涵盖您的要求。虽然它是一种有效的方法,但在您的用例中可能过于复杂。这完全取决于复杂性......
答案 2 :(得分:3)
如果一直使用,您的概念和实现看起来确实合理,甚至可以提高可读性。
例如,如果一个“type”字符串表示产品ID,那么您可以生成此类:
public abstract class ProductID{
private final String id;
public StringWrapper(String id){
Preconditions.checkNotNull(id, "Can't construct a ProductID from null");
Preconditions.checkArgument(isValidId(id), "%s is not a valid Product ID", id);
this.id = id;
}
public String getID(){
return id;
}
public static boolean isValidID(id) {
// check format
}
public boolean equals(Object o) {
return (o instanceof ProductID && ((ProductID) o).id.equals(this.id));
}
public boolean hashCode() {
return id.hashCode();
}
public String toString() {
return id;
}
}
请注意,必须equals()
/ hashCode()
才能将ProductID
用作Map
中的关键字,或确保Set
中的唯一性类似的事情(除了通常有用)。 toString()
可用作调试工具(在这种情况下,我会添加类名,可能使用整齐的MoreObjects.toStringHelper()
方法或手动)或< / em>用于以UI上使用的方式呈现产品ID(在这种情况下,给定的实现可能没问题。)
这有几个好处:
ProductID
对象都拥有有效产品ID(至少在语法上)ProductID
的方法使非常显式隐藏该值。如果他们接受String
而不是那么明确。不幸的是,我不能说我在任何大型系统中都有使用这种系统的经验(我在较小的系统中使用它很多)。但是我看到和/或可以想象的一些缺点是:
String
,但不会处理ProductID
,因此您需要转换String
值的地方:当用户输入值时,可能需要能够存储它,即使它不是有效的产品ID。String
/ ProductID
组合,到处都有大量的转化。从这三个方面来看,我认为最后一个是最严重的。
但是,如果这对您来说似乎有点过分,那么使用工厂方法而不是构造函数可能是一个解决方案:
public class SomeClass{
private String s1;
private String s2;
private SomeClass(String s1, String s2) {
Preconditions.checkArguments(s1 != null || s2 != null, "One of s1 or s2 must be non-null!");
this.s1 = s1;
this.s2 = s2;
}
public static SomeClass fromSomeString(String s1) {
return new SomeClass(s1, null);
}
public static SomeClass fromAnotherString(String s2) {
return new SomeClass(null, s2);
}
public static SomeClass fromStrings(String s1, String s2) {
return new SomeClass(s1, s2);
}
}
请注意,我使用Guava Preconditions
类来缩短参数检查代码。
答案 3 :(得分:1)
对我来说似乎有点麻烦..我会写一个构造函数,带一个字符串参数 - 不能为null的字符串。在构造函数中,我检查null,如果是,则抛出异常。 关于第二个字符串 - 您有两个选项,或者提供另一个带有2个调用第一个构造函数的字符串的构造函数,或者为第二个字符串设置一个setter方法。
答案 4 :(得分:1)
如果你使用一些静态的“构建器”方法怎么样:
public SomeClass(String s1, String s2) {
if(s1 == null && s2 == null)
throw new SomeKindOfException("can't both be null");
this.s1 = s1;
this.s2 = s2;
}
public static SomeClass newSomeClassFromS1(String s1){
return new SomeClass(s1, null);
}
public static SomeClass newSomeClassFromS2(String s2){
return new SomeClass(null, s2);
}