【问题标题】:Is this following the strategy design pattern这是否遵循策略设计模式
【发布时间】:2021-03-15 17:28:45
【问题描述】:

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

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

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

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

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

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

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

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

【问题讨论】:

  • 构造函数无所谓,一个策略重要的是它是否有一个统一的接口。通常,您可能拥有像handler 这样的属性,这是可能会有所不同的策略。然后代码会调用类似handler.doStuff(foo, bar) 的东西。如果您所有的策略都有一个带有两个参数的duStuff 方法,那么这就是您所需要的。构造函数是正交的。理想情况下,您希望从工厂生产它们,但最终的想法是,拥有策略的班级并不是制定策略的班级。因此,您将操作与类解耦。
  • 是的,它仍然是策略模式。调用者如何构建策略并不重要,重要的是如何在上下文中使用所选策略。 (当然,正如@VLAZ 所说,这些实际上是代码中的不同区域)。

标签: javascript reactjs typescript oop strategy-pattern


【解决方案1】:

策略的构造函数与某事物还是不是策略没有任何关系。策略模式的目标是提取一个独立于类的操作,并允许您在不改变类的情况下确定类的行为

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

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

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

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

【讨论】:

  • 很好的答案,我想听听你的意见。我做了一个沙箱,有两个例子: 1. UniversalList 2. SecondUniversalList 在您看来,它们都符合策略标准吗? codesandbox.io/s/priceless-archimedes-lmdmt?file=/src/App.js
  • @MichieldeVos 是的,对我来说似乎很好。您有两种策略可供选择,它们都与prepareData 具有相同的界面。如果您想动态更改它们,创建它们的地图,然后按名称查找它们是使用策略的非常常见的方法。它还解决了它们具有不同构造函数的问题——如果你实例化它们,构造函数需要什么参数并不重要,你只需获取一个已经存在的对象。请注意,对于您的具体示例,适配器可能会产生更好的结果,因为您可以在数据集上应用多个。
  • 是的,但是使用 SecondUniversalList,我们在使用它们的地方实例化策略,这会产生耦合并使它们不可互换。如果我们删除 categoryFilter 属性,屏幕将会中断。我认为这意味着 SecondUniversalList 因为这个原因不符合标准。
  • @MichieldeVos 好的,我错过了。我没有彻底检查代码。我认为这不会真正影响它们成为策略,但是,您可能不想在使用它们的地方实例化它们。好处是可重用性和解耦。您可以即时实例化,但期望这会发生在其他地方,并且使用该策略的对象将不知道也不关心它是哪一个。他们可能会使用lookup[myStrategy] 来获取或传入它(就像我上面的示例一样),或者甚至让一些 DI 从配置中生成它。
猜你喜欢
  • 1970-01-01
  • 2019-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-30
  • 1970-01-01
  • 1970-01-01
  • 2011-05-21
相关资源
最近更新 更多