Хорошая цепочка строителя - PullRequest
2 голосов
/ 20 января 2020

у меня есть сложный бизнес-объект, подобный этому

public class BusinessObject {

    public Team1Object object1;
    public Team2Object object2;
    public String debug;
    ...

}

Я использую небольшую модификацию схемы цепочки ответственности для построения вышеуказанного объекта.

Вот мой интерфейс

public interface BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext);

}

Теперь команды могут приходить и писать свои приложения. вот так

public class Team1ObjectAppender implements BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext) {
        Team1Object object1 = somehowComputeObject1();
        businessObject.object1(object1)
    }
}
public class Team2Appender implements BusinessObjectAppender {

    public void append(BusinessObjectBuilder businessObject, Context someApplicationContext) {
        Team2Object object2 = somehowComputeObject2();
        businessObject.object2(object2)
    }
}

При использовании этого подхода, при построении сложных объектов лог c не раздувается.

Но у него также есть проблемы типа

  1. В Team1 нет ограждений, которые могут испортить объект другой команды или зависеть от данных другой команды. Помимо проверок кода.

  2. Случаи, когда BusinessObject является полиморфным c, после того, как я создал конструктор типа 1, его невозможно изменить в дополнениях.

Вопрос

  1. Правильно ли это сделать?

  2. Каковы другие способы добиться того же? (создание сложных объектов в масштабируемом, понятном виде)

1 Ответ

1 голос
/ 20 января 2020

Если вы планируете использовать шаблон построителя, то после разделения задач я бы предпочел использовать отдельный класс для объекта BusinessObject, используя объект шаблона BusinessObjectBuilder построителя. Чтобы получить доступ к объекту шаблона компоновщика из соответствующего домена / бизнес-объекта, вы можете (необязательно, и я бы порекомендовал при необходимости) добавить метод public static create() для создания экземпляра как объекта компоновщика, так и инкапсулированного бизнес-объекта для компоновки. , Лично я предпочитаю свободный стиль объектов-строителей, так как методы могут быть объединены в цепочку, и это значительно облегчает написание кода.

Поскольку вас беспокоит сложность построения поля Team1Object и поля Team2Object как отдельных В связи с этим я бы подумал, что вы ищете не плоский строительный шаблон, а фасады строительного шаблона или фасады строителей. Чтобы использовать фасады компоновщика, вы должны использовать общий базовый класс компоновщика и классы фасада компоновщика, производные от базового класса.

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

Как во всех классах Fluent Builder тип возврата такой же, как и в классе Fluent Builder (или Fluent Builder). Рассмотрим следующие модификации:

public class BusinessObject {
    internal BusinessObject() {
        // Default constructor should exist
        // but only needs to be visible at the
        // BusinessObjectBuilder scope.
        // use whatever access modifier you would
        // prefer, however, based on needs,
        // internal or public is appropriate.
        // in C++, use `friend BusinessObjectBuilder`
    }
    public Team1Object object1;
    public Team2Object object2;
    public String debug;
    ...
    public static BusinessObjectBuilder create() {
        return new BusinessObjectBuilder();
    }
}

public class BusinessObjectBuilder {
    protected BusinessObject bObject; // the object we will build
    internal BusinessObjectBuilder() {
        // A default constructor, again minimally visible
        // to BusinessObject; internal or public is good here.
        // Needs to create a basic BusinessObject.
        bObject = new BusinessObject();
    }
    public BusinessObjectBuilder debug(String debugString) {
        // Sets BusinessObject.debug
        this.bObject.debug += debugString + "\n";
        // Then returns the BusinessObjectBuilder.
        // Every method should return this except the facade methods
        // and the build method.
        return this;
    }
    public Team1ObjectBuilder team1Object() {
        // Returns the Team1Object builder facet.
        return new Team1ObjectBuilder(this.bObject);
    }
    public Team2ObjectBuilder team2Object() {
        // Returns the Team2Object builder facet.
        return new Team1ObjectBuilder(this.bObject);
    }
    public BusinessObject build() {
        // Technically, it's already built at this point. Return it.
        return this.bObject;
    }
}

public class Team1ObjectBuilder extends BusinessObjectBuilder {
    private BusinessObject bObject; // the object we will build
    internal Team1ObjectBuilder(BusinessObject bObject) {
        // This time we copy the object we were building
        this.bObject = bObject;
    }
    private Team1Object somehowComputeObject1() {
        // pour on the magic
        return new Team1Object();
    }
    public Team1ObjectBuilder append(Context someApplicationContext) {
        this.bObject.object1 = somehowComputeObject1();
    }
}

public class Team2ObjectBuilder extends BusinessObjectBuilder {
    private BusinessObject bObject; // the object we will build
    internal Team2ObjectBuilder(BusinessObject bObject) {
        // Again we copy the object we were building
        this.bObject = bObject;
    }
    private Team2Object somehowComputeObject2() {
        // pour on the magic
        return new Team2Object();
    }
    public Team2ObjectBuilder append(Context someApplicationContext) {
        this.bObject.object2 = somehowComputeObject2();
    }
}

Если вы используете этого бегущего строителя с беглым рисунком фасада, вы можете использовать его так:

BusinessObject complexBusinessObject = BusinessObject.create()
                                                     .debug("Let's build team1Object.")
                                                     .team1Object().append( /* someApplicationContext */)
                                                     .debug("Let's build team2Object.")
                                                     .team2Object().append( /* someApplicationContext */)
                                                     .debug("All done.")
                                                     .build();

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

Вы упомянули цепочку ответственности. Этот шаблон используется, когда цепочка компонентов получает по очереди (в цепочке) для обработки команды / запроса и при необходимости останавливает выполнение цепочки.

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

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

Чтобы использовать шаблон цепочки ответственности, вам понадобятся BusinessObject, а также один или несколько BusinessObjectModifier классов. Поскольку область действия здесь ограничена объектами Team1Appender и Team2Appender, мы будем использовать эти два в качестве ссылки.

Чтобы построить цепочку, вы можете захотеть использовать базовый класс для * Поле 1030 * для следующего звена в цепочке и метод add() для передачи к следующему ответственному звену в цепочке.

Рассмотрим следующий шаблон цепочки ответственности:

    public class BusinessObject {
        public Team1Object object1;
        public Team2Object object2;
        public String debug;
        ...
    }

    public abstract class BusinessObjectAppender { // provides shared append() modifier
        protected BusinessObjectAppender next = null;
        public void add(BusinessObjectAppender boa) {
            if (this.next == null) {
                this.next = boa;
            }
            else {
                next.add(boa); // recursive call to the end of the linked list "chain"
            }
        }
        public abstract void append(BusinessObject businessObject, Context someApplicationContext);
    }

    public class Team1ObjectAppender extends BusinessObjectAppender {
        public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
            Team1Object object1 = somehowComputeObject1();
            businessObject.object1 = object1;
            if (this.next == null) {
                return businessObject; // you have to since you can't pass by ref/out in java
            }
            else {
                return next.append(businessObject, someApplicationContext);
            }
        }
    }

    public class Team2ObjectAppender extends BusinessObjectAppender {
        public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
            Team2Object object2 = somehowComputeObject2();
            businessObject.object2 = object2;
            if (this.next == null) {
                return businessObject; // you have to since you can't pass by ref/out in java
            }
            else {
                return next.append(businessObject, someApplicationContext);
            }
        }
    }

Теперь это должно настроить цепочку. Чтобы использовать его, вы можете сделать что-то вроде:

BusinessObject businessObject = new BusinessObject();
BusinessObjectAppender appendChain = new Team1ObjectAppender();
appendChain.add(new Team2ObjectAppender()); // start --> team1 --> team2 --> done
businessObject = appendChain(businessObject, /*someApplicationContext*/);

Решает ли это вашу проблему? Если у вас есть цепочка ответственности, тогда, возможно.

Я вижу, что ваша оригинальная спецификация использовала строителя в качестве субъекта для передачи по цепочке вместо конечного объекта. Это интересное пересечение двух шаблонов.

Если вы хотите использовать конструктор, но затем построить объект с использованием метода цепочки ответственности, вы можете рассмотреть что-то вроде:

public class BusinessObject {
    internal BusinessObject() {
        // Default constructor should exist
        // but only needs to be visible at the
        // BusinessObjectBuilder scope.
        // use whatever access modifier you would
        // prefer, however, based on needs,
        // internal or public is appropriate.
        // in C++, use `friend BusinessObjectBuilder`
    }
    public Team1Object object1;
    public Team2Object object2;
    public String debug;
    ...
    public static BusinessObjectBuilder create() {
        return new BusinessObjectBuilder();
    }
}

public abstract class BusinessObjectAppender { // provides shared append() modifier
    protected BusinessObjectAppender next = null;
    public void add(BusinessObjectAppender boa) {
        if (this.next == null) {
            this.next = boa;
        }
        else {
            next.add(boa); // recursive call to the end of the linked list "chain"
        }
    }
    public abstract void append(BusinessObject businessObject, Context someApplicationContext);
}

public class Team1ObjectAppender extends BusinessObjectAppender {
    public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
        Team1Object object1 = somehowComputeObject1();
        businessObject.object1 = object1;
        if (this.next == null) {
            return businessObject; // you have to since you can't pass by ref/out in java
        }
        else {
            return next.append(businessObject, someApplicationContext);
        }
    }
}

public class Team2ObjectAppender extends BusinessObjectAppender {
    public BusinessObject append(BusinessObject businessObject, Context someApplicationContext) {
        Team2Object object2 = somehowComputeObject2();
        businessObject.object2 = object2;
        if (this.next == null) {
            return businessObject; // you have to since you can't pass by ref/out in java
        }
        else {
            return next.append(businessObject, someApplicationContext);
        }
    }
}

public class BusinessObjectBuilder {
    protected BusinessObject bObject; // the object we will build
    internal BusinessObjectBuilder() {
        // A default constructor, again minimally visible
        // to BusinessObject; internal or public is good here.
        // Needs to create a basic BusinessObject.
        bObject = new BusinessObject();
    }
    public BusinessObjectBuilder debug(String debugString) {
        // Sets BusinessObject.debug
        this.bObject.debug += debugString + "\n";
        // Then returns the BusinessObjectBuilder.
        // Every method should return this except the facade methods
        // and the build method.
        return this;
    }
    public BusinessObjectBuilder append(Context someApplicationContext) {
        // Create the chain
        BusinessObjectAppender appendChain = new Team1ObjectAppender();
        appendChain.add(new Team2ObjectAppender()); // start --> team1 --> team2 --> done
        this.bObject = appendChain(this.bObject, someApplicationContext);
        // Return the Builder.
        return this;
    }
    public BusinessObject build() {
        // Technically, it's already built at this point. Return it.
        return this.bObject;
    }
}

А затем используйте это так:

BusinessObject complexBusinessObject = BusinessObject.create()
                                                     .debug("Run through the chain of responsibilities.")
                                                     .append( /* someApplicationContext */)
                                                     .debug("All done.")
                                                     .build();

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

Я бы, конечно, хотел бы ответить на ваши вопросы.

  1. Это правильный шаблон? Это зависит от того, что вам нужно.

Цепочка ответственности состоит из источника команды (в данном случае append() блок вызывающего), который обрабатывает команду (append) через каждый обрабатываемый объект в пределах одиночно связанный список последовательно обрабатываемых последовательностей объектов обработки (BusinessObjectAppender объектов).

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

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

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

Например, способ, которым автомобиль представлен водителю, покупателю или продавцу, скорее всего, не использует те же интерфейсы, которые были использованы для создания его на заводе. Конечно, у него будут марка, модель и год, все равно. Но заказчик не беспокоится о стоимости деталей, времени, необходимом для сборки, результатах испытаний различных систем, которые были задействованы сотрудниками в дни его создания. Но, конечно же, через 6,5 секунды он становится 0-60 и окрашивается в красный цвет.

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

И шаблон строителя, и шаблон цепочки ответственности являются частью оригинальных шаблонов проектирования «Банды четырех».

Где они отличается это шаблон Builder - это шаблон Creation, а Chain of Responsibility - поведенческий шаблон.

Я не ставлю перед собой цель переписать книгу, поэтому я мог бы просто отослать вас к заголовку «Шаблоны проектирования: элементы многоразового использования». «Объектно-ориентированное программное обеспечение» (1994. Гамма, Эрих; Хелм, Ричард; Джонсон, Ральф; и Флиссидес, Джон.), Если вы хотите, чтобы один из их шаблонов соответствовал вашим собственным потребностям. Поскольку вы не объяснили цель команды 1 и команды 2, я не могу решить для вас, что лучше.

Каковы другие способы?

Банда четырех предоставила несколько других образцов творчества и поведения.

Если цепь ответственности не решит вашу проблему, тогда Шаблон команды мог бы. (Это почти то же самое, что сделал для вас абстрактный метод BusinessObjectAppender.append(), за исключением цепочки; поскольку append() примерно execute() однажды реализовано.)

Если вам нужно выполнить одну и ту же Команду для той же темы через несколько (1 ... n) процессов, но если процессы не связаны друг с другом в цепочке ответственности и не требуют определенного порядка, тогда простой итератор подойдет. К счастью, Java предоставляет множество возможностей, которые очень легко повторяемы. Рассмотрим ArrayList<Appender> appenders.

Есть много, много вариантов на выбор. Иногда вы можете смешивать их.

Я фактически выбрал класс шаблонов проектирования для Udemy, специально для C ++, но есть много мест в Интернете, где вы можете найти эту информацию. Это выглядит как хороший источник сводок, особенно потому, что примеры приведены в Java и предлагают альтернативы выбранным мною вариантам дизайна, давая вам несколько дополнительных примеров: JournalDev .

Надеемся , это помогает направить вас в правильном направлении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...