组合/聚合复用
组合/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)一般也叫合成复用原则(Composite Reuse Principle, CRP)
核心思想:尽量使用合成/聚合,而不是通过继承达到复用的目的。
一般情况下,只有明确知道派生类和基类满IS A
的时候才选用继承,当满足HAS A
或者不能判断的情况下应该选用合成/聚合。
案例分析:
设计一个游戏中的角色系统,包括战士、法师和射手等角色。每种角色都有一些共同的属性和行为,比如生命值、法力值和攻击方法。但是,每种角色的攻击方式可能会有所不同,比如战士可能近战攻击,法师可能使用魔法攻击,射手可能远程攻击。
如果使用继承来设计这个系统,可能会创建一个基类Character
,然后让Warrior
、Mage
和Archer
继承自这个基类,并且在各自的子类中实现特定的攻击方法。这种方式看起来很直观,但是随着游戏角色种类的增加和功能的复杂化,系统的灵活性和可扩展性会受到限制。例如,如果出现了一种新的角色,既可以进行近战攻击也可以进行远程攻击,使用继承就很难处理这种“多重身份”的角色。
使用组合复用原则,我们可以将攻击方式设计为独立的类,比如MeleeAttack
、MagicAttack
和RangedAttack
。然后在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()