在 React 中应用设计模式:策略模式

这篇文章是关于我们许多人在 React 和前端开发中遇到的一个问题(有时甚至没有意识到这是一个问题):在不同的组件、钩子、实用程序等中实现了一段逻辑。

让我们深入了解问题的详细信息以及如何解决它。正如标题所暗示的,我们将使用策略模式来解决它。

问题:霰弹枪手术

Shotgun Surgery是一种代码味道,其中进行任何修改都需要对许多不同的地方进行许多小的更改


(图片来源:https ://refactoring.guru/smells/shotgun-surgery )

这怎么会发生在一个项目中?假设我们需要为产品实施定价卡,我们根据客户的来源调整价格、货币、折扣策略和消息:

我们大多数人可能会按如下方式实施定价卡:

  • 组件:PricingCardPricingHeaderPricingBody
  • 效用函数:(getDiscountMessageutils/discount.ts中),formatPriceByCurrency(在utils/price.ts 中)。
  • PricingBody组件还计算最终价格。

这是完整的实现:

现在假设我们需要更改一个国家/地区的定价计划,或为另一个国家/地区添加新的定价计划。您将如何处理上述实施?您必须至少修改 3 个地方并向已经凌乱的if-else块添加更多条件:

  • 修改PricingBody组件。
  • 修改getDiscountMessage函数。
  • 修改formatPriceByCurrency函数。

如果您已经听说过 SOLID,那么我们已经违反了前 2 个原则:单一职责原则和开闭原则。

解决方案:策略模式

策略模式非常简单。我们可以简单的理解为我们每个国家的定价方案都是一个策略。在那个策略类中,我们实现了该策略的所有相关逻辑。

假设您熟悉 OOP,我们可以有一个PriceStrategy实现共享/公共逻辑的抽象类 ( ),然后具有不同逻辑的策略将继承该抽象类。PriceStrategy抽象类如下所示:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-name-color)">Country</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">Currency</span> <span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">'</span><span style="color:var(--syntax-string-color)">../../types</span><span style="color:var(--syntax-string-color)">'</span><span style="color:var(--syntax-text-color)">;</span>

<span style="color:var(--syntax-declaration-color)">abstract</span> <span style="color:var(--syntax-declaration-color)">class</span> <span style="color:var(--syntax-name-color)">PriceStrategy</span> <span style="color:var(--syntax-text-color)">{</span>
  <span style="color:var(--syntax-declaration-color)">protected</span> <span style="color:var(--syntax-name-color)">country</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-name-color)">Country</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">Country</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">AMERICA</span><span style="color:var(--syntax-text-color)">;</span>
  <span style="color:var(--syntax-declaration-color)">protected</span> <span style="color:var(--syntax-name-color)">currency</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-name-color)">Currency</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">Currency</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">USD</span><span style="color:var(--syntax-text-color)">;</span>
  <span style="color:var(--syntax-declaration-color)">protected</span> <span style="color:var(--syntax-name-color)">discountRatio</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-literal-color)">0</span><span style="color:var(--syntax-text-color)">;</span>

  <span style="color:var(--syntax-name-color)">getCountry</span><span style="color:var(--syntax-text-color)">():</span> <span style="color:var(--syntax-name-color)">Country</span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">country</span><span style="color:var(--syntax-text-color)">;</span>
  <span style="color:var(--syntax-text-color)">}</span>

  <span style="color:var(--syntax-name-color)">formatPrice</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-declaration-color)">number</span><span style="color:var(--syntax-text-color)">):</span> <span style="color:var(--syntax-declaration-color)">string</span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">currency</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">toLocaleString</span><span style="color:var(--syntax-text-color)">()].</span><span style="color:var(--syntax-name-color)">join</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">''</span><span style="color:var(--syntax-text-color)">);</span>
  <span style="color:var(--syntax-text-color)">}</span>

  <span style="color:var(--syntax-name-color)">getDiscountAmount</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-declaration-color)">number</span><span style="color:var(--syntax-text-color)">):</span> <span style="color:var(--syntax-declaration-color)">number</span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-name-color)">price</span> <span style="color:var(--syntax-error-color)">*</span> <span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">discountRatio</span><span style="color:var(--syntax-text-color)">;</span>
  <span style="color:var(--syntax-text-color)">}</span>

  <span style="color:var(--syntax-name-color)">getFinalPrice</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-declaration-color)">number</span><span style="color:var(--syntax-text-color)">):</span> <span style="color:var(--syntax-declaration-color)">number</span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-name-color)">price</span> <span style="color:var(--syntax-error-color)">-</span> <span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">getDiscountAmount</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">);</span>
  <span style="color:var(--syntax-text-color)">}</span>

  <span style="color:var(--syntax-name-color)">shouldDiscount</span><span style="color:var(--syntax-text-color)">():</span> <span style="color:var(--syntax-name-color)">boolean</span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">discountRatio</span> <span style="color:var(--syntax-error-color)">></span> <span style="color:var(--syntax-literal-color)">0</span><span style="color:var(--syntax-text-color)">;</span>
  <span style="color:var(--syntax-text-color)">}</span>

  <span style="color:var(--syntax-name-color)">getDiscountMessage</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-declaration-color)">number</span><span style="color:var(--syntax-text-color)">):</span> <span style="color:var(--syntax-declaration-color)">string</span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">formattedDiscountAmount</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">formatPrice</span><span style="color:var(--syntax-text-color)">(</span>
      <span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">getDiscountAmount</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">)</span>
    <span style="color:var(--syntax-text-color)">);</span>

    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-string-color)">`It's lucky that you come from </span><span style="color:var(--syntax-text-color)">${</span><span style="color:var(--syntax-declaration-color)">this</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">country</span><span style="color:var(--syntax-text-color)">}</span><span style="color:var(--syntax-string-color)">, because we're running a program that discounts the price by </span><span style="color:var(--syntax-text-color)">${</span><span style="color:var(--syntax-name-color)">formattedDiscountAmount</span><span style="color:var(--syntax-text-color)">}</span><span style="color:var(--syntax-string-color)">.`</span><span style="color:var(--syntax-text-color)">;</span>
  <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-text-color)">}</span>

<span style="color:var(--syntax-declaration-color)">export</span> <span style="color:var(--syntax-declaration-color)">default</span> <span style="color:var(--syntax-name-color)">PriceStrategy</span><span style="color:var(--syntax-text-color)">;</span>
</code></span></span>

我们只需将实例化的策略作为 prop 传递给PricingCard组件:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-name-color)">PricingCard</span> <span style="color:var(--syntax-name-color)">price</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">{</span><span style="color:var(--syntax-literal-color)">7669</span><span style="color:var(--syntax-string-color)">}</span> <span style="color:var(--syntax-name-color)">strategy</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">{</span><span style="color:var(--syntax-declaration-color)">new</span> <span style="color:var(--syntax-name-color)">JapanPriceStrategy</span><span style="color:var(--syntax-text-color)">()</span><span style="color:var(--syntax-string-color)">}</span> <span style="color:var(--syntax-text-color)">/></span>
</code></span></span>

道具PricingCard定义为:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-declaration-color)">interface</span> <span style="color:var(--syntax-name-color)">PricingCardProps</span> <span style="color:var(--syntax-text-color)">{</span>
  <span style="color:var(--syntax-text-color)">price</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-declaration-color)">number</span><span style="color:var(--syntax-text-color)">;</span>
  <span style="color:var(--syntax-text-color)">strategy</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-name-color)">PriceStrategy</span><span style="color:var(--syntax-text-color)">;</span>
<span style="color:var(--syntax-text-color)">}</span>
</code></span></span>

同样,如果您了解 OOP,那么我们不仅在使用继承,而且还在此处使用多态性。

这是解决方案的完整实现:

让我们再次问同样的问题:我们如何为新的国家/地区添加新的定价计划?使用这个解决方案,我们只需要添加一个新的策略类,而不需要修改任何现有代码。通过这样做,我们也满足了 SOLID。

结论

因此,通过在我们的 React 代码库中检测代码异味——Shotgun Surgery,我们应用了一种设计模式——策略模式——来解决它。我们的代码结构来自于:

对此:

现在我们的逻辑存在于一个地方,不再分布在许多地方。

如果您对设计模式和架构以及如何使用它们来解决前端世界中的问题感兴趣,请务必给我点赞和关注。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

产品大道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值