Help us understand the problem. What is going on with this article?

メソッド参照とBeanValidationによる汎用ビルダー

More than 3 years have passed since last update.

Javaで多くのパラメータをもつオブジェクトを生成するとき、ビルダーパターンというやつがよく使われます。

http://www.techscore.com/tech/DesignPattern/Builder.html/

Undertow server = Undertow.builder()
    .addHttpListener(port, "localhost")
    .setHandler(path)
    .build();

いろいろオプションを付けっていって、最後にbuildメソッドを呼ぶと、パラメータ間の整合性のチェックがされ、インスタンスが作られます。

ときに、そういうオプションを色々もつようなクラスをたくさん作るようなケースで、このビルダーをそれぞれ用意すると大変だよ、ということになります。

そういうとき、メソッド参照とBeanValidationを使えば、汎用的なビルダーを作れます。

こんな感じの汎用ビルダーを用意します。

public class BeanBuilder<X> {
   private X x;
   private ValidatorFactory validatorFactory;

   private BeanBuilder(X x, ValidatorFactory validatorFactory) {
       this.x = x;
       this.validatorFactory = validatorFactory;
   }

   public static <Y> BeanBuilder<Y> builder(Y x, ValidatorFactory factory) {
       return new BeanBuilder<>(x, factory);
   }

   public static <Y> BeanBuilder<Y> builder(Y x) {
       return new BeanBuilder<>(x, null);
   }

   public <V> BeanBuilder<X> set(BiConsumer<X, V> caller, V v) {
       caller.accept(x, v);
       return this;
   }

   public X build() {
       if (validatorFactory != null) {
           Validator validator = validatorFactory.getValidator();
           Set<ConstraintViolation<X>> violations = validator.validate(x);
           if (!violations.isEmpty()) {
               throw new IllegalArgumentException(
                        violations.stream().map(ConstraintViolation::getMessage)
                                .collect(Collectors.joining(",")));
           }
       }
       return x;
   }
}

そしてこんな感じのBeanがあったとします。

    @Data
    public static class Commodity {
        @NonNull
        private Integer id;
        @NonNull
        private String name;
        @NonNull
        private Long catalogPrice;

        private Long lowestPrice;
        private Long averagePrice;
        private String makerName;

        public Commodity (int id, String name, long catalogPrice) {
            this.id = id;
            this.name = name;
            this.catalogPrice = catalogPrice;
        }

        @AssertTrue(message = "lowest price is grater than average price.")
        private boolean isAverageGreaterThanLowest() {
            if (lowestPrice != null && averagePrice != null) {
                return lowestPrice < averagePrice;
            } else {
                return true;
            }
        }
    }

Lower priceとAverage priceが両方値を持つときは、Lowerの方が小さくなくてはならないというルールが存在します。

これを使うと、あとはセッターをメソッド参照で指定して、以下のようにビルダーできます。

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Commodity commodity = BeanBuilder.builder(new Commodity(1, "NOTE", 100), factory)
                .set(Commodity::setAveragePrice, 120L)
                .set(Commodity::setLowestPrice, 80L)
                .build();

そして、

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Commodity commodity = BeanBuilder.builder(new Commodity(1, "NOTE", 100), factory)
                .set(Commodity::setAveragePrice, 110L)
                .set(Commodity::setLowestPrice, 120L)
                .build();

これはCommodityのルールに反するのでエラーになります。

java.lang.IllegalArgumentException: lowest price is grater than average price.
kawasima
Clojure関連のことをブログがわりに書き綴ります。 ※ここでの発言はシステムエンジニアを代表するものであって、所属する組織は二の次です。
https://github.com/kawasima/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした