Подтвердить что ты не робот

Является ли этот Monster Builder хорошим шаблоном Builder/Factory для абстрагирования длинных конструкторов, смешанных с сеттерами?

Это вопрос о человеческом интерфейсе о объединении шаблона построителя шагов с расширенным или wizard шаблоны builder в creational DSL. Он использует свободный интерфейс, хотя он использует цепочку методов, а не каскадирование. То есть методы возвращают разные типы.

Im сталкивается с классом монстров, который имеет два конструктора, которые принимают смесь из целых чисел, строк и массива строк. Каждый конструктор имеет 10 параметров. Он также имеет около 40 опциональных сеттеров; некоторые из которых конфликтуют друг с другом, если они используются вместе. Его код конструкции выглядит примерно так:

Person person = Person("Homer","Jay", "Simpson","Homie", null, "black", "brown", 
  new Date(1), 3, "Homer Thompson", "Pie Man", "Max Power", "El Homo", 
  "Thad Supersperm", "Bald Mommy", "Rock Strongo", "Lance Uppercut", "Mr. Plow");

person.setClothing("Pants!!");     
person.setFavoriteBeer("Duff");
person.setJobTitle("Safety Inspector");

Это в конечном итоге терпит неудачу, потому что получается, что оба любимого пива и название должности несовместимы. Вздох.

Реорганизация класса монстров не является вариантом. Его широко используется. Оно работает. Я просто не хочу смотреть, как он создается напрямую. Я хочу написать что-то чистое, что будет его кормить. Что-то, что будет следовать его правилам, не заставив разработчиков запомнить их.

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

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

Конструкторы заставляют разработчика явно добавлять обязательные поля, даже если их обнулить. Это теряется при использовании каскадного свободного конструктора. То же самое он потерял с сеттерами. Я хочу, чтобы разработчик не строил до тех пор, пока не будет добавлено каждое обязательное поле.

В отличие от многих шаблонов построителя, то, что Im после не является неизменяемым. Я покидаю класс, когда нашел его. Я хочу знать, что построенный объект находится в хорошем состоянии, просто посмотрев на код, который его создает. Без необходимости ссылаться на документацию. Это означает, что необходимо выполнить программист с помощью условно необходимых шагов.

Person makeHomer(PersonBuilder personBuilder){ //Injection avoids hardcoding implementation
    return personBuilder

         // -- These have good default values, may be skipped, and don't conflict -- //
        .doOptional()
            .addClothing("Pants!!")   //Could also call addTattoo() and 36 others

         // -- All fields that always must be set.  @NotNull might be handy. -- //
        .doRequired()                 //Forced to call the following in order
            .addFirstName("Homer")
            .addMiddleName("Jay")
            .addLastName("Simpson")
            .addNickName("Homie")
            .addMaidenName(null)      //Forced to explicitly set null, a good thing
            .addEyeColor("black")
            .addHairColor("brown")
            .addDateOfBirth(new Date(1))
            .addAliases(
                "Homer Thompson",
                "Pie Man",
                "Max Power",
                "El Homo",
                "Thad Supersperm",
                "Bald Mommy",
                "Rock Strongo",
                "Lance Uppercut",
                "Mr. Plow")

         // -- Controls alternatives for setters and the choice of constructors -- //
        .doAlternatives()           //Either x or y. a, b, or c. etc.
            .addBeersToday(3)       //Now can't call addHowDrunk("Hammered"); 
            .addFavoriteBeer("Duff")//Now can’t call addJobTitle("Safety Inspector");  

        .doBuild()                  //Not available until now
        ;
}   

Человек может быть создан после addBeersToday(), поскольку в этот момент вся информация о конструкторе известна, но не возвращается до doBuild().

public Person(String firstName, String middleName, String lastName,
               String nickName, String maidenName, String eyeColor, 
               String hairColor, Date dateOfBirth, int beersToday, 
               String[] aliases);

public Person(String firstName, String middleName, String lastName,
               String nickName, String maidenName, String eyeColor, 
               String hairColor, Date dateOfBirth, String howDrunk,
               String[] aliases);

Эти параметры задают поля, которые никогда не должны оставляться со значениями по умолчанию. beersToday и howDrunk устанавливают одно и то же поле разными способами. favoriteBeer и jobTitle - это разные поля, но вызывают конфликты с тем, как используется класс, поэтому нужно установить только один. Они обрабатываются сеттерами, не являющимися конструкторами.

Метод doBuild() возвращает объект Person. Это единственный, который делает, и Person - единственный тип, который он вернет. Когда он Person полностью инициализируется.

На каждом шаге интерфейса возвращаемый тип не всегда одинаковый. Изменение типа - это то, как разработчик руководствуется этими шагами. Он предлагает только действующие методы. Метод doBuild() не доступен, пока все необходимые шаги не будут завершены.

Префиксы do/add - это kludge, чтобы упростить запись, поскольку изменяющийся тип возвращаемого значения несоответствие с назначением и делает рекомендации по интеллекту в алфавитном порядке в затмении. Я подтвердил, что у intellij нет этой проблемы. Спасибо Нимхимпски.

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

Если вы предложите альтернативный шаблон, пожалуйста, покажите его интерфейс. Используйте все входы из примера.

Если вы предлагаете использовать представленный здесь интерфейс или некоторые незначительные вариации, пожалуйста, защитите его от критики, например this.

То, что я действительно хочу знать, - это то, что большинство людей предпочтут использовать этот интерфейс для создания или другого. Это вопрос о человеческом интерфейсе. Это нарушает PoLA? Не беспокойтесь о том, как сложно было бы реализовать.

Однако, если вам интересны реализации:

Произошла неудачная попытка (не было достаточного количества состояний или было понятно, что vs не был установлен по умолчанию)

Реализация построителя шагов (недостаточно гибкая для нескольких конструкторов или альтернатив)

Усовершенствованный построитель (неподвижный лайнер, но имеет гибкие состояния)

Wizard builder (работает с forking, но не помнит путь для выбора конструктора)

Требование:

  • Класс монстра (человека) уже закрыт для модификации и расширения; не обидчивый

Цели:

  • Скрыть длинные конструкторы, так как класс монстра имеет 10 обязательных параметров
  • Определите, какой конструктор должен вызывать на основе используемых альтернатив
  • Запретить конфликтующие сеттеры
  • Принудительные правила во время компиляции

Цель:

  • Четко сигнализировать, когда значения по умолчанию неприемлемы
4b9b3361

Ответ 1

статический внутренний строитель, прославленный josh bloch в эффективной java.

Обязательные параметры - конструкторы args, необязательные - методы.

Пример. Вызов, в котором требуется только имя пользователя:

RegisterUserDto myDto = RegisterUserDto.Builder(myUsername).password(mypassword).email(myemail).Build();

И базовый код (опуская очевидные экземпляры vars):

private RegisterUserDTO(final Builder builder) {
        super();
        this.username = builder.username;
        this.firstName = builder.firstName;
        this.surname = builder.surname;
        this.password = builder.password;
        this.confirmPassword = builder.confirmPassword;
    }


    public static class Builder {
        private final String username;

        private String firstName;

        private String surname;

        private String password;

        private String confirmPassword;

        public Builder(final String username) {
            super();
            this.username = username;
        }

        public Builder firstname(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder surname(final String surname) {
            this.surname = surname;
            return this;
        }

        public Builder password(final String password) {
            this.password = password;
            return this;
        }

        public Builder confirmPassword(final String confirmPassword) {
            this.confirmPassword = confirmPassword;
            return this;
        }

        public RegisterUserDTO build() {
            return new RegisterUserDTO(this);
        }
    }

Ответ 2

Таким образом, статический внутренний строитель в сочетании с функцией factory может выполнять некоторые из необходимых действий. (1) Он может применять зависимости типа, если установлен A, а также B. (2) Он может возвращать разные классы. (3) Он может выполнять логическую проверку записей.

Однако он все равно будет терпеть неудачу, если программисты войдут в неправильные поля.

Возможным преимуществом является шаблон "несколько строителей". Если клиент заранее знает, почему он создает конкретный элемент, тогда он может получить другой строитель. Вы можете создать конструктор для каждой комбинации.

В зависимости от типа логических зависимостей в вашем классе вы можете объединить эти несколько разработчиков с одним общим застройщиком. Вы можете иметь, скажем, общий строитель, и когда вы вызываете setOption (A) в общем построителе, он возвращает другой класс строителя, к которому вы можете только привязать методы, которые по-прежнему актуальны. Таким образом, вы получаете свободное владение, но вы можете исключить некоторые пути. Когда вы это делаете, вы должны быть осторожны, чтобы аннулировать поля, которые были установлены, но стали неуместными - вы не можете сделать подструктуры строителей друг другом.

Это может заставить клиента выбрать во время компиляции, как построить объект, это то, что вы после?

UPDATE - попытался ответить на комментарии:

Итак, сначала сначала - функция factory является первой в Joshua Blocks Эффективная Java, это просто означает, что для объекта вы делаете его конструктором частным, а вместо этого выполняете статическую функцию factory. Это более гибко, чем конструктор, потому что он может возвращать разные типы объектов. Когда вы объединяете функцию factory с несколькими сборщиками, вы можете получить очень мощную комбинацию. Вот описание шаблона: http://en.wikipedia.org/wiki/Factory_method_pattern http://en.wikipedia.org/wiki/Abstract_factory_pattern

Итак, представьте, что вы хотите создавать объекты, которые описывают человека и его работу, но когда вы укажете свое задание, вы хотите иметь список определенных подпараметров.

public class outer{
    Map <Map<Class<?>, Object> jobsMap - holds a class object of the new builder class, and a prototypical isntance which can be cloned.
    outer(){
        jobsMap.add(Mechanic.class, new Mechanic());
        //add other classes to the map
    }

    public class GeneralBuilder{
    String name;
    int age;

//constructor enforces mandatory parameters.
    GeneralBuilder(String name, int age, \\other mandatory paramerters){
        //set params
    }

    public T setJob(Class<T extends AbstractJob> job){
        AbstractJob result = super.jobsMap.getValue(job).clone();
        //set General Builder parameters name, age, etc
        return (T) result;
    }
}

public MechanicBuilder extends AbstractJobs{
//specific properties
    MechanicBuilder(GeneralBuilder){
      // set age, name, other General Builder properties
    }
    //setters for specific properties return this
    public Person build(){
        //check mechanic mandatory parameters filled, else throw exception.
        return Person.factory(this);
    }
}

public abstract class AbstractJob{
    String name;
    String age;
    //setters;
}

public class Person {
//a big class with lots of options
//private constructor
    public static Person Factory(Mechanic mechanic){
        //set relevant person options
    }
}

Итак, теперь это бегло. Я создаю экземпляр внешнего и заполняю карту всеми конкретными типами заданий. Затем я могу создать из этого столько новых строителей, сколько хочу, как экземпляров внутреннего класса. Я устанавливаю параметры для общего конструктора, вызывающего .setJobs(Mechanic.class), и возвращает экземпляр механика, который имеет кучу определенных свойств, которые я теперь могу свободно использовать с помощью .setOptionA() и т.д. В конце концов я вызываю build и это вызывает статический метод factory в классе Person и передает себя. Вы возвращаетесь к классу людей.

Его большая реализация, так как вам нужно создать конкретный класс строителя для каждого "типа" объекта, который может быть представлен классом Person, но он делает API, который очень удобен для клиента. На практике, хотя у этих классов есть много вариантов, на практике может быть только несколько шаблонов, которые люди намереваются создать, а все остальное появляется только случайно.

Ответ 3

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

Рефакторинг для конструктора с объектами параметров также может быть выполнен с поддержкой IDE и поэтому не так много работает. Таким образом, вы также можете реорганизовать существующий код. Чем вы все еще можете создавать сборщики для объектов параметров и класса, о которых идет речь, если они все еще необходимы.

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

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

Ответ 4

Значит, у меня было несколько лет, чтобы подумать об этом, и я думаю, что теперь я знаю правильный путь для этого.

Во-первых: реализовать каждый обязательный параметр как один интерфейс метода, который возвращает следующий интерфейс одного метода. Это заставляет клиента заполнять все требуемые параметры и имеет дополнительную выгоду, что все они должны быть заполнены в том же порядке везде в коде, что облегчает обнаружение ошибок.

Во-вторых: реализуйте все автономные необязательные параметры в одном интерфейсе, который является типом возврата окончательного параметра.

В-третьих: для любой сложной подгруппы необязательных параметров создайте больше интерфейсов, которые вынуждают выбор маршрутов идти вниз.

interface FirstName {

     public LastName setFirstName(String name)

}
interface LastName {
    public OptionalInterface setLastName(String name)
}

interface OptionalParams {
    public OptionalParams setOptionalParam(String numberOfBananas)
    public OptionalParams setOptionalParam(int numberOfApples)
    public AlcoholLevel setAlcoholLevel() // go down to a new interface
    public MaritalStatus setMaritalStatus()

    public Person build()
}

interface AlcoholLevel {
    //mutually exclusive options hoist you back to options
    public OptionalParams setNumberOfBeers(int bottles)
    public OptionalParams setBottlesOfWine(int wine)
    public OptionalParams setShots(int shots)
    public OptionalParams isHammered()
    public OptionalParams isTeeTotal()
    public OptionalParams isTipsy()
}

interface MaritalStatus {
    public OptionalParams isUnmarried()
    //force you into a required params loop
    public HusbandFirstName hasHusband()
    public WifeFirstName hasWife()
}

Имея серию интерфейсов одного метода, вы можете в значительной степени заставить хорошее поведение на клиенте. Этот шаблон хорошо работает, например, с формированием сформированных HTTP-запросов в сетях, где требуются определенные аутентификации. Наложение интерфейсов поверх стандартной библиотеки httml направляет клиентов в правильном направлении.

Некоторая условная логика в основном слишком сложна, чтобы ее стоило. Такие вещи, как настаивать на том, что сумма параметра 1 и параметра 2 меньше, чем параметр 3, лучше всего решать, бросая исключение во время выполнения метода сборки.