首页 > 解决方案 > 这是否遵循策略设计模式

问题描述

我和我的同事对于这是否遵循策略模式存在分歧。

我们有一个反应组件List,它需要一个具有以下形状的“策略”道具:

interface ListStrategy {
  renderItem: (index: number) => React.ReactNode
  itemCount: number
}

我们有一些函数可以创建以某种方式呈现列表的“策略”。例如,我们有以下策略构造函数。

createGroupedListStrategy(...args: GroupedListStrategyArgs): ListStrategy
createFlatListStrategy(...args: FlatListStrategyArgs): ListStrategy
createTreeListStrategy(...args: TreeListStrategyArgs): ListStrategy

我发现了很多例子,其中策略的构造函数要么不期望参数,要么期望每个策略都使用相同的参数。但是每个上面的构造函数都期望不同的参数。createGroupedListStrategy期望作为一个选项,可以在策略内部使用一个函数来将项目与其组匹配。createTreeListStrategy期望作为一个选项,它可以用来访问一个项目的孩子的功能。

由于构造函数如此不同,我的同事开始怀疑这些策略是否可以在策略模式的定义所讨论的意义上互换。但我的观点是,一旦策略被实例化,它们就可以毫无问题地互换。

任何人都可以清除这个吗?我真的很好奇。

标签: javascriptreactjstypescriptoopstrategy-pattern

解决方案


策略的构造函数与某事物是否是策略没有任何关系。策略模式的目标是提取一个独立于类的操作,并允许您确定的行为方式,而无需更改它。

考虑以下情况,我们想要制作一个简单的“计算器”,它接受两个值并使用它们进行运算。然后它以某种方式显示该结果。我们要提取以下逻辑:

  • 计算 -如何处理两个数字
  • 显示 - 如何显示结果

这意味着我们可以在不改变类本身的情况下改变计算器的工作方式。因此,我们提取了两种策略:

interface CalculationStrategy {
    doMaths: (a: number, b: number) => number
}

interface DisplayStrategy {
    show: (num: number) => void
}

我们可以提供多种实现:

//calculation strategies
class AddStrategy {
  doMaths(a, b) {
    return a + b;
  }
}

class MultiplyByConstantStrategy {
  constructor(x) {
    this.x = x;
  }

  doMaths(a, b) {
    return (a + b) * this.x;
  }
}

//display strategies
class ConsoleDisplayStrategy {
  show(num) {
    console.log(num.toFixed(2))
  }
}

class HTMLDisplayStrategy {
  constructor(elementSelector) {
    this.inputElement = document.querySelector(elementSelector);
  }

  show(num) {
    this.inputElement.value = num;
  }
}

//calculate class
class Calculate {
  constructor(operationHandler, displayHandler) {
    this.operationHandler = operationHandler;
    this.displayHandler = displayHandler;
  }

  calculate(a, b) {
    const result = this.operationHandler.doMaths(a, b);
    this.displayHandler.show(result);
  }
}


/*     usage     */

//calculate the total for a bill + tip
const tip = new Calculate(
  new MultiplyByConstantStrategy(1.15), 
  new HTMLDisplayStrategy("#totalWithTip")
);
document.querySelector("#billTotal")
  .addEventListener("click", () => {
    const coffee = Number(document.querySelector("#coffeePrice").value);
    const bagel = Number(document.querySelector("#bagelPrice").value);
    
    tip.calculate(coffee, bagel);
  });
  
//just display a calculation on the page
const showAdd = new Calculate(
  new AddStrategy(),
  new HTMLDisplayStrategy("#addResult")
);
showAdd.calculate(2, 8);


//print a sum
const printAdd = new Calculate(
  new AddStrategy(),
  new ConsoleDisplayStrategy()
);

document.querySelector("#printSum")
  .addEventListener("click", () => {
    const a = Number(document.querySelector("#a").value);
    const b = Number(document.querySelector("#b").value);
    
    printAdd.calculate(a, b);
  });
.as-console-wrapper {
    /* prevent the console output from covering the page */
    position: initial !important; 
}
<pre>MultiplyByConstantStrategy + HTMLDisplayStrategy</pre>

<div>
  <label for="coffeePrice">Price for coffee:</label>
  <input id="coffeePrice" value="2" type="number" />
</div>
<div>
  <label for="bagelPrice">Price for bagel:</label>
  <input id="bagelPrice" value="8" type="number" />
</div>
<div>
  <label for="totalWithTip">You owe:</label>
  <input id="totalWithTip" readonly/>
</div>
<button id="billTotal">Bill please!</button>

<hr/>

<pre>AddStrategy + HTMLDisplayStrategy</pre>

<div>
  <label for="addResult">2 + 8 = </label>
  <input id="addResult" readonly/>
</div>

<hr/>

<pre>AddStrategy + ConsoleDisplayStrategy</pre>

<div>
  <input id="a" value="2" type="number" />
  +
  <input id="b" value="8" type="number" />
</div>
<button id="printSum">print the sum</button>

目标在这里达到。我们已经成功地解耦了计算和显示。我们可以改变每一个,而不必改变另一个或Calculate类。这就是策略模式试图解决的问题。使用不同参数构建策略的事实与此结果无关。


推荐阅读