合成复用原则
合成复用原则(Composition Over Inheritance(COI)),即 组合优于继承
它强调,能用组合就别用继承——通过将对象组合成更复杂的功能,而不是一味地通过继承层层扩展。
与其一味造“父类—子类—孙类”的继承大楼,不如把不同能力“组装”成组件,灵活拼装,随插即用
Unity 的 GameObject-Component 模式中的游戏对象就是最经典的“组合优于继承”的代表:
所有的游戏对象最初都是一个空对象,通过为其添加各种组件(Component)来构成各种各样的复杂对象
默认带有
Transform组件(类似于 Godot 的Node),表示位置 / 旋转 / 缩放。添加
SpriteRenderer,就可以渲染 2D 图像(Sprite2D)。添加
CapsuleCollider2D,就能检测物理碰撞(类似于CollisionObject2D)。添加
Rigidbody2D,它就能受到物理力的影响自由运动(对应PhysicsBody2D)。Rigidbody2D组件的BodyType默认为Dynamic,能够进行动态物理运动(CharacterBody2D/RigidBody2D)如果将
BodyType更改为Static,它就变成了一个不会动的静态物体(如 Godot 的StaticBody2D)
通过Unity组建游戏对象的方式你可以发现,它并不是依据继承关系来构建具有不同能力的对象的,而是按需组合想要使用的组件而不需要拥有父类中使用不到的逻辑和属性,这就是组合优于继承的体现。
让每个对象只拥有它真正需要的功能,避免了继承带来的“强塞功能”和“臃肿类层级”。
为什么要使用组合?或者说,为什么组合优于继承?
因为继承虽然直观,会有以下缺点:
父类稍有改动,子类就可能全线崩溃(耦合度高)
子类被迫继承一些用不到的属性和方法,显得臃肿
如果两个功能类都很重要,但却只能继承其中一个(单继承限制)
继承链越长,理解和维护成本越高
而组合则像积木,你可以按需添加能力、替换组件,轻松打造多变的对象
例子
比如做一个游戏里的角色,有移动、攻击、回血等能力。
继承方式(容易变乱):
class Character
{
public virtual void Move() {}
}
class Warrior : Character
{
public void Attack() {}
}
class Healer : Character
{
public void Heal() {}
}class_name Character
func move(): pass
class_name Warrior extends Character
func attack(): pass
class_name Healer extends Character
func heal(): pass那么再加一个既会移动又会攻击还会治疗的角色怎么办?如果要不会移动但会攻击的角色怎么办?你要继承谁?
使用组合就会更加灵活,按需组合需要的组件来构成不同的对象
public class Character
{
public IMovementBehavior Movement;
public IAttackBehavior Attack;
public IHealBehavior Heal;
public void Move() => Movement?.Move();
public void Attack() => Attack?.Attack();
public void Heal() => Heal?.Heal();
}class_name Character
var movement: MovementBehavior
var attack: AttackBehavior
var heal: HealBehavior
func move():
if movement != null:
movement.move()
func attack():
if attack != null:
attck.attck()
func heal():
if heal != null:
heal.heal()想要不同的行为,只需要在构造的时候为其添加对应的行为组件即可,如果想要角色能飞,就继承IMovementBehavior编写飞行的行为,然后让Movement使用它,想要物理或魔法伤害,就继承IAttackBehavior编写行为逻辑,然后让Attck使用它
这样,想要角色有什么能力,就用什么组件,也不会因为继承导致类体乱套了