不良团队的9种选择(设计模式)

图片

这是什么,为什么?


在设计时,开发人员可能会遇到问题:生物和物体在不同的组合中可能具有不同的能力。 青蛙跳和游泳,鸭子游泳和飞行,但是没有负重,青蛙可以与树枝和鸭子飞。 因此,从继承转换为合成并动态添加功能非常方便。 对动画青蛙进行动画处理的需求导致对能力方法的不合理拒绝,并导致其中一种实现的团队代码被删除。 这是:

class CastSpellCommand extends Command { constructor (source, target, spell) { this.source = source; this.target = target; this.spell = spell; } execute () { const spellAbility = this.source.getAbility(SpellCastAbility); //      if (!cond) return error if (spellAbility == null) { throw new Error('NoSpellCastAbility'); } this.addChildren(new PayManaCommand(this.source, this.spell.manaCost)); this.addChildren(this.spell.getCommands(this.source, this.target)); //   : healthAbility.health = Math.max( 0, resultHealth ); } } // : async onMeleeHit (meleeHitCommand) { await view.drawMeleeHit( meleeHitCommand.source, meleeHitCommand.target ); } async onDealDamage (dealDamageCommand) { await view.showDamageNumbers( dealDamageCommand.target, dealDamageCommand.damage ); } 

该怎么办?


考虑几种不同性质的方法:

观察者


 class Executor extends Observer {/* ... */} class Animator extends Observer {/* ... */} 

程序员众所周知的经典解决方案。 您只需要进行最小的更改即可检查观察者返回的值:

 this.listeners.reduce((result, listener) => result && listener(action), true) 

缺点:观察者必须以正确的顺序订阅事件。

如果您执行错误处理,那么动画师也将能够显示失败动作的动画。 您可以将先前的值传递给观察者;从概念上讲,解决方案保持不变。 无论是调用观察者方法还是回调函数,是否使用常规循环而不是卷积,细节都不是那么重要。

照原样


的确如此。 当前的方法既有缺点也有优点:

  1. 测试运行命令的能力需要执行命令
  2. 顺序变化,条件和方法前缀的参数是硬连接的
  3. 循环依赖项(命令<spell <命令)
  4. 每个动作的其他实体(方法由方法,类及其构造函数代替)
  5. 单个团队的知识和行动过多:从游戏机制到同步错误以及对他人财产的直接操纵
  6. 该接口具有误导性(不仅执行调用,而且还通过addChildren添加命令;很明显,这是相反的)
  7. 疑问的需要和递归指令的实现本身
  8. 调度程序类(如果有)不执行其功能
  9. [+]据说,如果动画需要完整的数据,这是实践中制作动画的唯一方法(表示主要原因)
  10. [+]可能是其他原因

某些缺点可以单独解决,但其余的则需要更彻底的改变。

临时的


  • 团队执行的条件,特别是游戏机制,必须从团队中取出并单独执行。 条件可能在运行时发生变化,并且实际上在动画工作开始之前很久就以灰色突出显示不活动的按钮,更不用说逻辑了。 为了避免复制,将一般条件存储在能力原型中可能是有意义的。
  • 返回方法,结合上一段,对此类检查的需求将消失:

     const spellAbility = this.source.getAbility(SpellCastAbility); //      if (!cond) return error if (spellAbility == null) { throw new Error('NoSpellCastAbility'); } 

    错误调用该方法时,javascript引擎本身将显示正确的TypeError。
  • 团队也不需要这样的知识:

     healthAbility.health = Math.max( 0, resultHealth ); 
  • 为了解决参数更改位置的问题,可以通过对象传递它们。
  • 尽管调用代码尚无法研究,但大多数缺点似乎由于调用游戏动作的非最佳方式而加剧。 例如,按钮处理程序访问特定的实体。 因此,在处理程序中用特定命令替换它们似乎很自然。 如果您有调度员,则在操作结束后调用动画要容易得多,您可以向其中传递相同的信息,因此不会缺少数据。


要在动作完成后显示动作的动画,只需将它们添加到队列中并按照解决方案1的方式运行它们就足够了。

 [ [ walkRequirements, walkAction, walkAnimation ], [ castRequirements, castAction, castAnimation ], // ... ] 

数组中的实体无关紧要:带有必要参数禁止的功能,自定义类的实例或普通对象。
这种解决方案的价值在于简单性和透明性;很容易创建一个滑动窗口以查看最后N个命令。

非常适合原型设计和调试。

本科班


我们为此功能制作了一个动画课。

 class MovementAbility { walk (...args) { // action } } class AnimatedMovementAbility { walk (...args) { // animation } } 

如果不可能对调用类进行更改,则我们可以从该类继承或装饰所需的方法,以便它调用动画。 或者我们传输动画而不是功能,它们具有相同的界面。

非常适合当您需要几乎相同的方法集时,可以对其进行自动检查和测试。

方法组合


 const AnimatedMovementAbility = combinedClass(MovementAbility, { ['*:before'] (method, ...args) { // call requirements }, ['*:after'] (method, ...args) { // call animations } }) 

有了母语的支持,这将是一个有趣的机会。
如果此选项更有效,则最好使用它,尽管实际上需要代理。

代理人


我们将能力包装在代理中,将方法包装在吸气剂中。

 new Proxy(new MovementAbility, {/* handler */}) 

缺点:比常规调用慢很多倍,这对于动画而言并不重要。 在处理数百万个对象的服务器上,速度下降会很明显,但是服务器不需要动画。

承诺


您可以从Promise构建链,但是还有另一个选择(ES2018):

 for await (const action of actionDispatcher.getActions()) { // } 

getActions返回一个异步操作迭代器。 迭代器的next方法返回下一个动作的Deferred Promise。 处理来自用户和服务器的事件后,我们调用resolve(),创建一个新的Promise。

更好的团队


创建这样的对象:

 {actor, ability, method, options} 

代码归结为检查并调用带参数的能力方法。 最简单,最有效的选择。

注意事项


Source: https://habr.com/ru/post/zh-CN439506/


All Articles