组合聚合复用原则

36 阅读3分钟

组合/聚合复用

组合/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)一般也叫合成复用原则(Composite Reuse Principle, CRP)

核心思想:尽量使用合成/聚合,而不是通过继承达到复用的目的。

一般情况下,只有明确知道派生类和基类满IS A的时候才选用继承,当满足HAS A或者不能判断的情况下应该选用合成/聚合。

案例分析:

设计一个游戏中的角色系统,包括战士、法师和射手等角色。每种角色都有一些共同的属性和行为,比如生命值、法力值和攻击方法。但是,每种角色的攻击方式可能会有所不同,比如战士可能近战攻击,法师可能使用魔法攻击,射手可能远程攻击。

如果使用继承来设计这个系统,可能会创建一个基类Character,然后让WarriorMageArcher继承自这个基类,并且在各自的子类中实现特定的攻击方法。这种方式看起来很直观,但是随着游戏角色种类的增加和功能的复杂化,系统的灵活性和可扩展性会受到限制。例如,如果出现了一种新的角色,既可以进行近战攻击也可以进行远程攻击,使用继承就很难处理这种“多重身份”的角色。

使用组合复用原则,我们可以将攻击方式设计为独立的类,比如MeleeAttackMagicAttackRangedAttack。然后在Character类中通过组合的方式引用一个攻击方式的实例,而不是通过继承来获得攻击方式。这样,当需要定义不同的角色时,只需要组合不同的攻击方式即可。

使用继承:

class Character {
  constructor(public name) {}
  attack() {
    console.log('Character attacks')
  }
}
class Warrior extends Character {
  attack() {
    console.log(`${this.name} swings a sword`)
  }
}
class Mage extends Character {
  attack() {
    console.log(`${this.name} casts a spell`)
  }
}
class Archer extends Character {
  attack() {
    console.log(`${this.name} shoots an arrow`)
  }
}
function test() {
  const warrior = new Warrior('Warrior')
  const mage = new Mage('Mage')
  const archer = new Archer('Archer')
  warrior.attack()
  mage.attack()
  archer.attack()
}
test()

在这样的设计下,如果一个角色需要切换攻击方式将变得困难。

改用组合实现:

abstract class AttackMethod {
  abstract attack(): void
}
class MeleeAttack extends AttackMethod {
  attack() {
    console.log('Performing a melee attack')
  }
}
class MagicAttack extends AttackMethod {
  attack() {
    console.log('Casting a magic spell')
  }
}
class RangedAttack extends AttackMethod {
  attack() {
    console.log('Shooting an arrow')
  }
}
class Character {
  constructor(private name: string, private attack_method: AttackMethod) {
    this.name = name
    this.attack_method = attack_method
  }
  perform_attack() {
    this.attack_method.attack()
  }
  changeAttack(attack_method: AttackMethod) {
    this.attack_method = attack_method
  }
}
function test() {
  const warrior = new Character('Warrior', new MeleeAttack())
  const mage = new Character('Mage', new MagicAttack())
  const archer = new Character('Archer', new RangedAttack())
  warrior.perform_attack()
  mage.perform_attack()
  archer.perform_attack()
  // 更改攻击方式
  mage.changeAttack(new MeleeAttack())
  mage.perform_attack()
}
test()

这样改变或添加新的攻击方式将变得简单,而且同一个攻击方式可以复用。

abstract class AttackMethod {
  abstract attack(): void
}
class MeleeAttack extends AttackMethod {
  attack() {
    console.log('Performing a melee attack')
  }
}
class MagicAttack extends AttackMethod {
  attack() {
    console.log('Casting a magic spell')
  }
}
class RangedAttack extends AttackMethod {
  attack() {
    console.log('Shooting an arrow')
  }
}
class Stealth extends AttackMethod {
  attack() {
    console.log('Performing a stealth attack')
  }
}
class Character {
  constructor(private name: string, private attack_method: AttackMethod) {
    this.name = name
    this.attack_method = attack_method
  }
  perform_attack() {
    this.attack_method.attack()
  }
  changeAttack(attack_method: AttackMethod) {
    this.attack_method = attack_method
  }
}
​
function test() {
  const meleeAttack = new MeleeAttack()
  const magicAttack = new MagicAttack()
  const rangedAttack = new RangedAttack()
  const stealth = new Stealth()
  const warrior = new Character('Warrior', meleeAttack)
  const mage = new Character('Mage', magicAttack)
  const archer = new Character('Archer', rangedAttack)
  warrior.perform_attack()
  mage.perform_attack()
  archer.perform_attack()
  // 更改攻击方式
  mage.changeAttack(meleeAttack)
  mage.perform_attack()
  mage.changeAttack(stealth)
  mage.perform_attack()
}
test()