如何正确实施策略设计模式

时间:2020-02-07 05:45:52

标签: typescript design-patterns strategy-pattern

我正在尝试实施策略设计模式,并且想知道我是否正确执行了该方法。

可以说,我有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 个答案:

答案 0 :(得分:1)

在策略的设计模式术语中,您的FormBuilder充当上下文的角色,该上下文持有对所使用的当前策略的引用(IFormStragegy)。该策略是从外部(使用setter)传递的,因此可以扩展(OCP)。因此,关于您的问题:

  1. FormBuilder内部选择策略,而不是从外部传递策略是否正确?

这不是正确执行策略。您应该创建策略的实例并将其传递给上下文。因此,可以在运行时交换该策略。

  1. 这不违反开放封闭原则吗?因此,如果我想再添加一个表单策略或删除现有的表单策略,则必须编辑FormBuilder类。

是的,如果不进行更改,您将无法使FormBuilder知道新的策略。

您可以查看here作为示例。

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 :(得分:1)

您问了2个未直接链接到TypeScript的问题。该代码可以直接转换为常用的主流OOP语言C#/ Java。更加有趣,因为它涉及到设计模式 SOLID 原理,这是面向对象编程的两个支柱。

在更笼统之前让我们具体回答一下:

  1. FormBuilder内部选择策略,而不是从外部传递策略是否正确?

是的。相反的情况导致FormFactory包装器没有太大的兴趣。为什么不直接致电strategy.execute()

  1. 这不违反开放封闭原则吗?因此,如果我想再添加一种表单策略或删除现有的表单策略,则必须编辑FormBuilder类。

Builders Factories 通过设计紧密地与下面创建的类型耦合。这是对OCP的局部违反,但是使用它们,客户端代码就与表单创建实现细节分离了。

其他评论

  • 设计模式
    • 必须从上下文(客户端代码,甚至是业务域)中找到每个设计模式,而不是从头开始。该书很少使用设计模式,但必须进行调整以适合上下文,并且可以混合在一起。
    • IFormStrategy首先是一个(抽象的) Factory :它创建一个Form。更好的名称应该是IFormFactory { create(...): Form; }(或者仅仅是FormFactory,“ I”前缀在C#中比在TypeScript中更常见)。这是FormBuilder策略,但不是本质上的。顺便说一句,在命名类时很少使用 Strategy 策略,因为它太通用了。最好使用更明确/明确的术语。
    • FormBuilder并非完全是 Builder ,它应按部分创建对象,通常使用诸如formBuilder.withPartA().withPartB().build();之类的流畅API。此类根据输入参数选择适当的 Factory / Strategy 。这是策略选择器工厂工厂:D,它们也调用工厂以最终创建Form。也许做的太多:仅选择工厂就足够了。也许合适,从客户端代码中隐藏了复杂性。
  • OOP +设计模式与TypeScript中的函数式编程
      功能语言中的
    • 策略只是功能。 TypeScript允许使用interface / type定义高阶函数,但没有包装对象/ class可能不会带来更多价值。客户端代码只需传递另一个函数即可,该函数可以是“简单lambda”(胖箭头函数)。
  • 其他
    • params参数由 Builder Factories 使用。最好将其拆分,以避免混淆不同的关注点:策略选择表单创建
    • 如果要创建的表单类型(简单,扩展,自定义)不是动态的,但已从客户端代码端得知,则最好直接提供3个带有特定参数的方法:createSimpleForm(simpleFormArgs),{ {1}} ...每个方法将实例化关联的工厂,并将其称为createExtendedForm(extendsFormArgs)方法。这样,就不需要基于create(formArgs)if的复杂算法来选择策略,而这会增加环复杂性。调用每个switch方法也将更简单,而object参数则更少。

答案 2 :(得分:0)

策略是一种行为设计模式,它将一组行为转变为对象,并使它们在原始上下文对象内可互换。

原始对象(称为上下文)持有对策略对象的引用,并委托其执行行为。为了更改上下文执行其工作的方式,其他对象可以将当前链接的策略对象替换为另一个对象。

用法示例: 策略模式在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