对方法过度收费是个好主意吗?

Is it a good idea to overcharge a method?

我有3个类,都是上一个的扩展
实体 -> Body -> 玩家
每个都有一个 die() 方法,可以做非常不同的事情。
Entity.die() 将调用 db
Body.die() 将为 body
设置动画 Player.die() 将调用 UI 并播放特殊声音。

我不想在Body.die方法里面手动调用Entity.die(),主要是因为我有很多类和很多常用的方法,我不想忘记一些东西。

我写的这段代码正是这样做的,错误堆栈很容易理解并指向正确的行。

function overLoadMethods (parent, children) {
  const methods = {}
  for (let [fname, fn] of Object.entries(parent)) {
    if (typeof fn === 'function') {
      if (children[fname]) {
        methods[fname] = function () {
          fn()
          children[fname]()
        }
        Object.defineProperty(methods[fname], 'name', { value: fname })
      } else {
        methods[fname] = fn
      }
    }
  }
  return methods
}

function createEntity () {
  return {
    die: () => {
      console.log(new Error().stack)
      console.log('entity die')
    }
  }
}

const bodyMethods = {
  die: () => {
    console.log(new Error().stack)
    console.log('body die')
  }
}

function createBody () {
  const entity = createEntity()
  const overLoadedMethods = overLoadMethods(entity, bodyMethods)
  return {
    ...entity,
    ...bodyMethods,
    ...overLoadedMethods
  }
}

const playerMethods = {
  die: () => {
    console.log(new Error().stack)
    console.log('player die')
  }
}

function createPlayer () {
  const body = createBody()
  const overLoadedMethods = overLoadMethods(body, playerMethods)
  return {
    ...body,
    ...playerMethods,
    ...overLoadedMethods
  }
}

const player = createPlayer()
// will call Entity.die() then Body.die() then Player.die()
player.die()

一切正常,但我以前从未见过这种模式,我想这其中有一个我不知道的充分理由。 如果有的话(很确定有),有人能指出这种模式的弱点吗?

我理解不重复代码和创建难以犯错和忘记事情的代码的愿望。但是您仍然需要记住连接的代码。例如,您需要调用 overLoadMethods() 而不是调用 Entity.die()。我不确定这是否比常规 类 和调用 super.die().

有所改进

您可以使用 ES6 类 获得链式方法行为(您也可以使用原型获得它)。这有很多优点:

• 模式融入了语言。
• 很明显parent/child关系
• 有很多不同模式的评论、理论和示例

class Entity {
  die() {
    // Entity-specific behavior
    console.log('entity die')
  }
}

class Body extends Entity {
  die() {
    super.die()
    // Body-specific behavior
    console.log('body die')
  }
}

class Player extends Body {
  die() {
    super.die()
    // Player-specific behavior
    console.log('player die')
  }
}


const player = new Player
// will call Entity.die() then Body.die() then Player.die()
player.die()

Common Lisp 有 something similar。当你在派生中定义一个方法时class你可以决定这个方法是否应该被执行:

  • :before(即基础方法会在特化后自动调用)
  • :after(即基本方法将在专用方法之前自动调用)
  • :around(即只调用专门的方法,但在其主体内您可以使用 call-next-method 调用基方法,这是一种特殊语法,允许使用参数调用基方法由调用者指定或您要传递的参数代替)。

例如,C++ 只有 around 可用于一般方法(但无法使用原始参数调用基本版本),并强制在构造函数中使用 beforeafter 在析构函数中。