【问题标题】:Builder pattern that requires at least two properties to be specified至少需要指定两个属性的构建器模式
【发布时间】:2022-01-20 16:09:48
【问题描述】:

我正在编写一个RequestBuilder 类,它将根据以下条件处理查询字符串的创建

  • 类别 (String)
  • 国家 (String)
  • 关键字 (String[])
  • 页面 (int)
  • pageSize (int)

由于并非所有标准都是强制性的,而且它们之间有很多组合(我数了 7 个,其中只有四个应该是有效的 - 见下文),我决定使用构建器模式:

public class RequestBuilder {

    private String category = "";
    private String country = "&country=us";
    private String keywords = "";
    private String page = "";
    private String pageSize = "&pageSize=100";

    public RequestBuilder() {
        
    }

    private String buildQuery() {
        return this.category + this.country + this.keywords + this.page + this.pageSize;
    }
    // the setter methods, which I omitted for readability

但是有一个问题。在构建对象之前,我需要强制用户至少指定categorycountrykeywords 中的两个(现在用户甚至没有义务指定一个!)。例如,用户不应仅通过指定 country 来创建对象。 那么我如何强制这个要求呢?如果我创建三个构造函数(每个都有两个参数),我觉得我正在破坏 Builder 模式,即使还有另外三个可选属性要指定。

【问题讨论】:

    标签: java class optional-parameters builder-pattern


    【解决方案1】:

    作为设计师,您需要确定真正需要哪些字段。没有“可能需要”这样的东西。要使用 Builder Pattern 并强制执行所需的参数,请将字段标记为 final 并通过构造函数注入它们:

    public class Request {
        // fields
        private final String requiredField;
        private final String optional1;
        private final String optional2;
    
        private Request(RequestBuilder builder) {
            requiredField = builder.requiredField;
            optional1 = builder.optional1;
            optional2 = builder.optional2;
        }
    
        // add only getter method to the Request class (builds immutable Request objects)
    
        public static class RequestBuilder {
            private final String requiredField;
            private String optional1;
            private String optional2;
    
            public RequestBuilder(String requiredField) {
                this.requiredField = requiredField;
            }
    
            public RequestBuilder setOptional1(String optional1) {
                this.optional1 = optional1;
                return this;
            }
    
            public RequestBuilder setOptional2(String optional2) {
                this.optional2 = optional2;
                return this;
            }
    
            public Request build() {
                return new Request(this);
            }
        }
    }
    

    构建器强制执行必填和可选字段。构建器正在构建的对象隐藏了构造器,因此它只能通过构建器访问。请求对象中的字段都是最终的,以实现不变性。

    要使用,您将执行以下操作:

    RequestBuilder builder = new RequestBuilder("required");
    Request request = builder.setOptional1("foo").setOptional2("bar").build();
    

    或者您可以在调用构建器构造函数后随时调用build()

    更新

    现在解决您的问题....您可以(可能)修改build() 以检查您有多少“半必需”字段的值并将其与字段总数进行比较。对我来说,这是一个黑客。为此,您有两种选择

    1. 硬编码字段数并检查总数中有多少仍为空或空。如果未设置的字段数低于某个计数,则抛出一些异常(即InvalidRequiredFieldCount)。否则,您将返回新实例。为此,您需要在每次调用 setter 方法时增加“计数”。
    2. 使用反射获取字段列表(数组)并使用此字段并使用此字段计数来计算“必需”字段的最小数量。如果未达到最小值,则抛出异常;如果达到最小值,则返回新的请求实例。
    public Request build() throws Exception {
        Request request = new Request(this);
        int count = 0;
        int max = 2;
        Field[] allFields = Request.class.getDeclaredFields();
        for (Field field : allFields) {
            Object o = field.get(request);
            if (o != null) {
                count++;
            }
        }
            
        if (count < 2) {
            throw new Exception("Minimum number of set fields (2) not reached");
        }
            
        return request;
    }
    

    这并不漂亮,但它确实有效。如果我运行这个:

    RequestBuilder builder = new RequestBuilder("required");
    Request request = builder.build();
    

    会导致异常:

    Exception in thread "main" java.lang.Exception: Minimum number of set fields (2) not reached
        at com.master.oxy.Request$RequestBuilder.build(Request.java:54)
        at com.master.oxy.Request.main(Request.java:63)
    

    但是,如果我设置了至少一个可选项,则将返回新实例。

    【讨论】:

    • 这正是我所要求的,你解释得很好。感谢这个惊人的答案!
    • @DanielHalachev 尽管对build() 的修改可以被视为“酷”、“聪明”等,但我想重申这是一个糟糕的想法。我建议你不要这样做。坚持正确形式的构建器模式,其中包含明确的必填字段和可选字段。但是,如果您必须这样做,我很高兴能够为您提供此服务。
    猜你喜欢
    • 2015-10-28
    • 2021-08-02
    • 2011-05-17
    • 1970-01-01
    • 1970-01-01
    • 2020-07-07
    • 2019-01-24
    • 2012-10-30
    • 2015-10-28
    相关资源
    最近更新 更多