如何正确实施策略设计模式
How to correctly implement strategy design pattern
我正在尝试实施策略设计模式,想知道我是否做得正确。
比方说,我有 class FormBuilder
,它使用下面列表中的策略来构建表单:
SimpleFormStrategy
ExtendedFormStrategy
CustomFormStrategy
所以问题是:
- select 策略在
FormBuilder
内部而不是从外部传递策略是否正确?
- 这不是违反了开闭原则吗?因此,如果我想再添加一种表单策略或删除现有的策略,我必须编辑
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)是开放的。所以关于你的问题:
- 在
FormBuilder
内部select策略,而不是从外部传递策略是否正确?
策略执行不正确。您应该创建策略的实例并将其传递给上下文。因此,可以在 运行 时间交换策略。
- 这不是违反了开闭原则吗?因此,如果我想再添加一种表单策略或删除现有的表单策略,我必须编辑 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 原则,它们都是 面向对象编程 的支柱].
在更笼统之前先具体回答一下:
- Is it correct to select strategy inside
FormBuilder
, and not passing strategy from outside?
是的。相反导致 FormFactory
包装器没有太大兴趣。为什么不直接调用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.
Builders 和 Factories 在设计上与底层创建的类型紧密耦合。这是对 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
参数被 Builder 和 Factories 使用。拆分它可能会更好,以避免混淆不同的问题:策略select离子和形式创建.
- 如果要创建的表单类型(简单、扩展、自定义)不是动态的,但从客户端代码端已知,最好直接提供 3 个具有特定参数的方法:
createSimpleForm(simpleFormArgs)
, createExtendedForm(extendsFormArgs)
... 每个方法都会实例化关联的工厂并调用它 create(formArgs)
方法。这样,不需要复杂的算法来 select 策略,基于 if
s 或 switch
s 增加 Cyclomatic Complexity。调用每个 createXxxForm
方法也将更简单,对象参数更少。
我正在尝试实施策略设计模式,想知道我是否做得正确。
比方说,我有 class FormBuilder
,它使用下面列表中的策略来构建表单:
SimpleFormStrategy
ExtendedFormStrategy
CustomFormStrategy
所以问题是:
- select 策略在
FormBuilder
内部而不是从外部传递策略是否正确? - 这不是违反了开闭原则吗?因此,如果我想再添加一种表单策略或删除现有的策略,我必须编辑
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)是开放的。所以关于你的问题:
- 在
FormBuilder
内部select策略,而不是从外部传递策略是否正确?
策略执行不正确。您应该创建策略的实例并将其传递给上下文。因此,可以在 运行 时间交换策略。
- 这不是违反了开闭原则吗?因此,如果我想再添加一种表单策略或删除现有的表单策略,我必须编辑 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 原则,它们都是 面向对象编程 的支柱].
在更笼统之前先具体回答一下:
- Is it correct to select strategy inside
FormBuilder
, and not passing strategy from outside?
是的。相反导致 FormFactory
包装器没有太大兴趣。为什么不直接调用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.
Builders 和 Factories 在设计上与底层创建的类型紧密耦合。这是对 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"(粗箭头函数)。
- 函数式语言中的策略只是函数。 TypeScript 允许定义高阶函数,带有
- 其他
params
参数被 Builder 和 Factories 使用。拆分它可能会更好,以避免混淆不同的问题:策略select离子和形式创建.- 如果要创建的表单类型(简单、扩展、自定义)不是动态的,但从客户端代码端已知,最好直接提供 3 个具有特定参数的方法:
createSimpleForm(simpleFormArgs)
,createExtendedForm(extendsFormArgs)
... 每个方法都会实例化关联的工厂并调用它create(formArgs)
方法。这样,不需要复杂的算法来 select 策略,基于if
s 或switch
s 增加 Cyclomatic Complexity。调用每个createXxxForm
方法也将更简单,对象参数更少。