【问题标题】:how to implement fluent builder with inheritance in java如何在java中实现带有继承的fluent builder
【发布时间】:2017-04-24 04:06:20
【问题描述】:

问题

我想创建一个具有流利构建器的类,两者都可以继承和扩展。基类应该有所有公共和必填字段,子类应该有不同的可选字段

下面的简单示例(我能想到的最好、最简单的用例;p)

base: Animal
    name
    age

    static Builder


impl: Snake extends Animal
    length

    static Builder extends Animal.Builder


impl: Spider extends Animal
    numberOfLegs

    static Builder extends Animal.Builder

我想以其中一种方式使用它(最喜欢的是第一种):

Spider elvis = Spider.name("elvis").age(1).numberOfLegs(8).build();
Spider elvis = Spider.builder().name("elvis").age(1).numberOfLegs(8).build();
Spider elvis = new Spider.Builder().name("elvis").age(1).numberOfLegs(8).build();

我想要实现的是

  • 此构建器的用户必须提供一些最少的信息(这样系统才能正常工作),否则他将无法构建该对象
  • 所有可选字段都可以声明,没有特定顺序,在必填字段之后
  • 我可能需要为孩子添加一些必填字段,但只需更改构建器中调用的第一个方法即可轻松处理
    • 我不想在这些类之外(这里:在 Main 中)进行任何强制转换,但我不介意在这段代码中(这里:在 Animal 或 Spider 中)

到目前为止,我失败了,如果您能帮我找到解决方法,我将不胜感激 :) 或者也许我应该考虑一种不同的方法?

我使用的最有价值的资源

http://blog.crisp.se/2013/10/09/perlundholm/another-builder-pattern-for-java
http://egalluzzo.blogspot.com/2010/06/using-inheritance-with-fluent.html
Generic fluent Builder in Java

到目前为止完成的工作

到目前为止的代码可以在下面找到。有一些我尝试过但失败的东西的痕迹,有一些未使用或只是奇怪的东西(最好的例子是 IBuildImpl)。这些是为了让您了解我的尝试,但如果您认为这需要适度 - 请告诉我,我会清理它们

基础

package fafafa;

public abstract class Animal<T> {
    String name; //mandatory field, one of many
    Integer age; //mandatory field, one of many

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }


    interface IName {
        IAge name(String name);
    }

    interface IAge {
        IBuild age(Integer age);
    }

    interface IBuild<T extends Animal<T>> {
        T build();
    }


    public abstract static class Builder<T extends Animal<T>, B extends Builder<T, B>>
            implements IName, IAge, IBuild<T> {
        protected T objectBeingBuilt;

        protected abstract B that();
        protected abstract T createEmptyObject();

        Builder(){
            this.objectBeingBuilt = createEmptyObject();
            System.out.println();
        }

        @Override
        public IAge name(String name) {
            objectBeingBuilt.name = name;
            return that();
        }

        @Override
        public IBuild age(Integer age) {
            objectBeingBuilt.age = age;
            return that();
        }

//        @Override
//        public T build() {
//            return objectBeingBuilt;
//        }
    }


}

实施

package fafafa;

public class Spider extends Animal<Spider> {
    Integer numberOfLegs; //optional field, one of many

    private Spider() {
    }

    public Integer getNumberOfLegs() {
        return numberOfLegs;
    }

    @Override
    public String toString() {
        return "Spider{" +
                "numberOfLegs='" + numberOfLegs + '\'' +
                "} " + super.toString();
    }

//    public static Builder<Spider, Builder> name(String name) {
//        return (Builder) new Builder().name(name);
//    }


    interface INumberOfLegs {
        IBuild numberOfLegs(Integer numberOfLegs);
    }

    interface IBuildImpl extends IBuild<Spider>, INumberOfLegs {
        @Override
        Spider build();
    }


    public static class Builder extends Animal.Builder<Spider, Builder> implements IBuildImpl {

        @Override
        protected Builder that() {
            return this;
        }

        @Override
        protected Spider createEmptyObject() {
            return new Spider();
        }


        public IBuild numberOfLegs(Integer numberOfLegs) {
            objectBeingBuilt.numberOfLegs = numberOfLegs;
            return that();
        }

        public Spider build() {
            return objectBeingBuilt;
        }
    }
}

主要

package fafafa;

public class Main {
    public static void main(String[] args) {
        Spider build = new Spider.Builder().name("elvis")
                .age(1)
                .numberOfLegs(8) //cannot resolve method numberOfLegs
                .build();
        System.out.println(build);
    }
}

【问题讨论】:

    标签: java inheritance builder fluent fluent-interface


    【解决方案1】:

    问题出在抽象构建器中:

    public abstract static class Builder<T extends Animal<T>, B extends Builder<T, B>>
            implements IName, IAge, IBuild<T> {
     ...
        @Override
        public IAge name(String name) {
            objectBeingBuilt.name = name;
            return that();
        }
    
        @Override
        public IBuild age(Integer age) {
            objectBeingBuilt.age = age;
            return that();
        }
    

    因此,当您调用 age() 方法时,所有具体构建器都会返回相同的 IBuild&lt;T&gt; 接口。

    如你所见:

    interface IBuild<T extends Animal<T>> {
        T build();
    }
    

    此接口不允许返回一个对象,您可以在其中使用构建器设置属性的方法。

    当您调用 name() 方法时,您也不会得到构建器:

    interface IAge {
        IBuild age(Integer age);
    }
    

    您应该像这样在抽象构建器中声明age()name()

    public abstract static class Builder<T extends Animal<T>, B extends Builder<T, B>>{
       ...
    
        public B name(String name) {
            objectBeingBuilt.name = name;
            return that();
        }
    
    
        public B age(Integer age) {
            objectBeingBuilt.age = age;
            return that();
        }
    

    这样,在编译时,具体构建器将在您调用builder.age(..)时返回您正在创建的动物的构建器。
    此外,我不明白为什么有一个构建器接口用于名称而另一个用于年龄。
    处理IAgeIName 接口有什么兴趣?
    这似乎是一个太低级别的信息,在您的构建器中没有用处。
    为什么不简单地像这样声明你的基础构建器:

    public abstract static class Builder<T extends Animal<T>, B extends Builder<T, B>>
                implements IBuild<T> {
            protected T objectBeingBuilt;
    
            protected abstract B that();
            protected abstract T createEmptyObject();
    
            Builder(){
                this.objectBeingBuilt = createEmptyObject();
                System.out.println();
            }
    
            public B name(String name) {
                objectBeingBuilt.name = name;
                return that();
            }
    
            public B age(Integer age) {
                objectBeingBuilt.age = age;
                return that();
            }
    
        }
    

    我没有测试过代码。

    【讨论】:

    • 嘿。感谢您的评论,但我遇到的整个问题是构建这个 fluent 构建器。在这种情况下,我希望用户被限制为使用名字,然后是年龄,然后是其他人,别无他法:)
    • 没问题 :) 就我个人而言,我发现修复使用方法的顺序太受限制了。客户端应该能够选择要填充的属性的顺序。重要的是您的构建方法会在返回对象之前检查状态是否一致。
    【解决方案2】:

    看起来代码中有很多泛型,我试着简化了一点。

    动物

    package come.stackoverflow.builder;
    
    public abstract class Animal {
      private final String name; //mandatory field, one of many
      private final Integer age; //mandatory field, one of many
    
      Animal(final String name, final Integer age) {this.name = name; this.age = age;}
    
      public String getName() {return name;}
      public Integer getAge() {return age;}
      @Override public String toString() {return String.format("Animal {name='%s', age='%s'}'", name, age);}
    
      interface IBuild<T> {
        T build();
      }
    
      public abstract static class AnimalBuilder<B extends AnimalBuilder, T extends Animal> implements IBuild<T> {
        String name;
        Integer age;
    
        public B name(final String name) {this.name = name; return (B) this;}
        public B age(final Integer age) {this.age = age; return (B) this;}
      }
    }
    

    蜘蛛

    package come.stackoverflow.builder;
    
    public class Spider extends Animal {
      private final Integer numberOfLegs; //optional field, one of many
    
      private Spider(final String name, final Integer age, final Integer numberOfLegs) {super(name, age); this.numberOfLegs = numberOfLegs;}
    
      public Integer getNumberOfLegs() {return numberOfLegs;}
      @Override public String toString() {return String.format("Spider {numberOfLegs='%s'}, %s", getNumberOfLegs(), super.toString());}
    
      public static class SpiderBuilder extends AnimalBuilder<SpiderBuilder, Spider> {
        Integer numberOfLegs;
        public SpiderBuilder numberOfLegs(final Integer numberOfLegs) {this.numberOfLegs = numberOfLegs; return this;}
        public Spider build() {return new Spider(name, age, numberOfLegs);}
      }
    }
    

    主要测试

    import come.stackoverflow.builder.Spider;
    
    public class Main {
        public static void main(String[] args) {
            Spider build = new Spider.SpiderBuilder()
                .name("elvis").numberOfLegs(8).age(1)
                .build();
            System.out.println(build);
        }
    }
    

    执行结果: Spider {numberOfLegs='8'}, Animal {name='elvis', age='1'}'

    【讨论】:

    【解决方案3】:

    你的代码的问题是接口:

    interface IAge {
        IBuild age(Integer age);
    }
    

    这将始终返回不带参数的基本IBuild 接口,不管实现是否带有一些参数。实际上,即使使用参数返回它也不会使用其他方法扩展构建器。

    1. builder 中的参数需要是扩展的builder,而不是要构建的类型。
    2. 公共参数的所有接口都需要使用它进行参数化,以允许适当的延续。

    这里有一个建议: 1.不要使用IName接口。替换为builder的静态入口方法 2.参数化IAge接口 3. 无需通用构建器。可以用内联 lambda 实现代替

    代码如下:

    @FunctionalInterface
    public interface IAge<B> {
        B age(Integer age);
    }
    
    public class AnimalBuilder implements IBuild<Animal> {
    
        private final String name;
        private final Integer age;
        private Integer numberOfLegs;
    
        private AnimalBuilder(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    
        // Builder entry method
        public static IAge<AnimalBuilder> name(String name) {
            return age -> new AnimalBuilder(name, age);
        }
    
        public AnimalBuilder numberOfLegs(int value) {
            numberOfLegs = value;
            return this;
        }
    
        @Override
        public Animal build() {
            return new Animal(name, age, numberOfLegs);
        }
    }
    

    这允许以下用法:

    AnimalBuilder.name("elvis").age(1).numberOfLegs(8).build();
    

    【讨论】:

    • 非常感谢。我知道我只是错过了一些通用声明,这一直困扰着我:) 为了完整起见,我将粘贴我的最终代码(基本版本,没有 lambda)作为答案
    猜你喜欢
    • 2019-11-23
    • 1970-01-01
    • 2013-06-14
    • 1970-01-01
    • 1970-01-01
    • 2018-02-06
    • 2017-06-15
    • 2016-04-14
    • 1970-01-01
    相关资源
    最近更新 更多