简体   繁体   English

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

[英]How to correctly implement strategy design pattern

I'm trying to implement strategy design pattern, and want to know if I do it correctly.我正在尝试实施策略设计模式,并想知道我是否正确执行。

Lets say, I have class FormBuilder which uses strategy from list below to build the form:可以说,我有类FormBuilder ,它使用下面列表中的策略来构建表单:

  • SimpleFormStrategy
  • ExtendedFormStrategy
  • CustomFormStrategy

So the questions are:所以问题是:

  1. Is it correct to select strategy inside FormBuilder , and not passing strategy from outside?FormBuilder内部选择策略而不是从外部传递策略是否正确?
  2. 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.因此,如果我想再添加一种表单策略或删除现有的一种,我必须编辑FormBuilder类。

Draft code example草稿代码示例

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

In design pattern terms for Strategy, your FormBuilder plays the role of a Context, which holds the reference to the current strategy in use ( IFormStragegy ).在 Strategy 的设计模式术语中,您的 FormBuilder 扮演 Context 的角色,它保存对当前使用的策略 ( IFormStragegy ) 的引用。 The strategy is passed from outside (using setter ) so it is open to extension (OCP).该策略从外部传递(使用setter ),因此它对扩展(OCP)开放。 So regarding your questions:所以关于你的问题:

  1. Is it correct to select strategy inside FormBuilder , and not passing strategy from outside?FormBuilder内部选择策略而不是从外部传递策略是否正确?

It is not correct implementation of strategy.这不是战略的正确实施。 You should create instances of your strategy and pass it to the context.您应该创建策略的实例并将其传递给上下文。 The strategy therefore can be swapped at run-time.因此可以在运行时交换策略。

  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.因此,如果我想再添加一种表单策略或删除现有的一种,我必须编辑 FormBuilder 类。

Yes it does, you cannot make a new strategy known to the FormBuilder without changing it.是的,您不能在不更改它的情况下使 FormBuilder 知道新策略。

You can look here for an example.您可以在此处查看示例。

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 */)

You asked 2 questions that are not directly linked to TypeScript.您提出了 2 个与 TypeScript 没有直接关联的问题。 The code can be directly converted to C# / Java, the usual mainstream OOP languages.代码可以直接转换为C#/Java,通常的主流OOP语言。 It's even more interesting because it's about both Design Patterns and SOLID principles, both pillars of the Object Oriented Programming .它更有趣,因为它涉及设计模式SOLID原则,这两个都是面向对象编程的支柱。

Let's answer it specifically before being more general:在更笼统之前,让我们具体回答一下:

  1. Is it correct to select strategy inside FormBuilder , and not passing strategy from outside?FormBuilder内部选择策略而不是从外部传递策略是否正确?

Yes.是的。 The opposite leads to a FormFactory wrapper without much interest.相反会导致一个没有太多兴趣的FormFactory包装器。 Why not calling directly strategy.execute() ?为什么不直接调用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.因此,如果我想再添加一种表单策略或删除现有的一种,我必须编辑FormBuilder类。

Builders and Factories are tightly couple to the underneath created types by design. BuildersFactories通过设计与底层创建的类型紧密耦合。 It's a local violation of the OCP but with them the client code is decoupled from form creation implementation details.这是对 OCP 的局部违反,但有了它们,客户端代码与表单创建实现细节分离。

Misc comments杂项评论

  • Design patterns设计模式
    • Every design pattern must be found from the context (client code and even business domain) and not upfront.每个设计模式都必须从上下文(客户端代码甚至业务领域)中找到,而不是预先找到。 Design patterns are rarely used by the book but must be adapted to suit the context and can be mixed together.本书很少使用设计模式,但必须根据上下文进行调整,并且可以混合在一起。
    • The IFormStrategy is firstly an (abstract) Factory : it creates a Form . IFormStrategy首先是一个(抽象)工厂:它创建一个Form A better name should be IFormFactory { create(...): Form; }更好的名字应该是IFormFactory { create(...): Form; } IFormFactory { create(...): Form; } (or just FormFactory , the "I" prefix being more common in C# than in TypeScript). IFormFactory { create(...): Form; } (或只是FormFactory ,“我”的前缀是更多的C#常见比打字稿)。 It's a Strategy for the FormBuilder but not intrinsically.这是FormBuilder的一种策略,但不是本质上的。 By the way, the term Strategy is rarely used when naming classes, because it's too generic.顺便说一下,在命名类时很少使用术语策略,因为它太通用了。 Better use a more specific/explicit term.最好使用更具体/明确的术语。
    • The FormBuilder is not exactly a Builder which should create an object by parts, usually with a fluent API like formBuilder.withPartA().withPartB().build(); FormBuilder 不完全是一个应该按部分创建对象的构建器,通常使用像formBuilder.withPartA().withPartB().build();这样的流畅 API formBuilder.withPartA().withPartB().build(); . . This class selects the appropriate Factory/Strategy based on the input params.此类根据输入参数选择适当的工厂/策略 It's a Strategy Selector , or a Factory of Factory :D that also call the factory to finally create the Form .它是一个Strategy SelectorFactory of Factory :D ,它也调用工厂以最终创建Form Maybe it does too much: just selecting the factory would be enough.也许它做得太多了:只需选择工厂就足够了。 Maybe it's appropriate, hiding complexity from the client code.也许这是合适的,隐藏客户端代码的复杂性。
  • OOP + Design Patterns vs Functional Programming in TypeScript OOP + 设计模式与 TypeScript 中的函数式编程
    • Strategies in a functional language are just functions.函数式语言中的策略只是函数。 TypeScript allows to define higher order functions, with an interface / type but without a wrapping object /class that may not bring more value. TypeScript 允许定义更高阶的函数,带有interface / type但没有包装对象/类,这可能不会带来更多价值。 The client code just have to pass another function which can be a "simple lambda" (fat arrow function).客户端代码只需要传递另一个可以是“简单 lambda”(粗箭头函数)的函数。
  • Misc杂项
    • params argument is used by both the Builder and the Factories . params参数由BuilderFactories 使用 It would be probably better to split it, to avoid mixing distinct concerns: strategy selection and form creation .拆分它可能会更好,以避免混合不同的关注点:策略选择表单创建
    • If the kind of the form to create (Simple, Extended, Custom) is not dynamic but already known from the client code side, it may be better to offer directly 3 methods with specific arguments: createSimpleForm(simpleFormArgs) , createExtendedForm(extendsFormArgs) ... Each method will instanciate the associated factory and call it create(formArgs) method.如果要创建的表单类型(Simple、Extended、Custom)不是动态的,而是从客户端代码端已知的,最好直接提供 3 个带有特定参数的方法: createSimpleForm(simpleFormArgs)createExtendedForm(extendsFormArgs) 。 .. 每个方法都会实例化关联的工厂并调用它create(formArgs)方法。 This way, no need for a complex algorithm to select the strategy, based on if s or switch s which increases the Cyclomatic Complexity .这样,就不需要复杂的算法来选择策略,基于if s 或switch s 这会增加Cyclomatic Complexity Calling each createXxxForm method will also be simpler, the object argument being more little.调用每个createXxxForm方法也会更简单,对象参数createXxxForm

Strategy is a behavioral design pattern that turns a set of behaviors into objects and makes them interchangeable inside original context object.策略是一种行为设计模式,它将一组行为转换为对象,并使它们在原始上下文对象中可互换。

The original object, called context, holds a reference to a strategy object and delegates it executing the behavior.原始对象,称为上下文,持有对策略对象的引用,并委托它执行行为。 In order to change the way the context performs its work, other objects may replace the currently linked strategy object with another one.为了改变上下文执行其工作的方式,其他对象可以用另一个对象替换当前链接的策略对象。

Usage examples: The Strategy pattern is very common in TypeScript code.使用示例:策略模式在 TypeScript 代码中非常常见。 It's often used in various frameworks to provide users a way to change the behavior of a class without extending it.它经常用于各种框架中,为用户提供一种无需扩展即可更改类行为的方法。

Identification: Strategy pattern can be recognized by a method that lets nested object do the actual work, as well as the setter that allows replacing that object with a different one.识别:策略模式可以通过让嵌套对象完成实际工作的方法以及允许用不同的对象替换该对象的设置器来识别。

Conceptual Example This example illustrates the structure of the Strategy design pattern.概念示例此示例说明了策略设计模式的结构。 It focuses on answering these questions: • What classes does it consist of?它侧重于回答以下问题: • 它由哪些类组成? • What roles do these classes play? • 这些类扮演什么角色? • In what way the elements of the pattern are related? • 模式的元素以何种方式相关?

index.ts: Conceptual Example 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: Execution result 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM