首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java:如何实现一个setter顺序不重要的步骤生成器?

Java:如何实现一个setter顺序不重要的步骤生成器?
EN

Software Engineering用户
提问于 2016-07-07 22:55:52
回答 4查看 5.6K关注 0票数 11

编辑:我想指出,这个问题描述了一个理论问题,我知道我可以为强制参数使用构造函数参数,或者在API使用不当时抛出运行时异常。但是,我正在寻找一种不需要构造函数参数或运行时检查的解决方案。

假设您有这样一个Car接口:

代码语言:javascript
复制
public interface Car {
    public Engine getEngine(); // required
    public Transmission getTransmission(); // required
    public Stereo getStereo(); // optional
}

如注释所示,Car必须具有EngineTransmission,但Stereo是可选的。这意味着一个可以build() Car实例的生成器只应该有一个build()方法,如果EngineTransmission都已经给了构建器实例。这样,类型检查器将拒绝编译任何试图在没有CarTransmission的情况下创建Engine实例的代码。

这需要一个步骤生成器。通常,您会实现如下所示:

代码语言:javascript
复制
public interface Car {
    public Engine getEngine(); // required
    public Transmission getTransmission(); // required
    public Stereo getStereo(); // optional

    public class Builder {
        public BuilderWithEngine engine(Engine engine) {
            return new BuilderWithEngine(engine);
        }
    }

    public class BuilderWithEngine {
        private Engine engine;
        private BuilderWithEngine(Engine engine) {
            this.engine = engine;
        }
        public BuilderWithEngine engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            return new CompleteBuilder(engine, transmission);
        }
    }

    public class CompleteBuilder {
        private Engine engine;
        private Transmission transmission;
        private Stereo stereo = null;
        private CompleteBuilder(Engine engine, Transmission transmission) {
            this.engine = engine;
            this.transmission = transmission;
        }
        public CompleteBuilder engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            this.transmission = transmission;
            return this;
        }
        public CompleteBuilder stereo(Stereo stereo) {
            this.stereo = stereo;
            return this;
        }
        public Car build() {
            return new Car() {
                @Override
                public Engine getEngine() {
                    return engine;
                }
                @Override
                public Transmission getTransmission() {
                    return transmission;
                }
                @Override
                public Stereo getStereo() {
                    return stereo;
                }
            };
        }
    }
}

有一系列不同的构建器类(BuilderBuilderWithEngineCompleteBuilder),它们一个接一个地添加所需的setter方法,最后一个类也包含所有可选的setter方法。

这意味着此步骤生成器的用户仅限于作者使强制设置器可用的顺序。下面是一个可能使用的示例(请注意,它们都是严格排序的:首先是engine(e),其次是transmission(t),最后是可选的stereo(s))。

代码语言:javascript
复制
new Builder().engine(e).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).engine(e).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).engine(e).build();
new Builder().engine(e).transmission(t).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).stereo(s).build();

但是,在很多情况下,这对构建器的用户来说并不理想,特别是如果构建器不仅有设置程序,而且还有加数器,或者如果用户无法控制构建器的某些属性可用的顺序。

我能想到的唯一解决方案是非常复杂的:对于已经设置或尚未设置的强制属性的每个组合,我创建了一个专用构建器类,该类知道在到达build()方法应该可用的状态之前需要调用哪些其他强制设置器,并且每个设置器返回一个更完整的构建器类型,它距离包含一个build()方法更近一步。

我在下面添加了代码,但您可能会说,我正在使用类型系统创建一个允许您创建Builder的FSM,它可以转换为BuilderWithEngineBuilderWithTransmission,然后两者都可以转换为CompleteBuilder,后者实现了build()方法。可以在任何这些构建器实例上调用可选的setter。

代码语言:javascript
复制
public interface Car {
    public Engine getEngine(); // required
    public Transmission getTransmission(); // required
    public Stereo getStereo(); // optional

    public class Builder extends OptionalBuilder {
        public BuilderWithEngine engine(Engine engine) {
            return new BuilderWithEngine(engine, stereo);
        }
        public BuilderWithTransmission transmission(Transmission transmission) {
            return new BuilderWithTransmission(transmission, stereo);
        }
        @Override
        public Builder stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
    }

    public class OptionalBuilder {
        protected Stereo stereo = null;
        private OptionalBuilder() {}
        public OptionalBuilder stereo(Stereo stereo) {
            this.stereo = stereo;
            return this;
        }
    }

    public class BuilderWithEngine extends OptionalBuilder {
        private Engine engine;
        private BuilderWithEngine(Engine engine, Stereo stereo) {
            this.engine = engine;
            this.stereo = stereo;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            return new CompleteBuilder(engine, transmission, stereo);
        }
        public BuilderWithEngine engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        @Override
        public BuilderWithEngine stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
    }

    public class BuilderWithTransmission extends OptionalBuilder {
        private Transmission transmission;
        private BuilderWithTransmission(Transmission transmission, Stereo stereo) {
            this.transmission = transmission;
            this.stereo = stereo;
        }
        public CompleteBuilder engine(Engine engine) {
            return new CompleteBuilder(engine, transmission, stereo);
        }
        public BuilderWithTransmission transmission(Transmission transmission) {
            this.transmission = transmission;
            return this;
        }
        @Override
        public BuilderWithTransmission stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
    }

    public class CompleteBuilder extends OptionalBuilder {
        private Engine engine;
        private Transmission transmission;
        private CompleteBuilder(Engine engine, Transmission transmission, Stereo stereo) {
            this.engine = engine;
            this.transmission = transmission;
            this.stereo = stereo;
        }
        public CompleteBuilder engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            this.transmission = transmission;
            return this;
        }
        @Override
        public CompleteBuilder stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
        public Car build() {
            return new Car() {
                @Override
                public Engine getEngine() {
                    return engine;
                }
                @Override
                public Transmission getTransmission() {
                    return transmission;
                }
                @Override
                public Stereo getStereo() {
                    return stereo;
                }
            };
        }
    }
}

可以看出,这不是很好的扩展,因为所需的不同构建器类的数量是O(2^n),其中n是强制设置器的数量。

,因此我的问题:这能做得更优雅吗?

(我正在寻找一个适用于Java的答案,尽管Scala也是可以接受的)

EN

回答 4

Software Engineering用户

发布于 2016-07-08 17:57:29

根据您提供的方法调用,您似乎有两个不同的需求。

  1. 只有一个(必需)引擎,一个(必需)传输,和一个(可选)立体声。
  2. 一个或多个(必需)引擎、一个或多个(必需)传输和一个或多个(可选)立体声。

我认为这里的第一个问题是,你不知道你想让这门课做什么。部分原因是它不知道你想要构建的对象是什么样子。

一辆汽车只能有一个发动机和一个变速器。即使是混合动力汽车也只有一台发动机(也许是GasAndElectricEngine)。

我将讨论这两种实现:

代码语言:javascript
复制
public class CarBuilder {

    public CarBuilder(Engine engine, Transmission transmission) {
        // ...
    }

    public CarBuilder setStereo(Stereo stereo) {
        // ...
        return this;
    }
}

代码语言:javascript
复制
public class CarBuilder {

    public CarBuilder(List<Engine> engines, List<Transmission> transmission) {
        // ...
    }

    public CarBuilder addStereo(Stereo stereo) {
        // ...
        return this;
    }
}

如果需要引擎和变速器,那么它们应该在构造函数中。

如果您不知道所需的引擎或变速器,那么还不要设置一个;这是一个迹象,您正在创建的构建器太高的堆栈。

票数 4
EN

Software Engineering用户

发布于 2016-07-08 13:31:51

为什么不使用空对象模式?摆脱这个构建器,您可以编写的最优雅的代码就是您实际上不必编写的代码。

代码语言:javascript
复制
public final class CarImpl implements Car {
    private final Engine engine;
    private final Transmission transmission;
    private final Stereo stereo;

    public CarImpl(Engine engine, Transmission transmission) {
        this(engine, transmission, new DefaultStereo());
    }

    public CarImpl(Engine engine, Transmission transmission, Stereo stereo) {
        this.engine = engine;
        this.transmission = transmission;
        this.stereo = stereo;
    }

    //...

}
票数 2
EN

Software Engineering用户

发布于 2016-07-08 09:55:35

首先,除非你有比我工作过的任何一家商店更多的时间,否则可能不值得允许任何操作订单,或者仅仅接受这样一个事实:你可以指定一个以上的收音机。请注意,您讨论的是代码,而不是用户输入,因此您可以在单元测试期间而不是在编译时的一秒钟前获得将失败的断言。

但是,如果您的约束(如注释中所给出的那样)必须有一个引擎和一个传输程序,那么通过将所有必需的属性都设置为构建器的构造函数来强制执行该约束。

代码语言:javascript
复制
new Builder(e, t).build();                      // ok
new Builder(e, t).stereo(s).build();            // ok
new Builder(e, t).stereo(s).stereo(s).build();  // exception on second call to stereo as stereo is already set 

如果只有立体声是可选的,那么使用构建器的子类执行最后一步是可能的,但除此之外,在编译时而不是在测试中获得错误的好处可能不值得付出努力。

票数 1
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/324316

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档