将字符串的特定“类型”传递给函数的“最佳实践”方法是什么?

时间:2011-08-10 15:17:24

标签: java

例如,我有一个有两个字符串的类,其中一个必须设置,另一个可以为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类的代码看起来相当愚蠢,因为它只是定义了一个调用超类构造函数的构造函数......但假设你还在那里添加了一些验证,对我来说这看起来很合理。 实施听起来合理吗?

有没有人会想到更好的概念来解决基本问题,或者更好的实施来概括我所概述的概念?

5 个答案:

答案 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而不是那么明确。
  • 您可以轻松区分两种只从其参数类型中获取ID的方法。

不幸的是,我不能说我在任何大型系统中都有使用这种系统的经验(我在较小的系统中使用它很多)。但是我看到和/或可以想象的一些缺点是:

  • 外部API:如果某些代码调用了您,它通常可以处理String,但不会处理ProductID,因此您需要转换
  • 仍然需要使用裸String值的地方:当用户输入值时,可能需要能够存储它,即使它是有效的产品ID。
  • mindshare:开发人员需要购买这个概念,真正掌握它并接受它。否则你最终会得到一个丑陋的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);
}