Как правильно реализовать стратегию проектирования шаблона - PullRequest
3 голосов
/ 07 февраля 2020

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

Допустим, у меня есть класс FormBuilder, который использует стратегию из списка ниже для построения формы:

  • SimpleFormStrategy
  • ExtendedFormStrategy
  • CustomFormStrategy

Итак, вопросы:

  1. Правильно ли выбирать стратегию внутри FormBuilder, а не передавать стратегию извне?
  2. Разве это не нарушает принцип открытого закрытия? Поэтому, если я хочу добавить еще одну стратегию формы или удалить существующую, мне нужно отредактировать класс FormBuilder.

Пример кода проекта

class Form {
    // Form data here
}

interface IFormStrategy {
    execute(params: object): Form;
}

class SimpleFormStrategy implements IFormStrategy {
    public execute(params: object): Form {
        // Here comes logics for building simple form
        return new Form();
    }
}

class ExtendedFormStrategy implements IFormStrategy {
    public execute(params: object): Form {
        // Here comes logics for building extended form
        return new Form();
    }
}

class CustomFormStrategy implements IFormStrategy {
    public execute(params: object): Form {
        // Here comes logics for building custom form
        return new Form();
    }
}

class FormBuilder {
    public build(params: object): Form {
        let strategy: IFormStrategy;

        // Here comes strategy selection logics based on params

        // If it should be simple form (based on params)
        strategy = new SimpleFormStrategy();
        // If it should be extended form (based on params)
        strategy = new ExtendedFormStrategy();
        // If it should be custom form (based on params)
        strategy = new CustomFormStrategy();

        return strategy.execute(params);
    }
}

Ответы [ 3 ]

1 голос
/ 07 февраля 2020

В терминах шаблона проектирования для Стратегии ваш FormBuilder играет роль контекста, который содержит ссылку на текущую используемую стратегию (IFormStragegy). Стратегия передается извне (с использованием setter), поэтому она открыта для расширения (OCP). Что касается ваших вопросов:

  1. Правильно ли выбирать стратегию внутри FormBuilder, а не передавать стратегию извне?

Это не правильная реализация стратегии. Вы должны создать экземпляры своей стратегии и передать ее контексту. Поэтому стратегию можно менять во время выполнения.

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

Да, это так, вы не можете сделать новую стратегию известной FormBuilder без изменив его.

Вы можете посмотреть здесь в качестве примера.

FormBuilder context = new FormBuilder();
IFormStrategy simple = new SimpleFormStrategy();
IFormStrategy extended = new ExtendedFormStrategy();
IFormStrategy custom = new CustomFormStrategy();

context.setStrategy(simple);
context.build(/* parameters */)

context.setStrategy(custom);
context.build(/* parameters */)
1 голос
/ 08 февраля 2020

Вы задали 2 вопроса, которые не связаны напрямую с TypeScript. Код можно напрямую преобразовать в C# / Java, обычные основные языки OOP. Это еще более интересно, потому что речь идет о шаблонах проектирования и SOLID принципах, обеих столпах объектно-ориентированного программирования .

Давайте ответим прежде чем быть более общим:

  1. Правильно ли выбирать стратегию внутри FormBuilder, а не передавать стратегию извне?

Да , Противоположность приводит к обертке FormFactory без особого интереса. Почему бы не позвонить напрямую strategy.execute()?

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

Builders и Фабрики тесно связаны с созданными типами. Это локальное нарушение OCP, но с ними клиентский код отделен от деталей реализации создания формы.

Mis c comments

  • Шаблоны проектирования
    • Каждый шаблон проектирования должен быть найден из контекста (клиентский код и даже бизнес-область), а не заранее. Шаблоны проектирования редко используются в книге, но должны быть адаптированы к контексту и могут быть смешаны вместе.
    • IFormStrategy - это, во-первых, (абстрактный) Factory : он создает Form. Лучшее имя должно быть IFormFactory { create(...): Form; } (или просто FormFactory, при этом префикс «I» чаще встречается в C#, чем в TypeScript). Это Стратегия для FormBuilder, но не по сути. Кстати, термин Стратегия редко используется при именовании классов, потому что он слишком обобщенный c. Лучше использовать более конкретный / явный термин.
    • FormBuilder не совсем Builder , который должен создавать объект по частям, обычно с плавным API, таким как formBuilder.withPartA().withPartB().build();. Этот класс выбирает соответствующий Фабрика / Стратегия на основе входных параметров. Это Выбор стратегии или Фабрика Фабрики : D, которая также вызывает фабрику для окончательного создания Form. Может быть, это слишком много: достаточно выбрать фабрику. Может быть, это уместно, скрывая сложность от клиентского кода.
  • OOP + Шаблоны проектирования и функциональное программирование в TypeScript
    • Стратегии на функциональном языке - это просто функции. TypeScript позволяет определять функции более высокого порядка с interface / type, но без обертывающего объекта / класса, который может не принести больше значения. Код клиента просто должен передать другую функцию, которая может быть «простой лямбда» (жирная стрелка).
  • Mis c
    • params аргумент используется как Builder , так и Factory . Вероятно, было бы лучше разделить его, чтобы избежать смешивания различных проблем: выбор стратегии и создание формы .
    • Если тип формы для создания (Простой, Extended, Custom) не является Dynami c, но уже известен со стороны клиентского кода, может быть лучше предложить непосредственно 3 метода с указанными c аргументами: createSimpleForm(simpleFormArgs), createExtendedForm(extendsFormArgs) ... Каждый метод будет создавать экземпляр связанную фабрику и назовите ее create(formArgs) метод. Таким образом, нет необходимости в сложном алгоритме выбора стратегии, основанном на if с или switch с, который увеличивает Cyclomati c Сложность . Вызов каждого createXxxForm метода также будет проще, аргумент объекта будет меньше.
0 голосов
/ 07 февраля 2020

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

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

Примеры использования: Шаблон стратегии имеет вид очень часто встречается в коде TypeScript. Он часто используется в различных средах, чтобы предоставить пользователям способ изменить поведение класса, не расширяя его.

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

Концептуальный пример Этот пример иллюстрирует структуру шаблона разработки Стратегии. Он сфокусирован на ответе на следующие вопросы: • Из каких классов он состоит? • Какие роли играют эти классы? • Каким образом связаны элементы шаблона?

index.ts: Концептуальный пример

/**
     * The Context defines the interface of interest to clients.
     */
    class Context {
        /**
         * @type {Strategy} The Context maintains a reference to one of the Strategy
         * objects. The Context does not know the concrete class of a strategy. It
         * should work with all strategies via the Strategy interface.
         */
        private strategy: Strategy;

        /**
         * Usually, the Context accepts a strategy through the constructor, but also
         * provides a setter to change it at runtime.
         */
        constructor(strategy: Strategy) {
            this.strategy = strategy;
        }

        /**
         * Usually, the Context allows replacing a Strategy object at runtime.
         */
        public setStrategy(strategy: Strategy) {
            this.strategy = strategy;
        }

        /**
         * The Context delegates some work to the Strategy object instead of
         * implementing multiple versions of the algorithm on its own.
         */
        public doSomeBusinessLogic(): void {
            // ...

            console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
            const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
            console.log(result.join(','));

            // ...
        }
    }

    /**
     * The Strategy interface declares operations common to all supported versions
     * of some algorithm.
     *
     * The Context uses this interface to call the algorithm defined by Concrete
     * Strategies.
     */
    interface Strategy {
        doAlgorithm(data: string[]): string[];
    }

    /**
     * Concrete Strategies implement the algorithm while following the base Strategy
     * interface. The interface makes them interchangeable in the Context.
     */
    class ConcreteStrategyA implements Strategy {
        public doAlgorithm(data: string[]): string[] {
            return data.sort();
        }
    }

    class ConcreteStrategyB implements Strategy {
        public doAlgorithm(data: string[]): string[] {
            return data.reverse();
        }
    }

    /**
     * The client code picks a concrete strategy and passes it to the context. The
     * client should be aware of the differences between strategies in order to make
     * the right choice.
     */
    const context = new Context(new ConcreteStrategyA());
    console.log('Client: Strategy is set to normal sorting.');
    context.doSomeBusinessLogic();

    console.log('');

    console.log('Client: Strategy is set to reverse sorting.');
    context.setStrategy(new ConcreteStrategyB());
    context.doSomeBusinessLogic();

Output.txt: Результат выполнения

Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e

Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
...