繁体   English   中英

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

[英]How to correctly implement strategy design 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);
    }
}

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

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

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

  1. 这不违反开闭原则吗? 因此,如果我想再添加一种表单策略或删除现有的一种,我必须编辑 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 */)

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

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

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

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

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

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

杂项评论

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

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

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

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

暂无
暂无

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

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