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

How to correctly implement strategy design pattern

我正在尝试实施策略设计模式,想知道我是否做得正确。

比方说,我有 class FormBuilder,它使用下面列表中的策略来构建表单:

所以问题是:

  1. select 策略在 FormBuilder 内部而不是从外部传递策略是否正确?
  2. 这不是违反了开闭原则吗?因此,如果我想再添加一种表单策略或删除现有的策略,我必须编辑 FormBuilder class.

代码示例草稿

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);
    }
}

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

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

用法示例:策略模式在 TypeScript 代码中非常常见。它经常用于各种框架中,为用户提供一种无需扩展 class 即可更改其行为的方法。

标识: 策略模式可以通过让嵌套对象完成实际工作的方法来识别,以及 setter 允许用不同的。

概念示例 这个例子说明了策略设计模式的结构。它着重于回答这些问题: • 它包含哪些classes? • 这些classes 扮演什么角色? • 模式的元素以何种方式相关?

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

在 Strategy 的设计模式术语中,您的 FormBuilder 扮演着 Context 的角色,它持有对当前使用的策略的引用 (IFormStragegy)。该策略是从外部传递的(使用setter),因此它对扩展(OCP)是开放的。所以关于你的问题:

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

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

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

是的,您不能在不更改新策略的情况下让 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 */)

您问了 2 个与 TypeScript 没有直接关系的问题。代码可以直接转换成C#/Java,通常主流的OOP语言。它更有趣,因为它涉及 设计模式SOLID 原则,它们都是 面向对象编程 的支柱].

在更笼统之前先具体回答一下:

  1. Is it correct to select strategy inside FormBuilder, and not passing strategy from outside?

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

  1. Doesn't this violates open closed principle? So, if I want to add one more form strategy or to remove an existing one, I have to edit the FormBuilder class.

BuildersFactories 在设计上与底层创建的类型紧密耦合。这是对 OCP 的局部违反,但客户端代码与表单创建实现细节分离。

杂项评论

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