依赖倒置原则
依赖倒置原则(Dependency Inversion Principle(DIP)), 高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖具体实现,具体实现应该依赖抽象。
简单来说就是:程序的核心逻辑(高层模块)不要直接依赖具体实现(低层模块),而是依赖一个抽象接口(比如接口、抽象类,基类),具体实现再来实现这个接口,这样就能轻松扩展和替换!
一般来说,程序是"高层调用低层"
游戏管理器(GameManager) -> 直接创建并控制 -> MP3播放器(Mp3Player)
这时候如果你想要换成 Ogg 播放器就得改 GameManager 的代码
依赖倒置的做法是
游戏管理器(GameManager) -> 依赖一个抽象的音频播放器(IAudioPlayer)
这样 GameManager 根本不管你到底是 mp3 还是 ogg,它只认识 IAudioPlayer,可以随时切换
高层模块(核心逻辑)不再被底层实现“绑架”
底层模块可以轻松替换或拓展(比如你可以写个新播放器)
抽象成为中间“契约”,代码结构更清晰,更解耦
例子
我们举个例子!比如你有一个 AnimalFeeder 的类,它要喂不同的动物,但不能死死地写死是喂“猫”还是“狗”:
// 抽象接口
public interface IAnimal
{
void Eat();
}
// 具体类
public class Dog : IAnimal
{
public void Eat() => Console.WriteLine("Dog is eating.");
}
public class Cat : IAnimal
{
public void Eat() => Console.WriteLine("Cat is eating.");
}
// 高层模块依赖于抽象 IAnimal
public class AnimalFeeder
{
private IAnimal _animal;
public AnimalFeeder(IAnimal animal)
{
_animal = animal;
}
public void Feed()
{
_animal.Eat(); // 不关心它到底是猫还是狗
}
}GDScript 中的实现方式
我们来分别看看使用抽象类(Godot4.5)和不使用抽象类:
# Animal.gd
@abstract
class_name Animal
@abstract func eat() -> void
# Cat.gd
class_name Cat
extends Animal
func eat():
print("猫猫在吃鱼")
# Dog.gd
class_name Dog
extends Animal
func eat():
print("狗狗在吃骨头")
# AnimalFeeder.gd
class_name AnimalFeeder
var _animal: Animal
func _init(animal: Animal):
_animal = animal
func feed():
animal.eat() # 喂动物,不管具体是谁!这里 AnimalFeeder 只依赖抽象类 Animal,完全不知道 Cat 和 Dog 的细节,就完美符合 DIP
Godot 4.4 及以下(不支持抽象类) 没有抽象类,
可以用 鸭子类型(duck typing)来近似实现:
# Cat.gd
class_name Cat
func eat(): print("猫猫吃饭ing")
# Dog.gd
class_name Dog
func eat(): print("狗狗大口吃")
# AnimalFeeder.gd
class_name AnimalFeeder
var _animal
func _init(animal):
_animal = animal
func feed():
if animal.has_method("eat"):
animal.eat() # 喂动物,不管具体是谁,只要可以吃就行!虽然没有类型约束,但只要大家都约定实现 eat(),AnimalFeeder 一样可以「只依赖抽象行为(eat)」而不是具体类,思想是对的!当然,这种写法缺少编译期保障
还有一种做法是常见的多态做法,让高层模块依赖于低层的基类,而不是某个子类,用普通类来当抽象类用,通过多态实现行为复用
class_name Animal
func eat(): pass
# Cat.gd
class_name Cat
func eat(): print("猫猫吃饭ing")
# Dog.gd
class_name Dog
func eat(): print("狗狗大口吃")
# AnimalFeeder.gd
class_name AnimalFeeder
var _animal: Animal
func _init(animal: Animal):
_animal = animal
func feed():
animal.eat() # 喂动物,不管具体是谁!