Рубрики
Без рубрики

Простая Реализация Fluent Builder – Безопасная Альтернатива Традиционному Конструктору

Беглый шаблон строителя. С тегами для начинающих, java, учебник, шаблон.

Шаблон Builder чрезвычайно популярен в приложениях Java. К сожалению, это часто неправильно понимают, неправильно реализуют и используют, что приводит к ошибкам во время выполнения.

Итак, что делать в случаях, когда объект должен быть построен полностью и для полей нет безопасных значений по умолчанию (т. Е. Шаблон компоновщика неприменим)? В таких случаях было бы очень удобно использовать шаблон интерфейса Fluent, который позволяет избежать ошибок, вызванных отсутствующими полями. К сожалению, правильная реализация интерфейса Fluent обычно слишком многословна, чтобы быть практичной, поэтому разработчики используют простой конструктор и стараются полагаться на тщательное тестирование, чтобы избежать ошибок во время выполнения.

К счастью, для наиболее частого случая использования, когда все поля для объекта должны быть заданы, существует простое решение, которое приводит к очень лаконичной и безопасной реализации конструктора с плавным интерфейсом, который я называю Беглый строитель .

Давайте предположим, что мы хотим создать Fluent Builder для простого компонента, показанного ниже:

public class SimpleBean {
    private final int index;
    private final String name;

    private SimpleBean(final int index, final String name) {
        this.index = index;
        this.name = name;
    }

}

В Java 14 такие классы могут быть объявлены как записи, поэтому необходимый шаблонный код будет значительно сокращен.

Давайте добавим конструктор. Первый шаг вполне традиционен:

...
    public static SimpleBeanBuilder builder() {
        return new SimpleBeanBuilder();
    }
...

Давайте сначала внедрим традиционный конструктор, чтобы было более ясно, как создается свободный код конструктора. Традиционный класс строителей выглядел бы так:

...
    private static class SimpleBeanBuilder {
        private int index;
        private String name;

        public SimpleBeanBuilder setIndex(final int index) {
            this.index = index;
            return this;
        }

        public SimpleBeanBuilder setName(final String name) {
            this.name = name;
            return this;
        }

        public SimpleBean build() {
            return new SimpleBean(index, name);
        }
    }

Одно важное наблюдение: каждый сеттер возвращает это а это, в свою очередь, позволяет пользователям этого вызова вызывать все методы, доступные в builder. В этом корень проблемы, потому что пользователю разрешено вызывать метод build() преждевременно, до того, как будут заданы все необходимые поля.

Для того чтобы сделать Fluent Builder, нам нужно ограничить возможные варианты только разрешенными, тем самым обеспечив правильное использование конструктора. Поскольку мы рассматриваем случай, когда необходимо задать все поля, то на каждом этапе построения доступен только один метод. Для этого мы можем вернуть выделенные интерфейсы вместо этого и пусть Builder реализует все эти интерфейсы:

    ...
    public static SimpleBeanBuilder0 builder() {
        return new SimpleBeanBuilder();
    }
    ...

    private static class SimpleBeanBuilder implements SimpleBeanBuilder0, SimpleBeanBuilder1, SimpleBeanBuilder2 {
        private int index;
        private String name;

        public SimpleBeanBuilder1 setIndex(final int index) {
            this.index = index;
            return this;
        }

        public SimpleBeanBuilder2 setName(final String name) {
            this.name = name;
            return this;
        }

        public SimpleBean build() {
            return new SimpleBean(index, name);
        }

        public interface SimpleBeanBuilder0 {
            SimpleBeanBuilder1 setIndex(final int index);
        }

        public interface SimpleBeanBuilder1 {
            SimpleBeanBuilder2 setName(final String name);
        }

        public interface SimpleBeanBuilder2 {
            SimpleBean build();
        }
    }

Хм. Несколько уродливый и много дополнительных шаблонов. Можем ли мы сделать лучше? Давай попробуем.

Первый шаг – прекратить реализацию интерфейсов и вместо этого вернуть анонимные классы, которые реализуют эти интерфейсы:

    public static SimpleBeanBuilder builder() {
        return new SimpleBeanBuilder();
    }
    ...
    ...
    private static class SimpleBeanBuilder {
        public SimpleBeanBuilder1 setIndex(int index) {
            return new SimpleBeanBuilder1() {
                @Override
                public SimpleBeanBuilder2 setName(final String name) {
                    return new SimpleBeanBuilder2() {
                        @Override
                        public SimpleBean build() {
                            return new SimpleBean(index, name);
                        }
                    };
                }
            };
        }

        public interface SimpleBeanBuilder1 {
            SimpleBeanBuilder2 setName(final String name);
        }

        public interface SimpleBeanBuilder2 {
            SimpleBean build();
        }
    }

Это намного лучше. Мы снова можем безопасно вернуть Simple Bean Builder из метода builder() , поскольку этот класс предоставляет только один метод и не позволяет преждевременно создавать экземпляр. Но гораздо важнее то, что мы можем опустить целые шаблоны и шаблонные изменяемые поля в сборщике, значительно сократив объем кода. Это возможно, потому что мы создаем анонимные классы в области, где параметры всех установщиков видны и доступны. И мы можем использовать эти параметры напрямую, без необходимости их хранения!

Результирующий внешний вид кода сопоставим с исходной реализацией конструктора в отношении общего объема кода.

Но мы можем сделать лучше! Поскольку все анонимные классы на самом деле являются реализацией интерфейсов, которые содержат только один метод, мы можем заменить анонимные классы лямбдами:

    private static class SimpleBeanBuilder {
        public SimpleBeanBuilder1 setIndex(int index) {
            return name -> () -> new SimpleBean(index, name);
        }

        public interface SimpleBeanBuilder1 {
            SimpleBeanBuilder2 setName(final String name);
        }

        public interface SimpleBeanBuilder2 {
            SimpleBean build();
        }
    }

Результирующий Fluent Builder содержит еще меньше шаблонного кода, чем исходный конструктор!

Но мы можем сделать еще лучше! Обратите внимание, что оставшийся Простой компоновщик компонентов класс очень похож на другие интерфейсы компоновщика, поэтому мы также можем заменить его на лямбда:

    public static SimpleBeanBuilder builder() {
        return index -> name -> () -> new SimpleBean(index, name);
    }

    public interface SimpleBeanBuilder {
        SimpleBeanBuilder1 setIndex(int index);
    }

    public interface SimpleBeanBuilder1 {
        SimpleBeanBuilder2 setName(final String name);
    }

    public interface SimpleBeanBuilder2 {
        SimpleBean build();
    }

Для тех, кто еще не привык к глубоко вложенным лямбдам, этот код может быть сложнее для чтения. С другой стороны, нет необходимости писать его вручную, так как мы можем разгрузить эту задачу, чтобы УМЕРЕТЬ (так же, как мы делаем с традиционными сборщиками).

Используя этот подход, мы можем заменить традиционных строителей на беглых строителей и получить удобство строителя с безопасностью интерфейса.

Оригинал: “https://dev.to/siy/simple-implementation-of-fluent-builder-safe-alternative-to-traditional-builder-3m1d”