这是否遵循策略设计模式

Is this following the strategy design pattern

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

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

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

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

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

我发现了很多示例,其中策略的构造函数不期望任何参数,或者期望每个策略都具有相同的参数。但是上面的每个构造函数都期望不同的参数。 createGroupedListStrategy 需要一个函数作为选项,该函数可以在策略中使用以将项目与其组匹配。 createTreeListStrategy 需要一个可用于访问项目子项的函数作为选项。

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

谁能解决这个问题?我很好奇。

策略的构造函数与还是不是策略无关。策略模式的目标是提取一个独立于 class 的操作,并允许您确定 如何 一个 class 的行为,而不改变它。

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

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

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

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 class。而这正是策略模式试图解决的问题。使用不同参数构建策略的事实与此结果无关。