【问题标题】:Generic Builder in JavaJava 中的通用生成器
【发布时间】:2014-02-26 22:36:02
【问题描述】:

我为查找表创建了一个构建器并使用它,如下所示。

public class RaceCodeDataBuilder {

    private RaceCode raceCode;  

    public RaceCodeDataBuilder() {
        raceCode = new RaceCode();
    }

    public RaceCodeDataBuilder code(String code) {
        raceCode.setCode(code);     
        return this;
    }

    public RaceCodeDataBuilder displayName(String displayName) {
        raceCode.setDisplayName(displayName);
        return this;
    }

    public RaceCode build() {
        return raceCode;
    }

}

在测试中使用此构建器:

   RaceCode mockRaceCode = new RaceCodeDataBuilder()
                         .code("2054-5")
                         .displayName("Black or African American")
                         .build();

我期待更多类似的构建器用于其他查找表,例如 StateCodeBuilder、GenderCodeBuilder,它们都只有“代码”和“显示名称”,类似于上面的构建器。

我想创建一个通用构建器,并避免创建多个构建器类以不同的名称执行相同的工作。

我在泛型中尝试了一些东西,但我还差得很远..

public class CodeDataBuilder<T>{

    private T t;    

    public CodeDataBuilder(T t) {
        this.t = t;
    }

    public CodeDataBuilder code(String code) {
        raceCode.setCode(code);     // Cant write T.setCode here for obvious resons
        return this;
    }

    public CodeDataBuilder displayName(String displayName) {
        raceCode.setDisplayName(displayName); // Cant write T.setDisplayNamehere for obvious resons
        return this;
    }

    public T build() {
        return t;
    }

}

有人可以帮我吗?

谢谢。

【问题讨论】:

  • 创建一个接口,这样所有实现该接口的类都会保证有一定的方法,例如.setCode()。将T 转换为Interface,您可以访问它的方法。

标签: java generics design-patterns


【解决方案1】:

使用您需要的方法创建一个接口BuildableCodeData,并使用RaceData 等类实现它。

您的代码将如下所示:

public interface BuildableCodeData {

  public void setCode(String code);

  public void setDisplayName(String name);
}

public class Builder<T extends BuildableCodeData> {
  private T codeData;

  public Builder(T codeData) {
    this.codeData = codeData;
  }

  public Builder<T> setCode(String code) {
    codeData.setCode(code);
    return this;
  }

  public Builder<T> setDisplayName(String displayName) {
    codeData.setDisplayName(displayName);
    return this;
  }

  public T build() {
    return codeData;
  }
}

【讨论】:

  • 感谢 abra,您的解决方案干净清晰。我唯一不喜欢的是我需要“修改” RaceData 来实现我的接口,如果它是我无法更改的 Legacy/ThirdParty 类怎么办?
  • 将它包裹在另一个实现该接口的类周围。
【解决方案2】:

看起来你应该使用一个接口,并让你的构建方法返回那个接口。例如:

public interface Buildable{
    void setDisplayName(String name);
    void setCode(String code);
}

public class CodeDataBuilder {

    private Buildable mObj;    

    public CodeDataBuilder(Buildable mObj) {
        this.mObj = mObj;
    }

    public CodeDataBuilder code(String code) {
        mObj.setCode(code);     // Cant write T.setCode here for obvious resons
        return this;
    }

    public CodeDataBuilder displayName(String displayName) {
        mObj.setDisplayName(displayName); // Cant write T.setDisplayNamehere for obvious resons
        return this;
    }

    public Buildable build() {
        return mObj;
    }

}
}

然后让任何你想构建的对象实现 Buildable 接口。

【讨论】:

  • 您每次都必须将构建器的输出转换为特定的类。
  • 没错,但演员表并不是真正声明的问题——只是不必实现多个构建器。如其他答案所示,扩展为使用泛型相对容易。
  • 谢谢 Submersed,你的方法是正确的,但我接受 Abra 的回答,因为他用泛型展示了它,这符合我的需要并避免了显式转换。
【解决方案3】:

如果您使用所需方法创建接口:

interface CodeModel {
  public void setCode(String s);
  public void setDisplayName(String s);
}

然后你可以让你的泛型类只接受T extends CodeModel,像这样:

class CodeDataBuilder<T extends CodeModel> {
  // T has setCode method now!
}

希望这会有所帮助!

【讨论】:

  • 以及接口如何消耗泛型参数?
  • 很抱歉,这是一个错字。删除了错误。
【解决方案4】:

如果你有一个带有一些标准函数的接口,你可以为它创建一个通用构建器。基础构建器将是抽象的,并且对于每个具体的实现,都会有一个具体的构建器。

界面:

public interface CodeNameable  {
   String getCode();
   String getName();
}

具体实现:

public class CodeNamedCar implements CodeNameable  {
   private String code;
   private String name;
   public CodeNamedCar(String code, String name)  {
      this.code = code;
      this.name = name;
   }
}

抽象生成器:

public abstract class CodeNameBuilder<C extends CodeNameable>  {
   public String code;
   public String name;
   public CodeNameBuilder()  {
   }
}

混凝土建造者:

public abstract class CarBuilder extends CodeNameBuilder<CodeNamedCar>  {
   public CarBuilder()  {
   }
   public CarBuilder code(String co_de)  {
      this.code = code;
      return  this;
   }
   public CarBuilder name(String name)  {
      this.name = name;
      return  this;
   }
   public CodeNameCar build()  {
      return  (new CodeNameCar(code, name));
   }
}

那么你就可以随心所欲地使用它了:

CodeNamedCar car = new CarBuilder().code("thecode").name("Mazda").build();

使用这种设计,您需要在CodeNameCar 构造函数中检查每个字段的正确性(例如,非空和非空)。还有其他设计方法。

【讨论】:

    【解决方案5】:

    构建器模式即将创建一个新的类实例并尽可能多地初始化它。

    按照您的方式,您倾向于使用某些属性的部分设置器完成并创建一个可能的假关系,因为两个对象具有相同的属性。

    为了在示例中支持这一点,所有东西都有一个 name 属性,但你没有创建一个名为 Nameable 的超级接口并在每个可能的地方实现它。

    如果这些属性在您的类之间共享,您应该考虑为它们创建一个类。

    class Code {
      int number; 
      String name;
    }
    
    class Race {
      Code code;
      //other attributes; 
    }
    

    然后你有一个用于代码的构建器和另一个用于竞赛的构建器。

    还要注意,好的设计是一种权衡取舍。如果两个字段对于 5 个类是通用的,那么目的是使代码复杂化并创建一个专门的机制,该机制只会打包初始化并且不会做任何有成效的事情。

    【讨论】:

      猜你喜欢
      • 2016-08-15
      • 1970-01-01
      • 1970-01-01
      • 2011-03-01
      • 2011-05-30
      • 1970-01-01
      • 1970-01-01
      • 2017-01-22
      • 1970-01-01
      相关资源
      最近更新 更多