开闭原则
开闭原则(Open-closed Principle(OCP)),即一个软件实体(类、模块、函数等)应对扩展开放,对修改关闭。
也就是说,当你的程序需要新功能时,你应该通过添加新代码(扩展)来实现功能,而不是修改已有的代码(修改)。
这样可以避免破坏现有系统,提高系统的可维护性、可复用性,也能减少因改动旧代码而引发的bug
因为动旧代码就像——在费尽功夫造好的建筑中不小心拆掉一根承重柱,一不小心就全倒了
开闭原则就像给系统留了“插槽”,每次只要插新模块,而不是动老模块 在游戏开发里无论是技能、怪物、道具、关卡逻辑……只要模块化设计得好,都会变得灵活
角色、技能、AI 行为、武器系统等都是特别适合运用开闭原则的领域
例子1
假设你在开发一个 RPG 游戏,里面有各种敌人(Enemy),最初你只设计了一个 Goblin。
不符合开闭原则的写法:
gdscript
class_name Enemy
var type: String
func attack() -> void:
if type == "Goblin":
print("Goblin attacks with a dagger!")
elif type == "Orc":
print("Orc smashes with a club!")
# 每次添加新敌人都要修改这个类csharp
public class Enemy
{
public string Type;
public void Attack()
{
if (Type == "Goblin")
{
Console.WriteLine("Goblin attacks with a dagger!");
}
else if (Type == "Orc")
{
Console.WriteLine("Orc smashes with a club!");
}
// 每次添加新敌人都要修改这个类
}
}这种写法不好!每次增加一个敌人类型都要来改 Enemy 类,改一次就可能出新 bug!而且代码臃肿又不好测试!
符合开闭原则的写法:
我们可以使用基类Enemy来扩展功能,
这样,当你要添加新的敌人类型时,只需要新增一个类继承 Enemy 就可以了,完全不需要动旧代码:
gdscript
class_name Enemy
func attack() -> void: pass
# 如果是Godot 4.5版本,可以使用抽象类
@abstract class_name Enemy
@abstract func attack() -> void
class_name Goblin extends Enemy
func attack() -> void:
print("Goblin attacks with a dagger!")
class_name Orc extends Enemy
func attack() -> void:
print("Orc samshes with a club!")
class_name Dragon extends Enemy
func attack() -> void:
print("Dragon breathes fire!")csharp
public abstract class Enemy
{
public abstract void Attack();
}
public class Goblin : Enemy
{
public override void Attack()
{
Console.WriteLine("Goblin attacks with a dagger!");
}
}
public class Orc : Enemy
{
public override void Attack()
{
Console.WriteLine("Orc smashes with a club!");
}
}
public class Dragon : Enemy
{
public override void Attack()
{
Console.WriteLine("Dragon breathes fire!");
}
}例子2
技能系统设计
没有遵循开闭原则的写法:
gdscript
class_name SkillSystem
func use_skill(skill_name: String) -> void:
if skill_name == "Fireball":
print("Cast Fireball!")
elif skill_name == "IceBlast":
print("Cast Ice Blast!")
elif skill_name == "Heal":
print("Cast Heal!")
# 添加技能就要一直加 elifcsharp
public class SkillSystem
{
public void UseSkill(string skillName)
{
if (skillName == "Fireball")
{
Console.WriteLine("Cast Fireball!");
}
else if (skillName == "IceBlast")
{
Console.WriteLine("Cast Ice Blast!");
}
else if (skillName == "Heal")
{
Console.WriteLine("Cast Heal!");
}
// 添加技能就要一直加 else if……
}
}如果游戏后期加了“闪电术”,就又得改 UseSkill,这样就违背了“修改封闭”,代码容易变得一团乱麻(技能越多越可怕)
遵循开闭原则的写法:
gdscript
class_name Skill
func cast() —> void: pass
# 如果是Godot4.5也可以使用抽象类
@abstract class_name Skill
@abstract func cast() -> void
class_name Fireball extends Skill
func cast() -> void:
pirnt("Cast Fireball!")
class_name IceBlast extends Skill
func cast() -> void:
print("Cast Ice Blast!")
class_name SkillSystem
var _skills: Dictionary[String, Skill] = {}
func register_skill(name: String, skill: Skill) -> void:
_skills[name] = skill
func use_skill(name: String) -> void:
if _skills.has(name):
_skills[name].cast()csharp
public interface ISkill
{
void Cast();
}
public class Fireball : ISkill
{
public void Cast()
{
Console.WriteLine("Cast Fireball!");
}
}
public class IceBlast : ISkill {
public void Cast() {
Console.WriteLine("Cast Ice Blast!");
}
}
public class SkillSystem {
private Dictionary<string, ISkill> _skills = new();
public void RegisterSkill(string name, ISkill skill)
{
_skills[name] = skill;
}
public void UseSkill(string name)
{
if (_skills.TryGetValue(name, out var skill))
{
skill.Cast();
}
}
}然后在游戏初始化的时候:
csharp
skillSystem.RegisterSkill("Fireball", new Fireball());
skillSystem.RegisterSkill("IceBlast", new IceBlast());gdscript
skill_system.register_skill("Fireball", Fireball.new())
skill_system.register_skill("IceBlast", IceBlast.new())