[英]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:所以问题是:
FormBuilder
, and not passing strategy from outside?FormBuilder
内部选择策略而不是从外部传递策略是否正确?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:所以关于你的问题:
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.
因此可以在运行时交换策略。
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:在更笼统之前,让我们具体回答一下:
- 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()
?
- 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. Builders和Factories通过设计与底层创建的类型紧密耦合。 It's a local violation of the OCP but with them the client code is decoupled from form creation implementation details.
这是对 OCP 的局部违反,但有了它们,客户端代码与表单创建实现细节分离。
Misc comments杂项评论
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.formBuilder.withPartA().withPartB().build();
formBuilder.withPartA().withPartB().build();
这样的流畅 API formBuilder.withPartA().withPartB().build();
. Form
.Form
。 Maybe it does too much: just selecting the factory would be enough.interface
/ type
but without a wrapping object /class that may not bring more value. interface
/ type
但没有包装对象/类,这可能不会带来更多价值。 The client code just have to pass another function which can be a "simple lambda" (fat arrow function).params
argument is used by both the Builder and the Factories . params
参数由Builder和Factories 使用。 It would be probably better to split it, to avoid mixing distinct concerns: strategy selection and form creation .createSimpleForm(simpleFormArgs)
, createExtendedForm(extendsFormArgs)
... Each method will instanciate the associated factory and call it create(formArgs)
method.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.