Skip to content

依赖倒置原则

依赖倒置原则(Dependency Inversion Principle(DIP)), 高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖具体实现,具体实现应该依赖抽象。

简单来说就是:程序的核心逻辑(高层模块)不要直接依赖具体实现(低层模块),而是依赖一个抽象接口(比如接口、抽象类,基类),具体实现再来实现这个接口,这样就能轻松扩展和替换!

一般来说,程序是"高层调用低层"

游戏管理器(GameManager) -> 直接创建并控制 -> MP3播放器(Mp3Player)

这时候如果你想要换成 Ogg 播放器就得改 GameManager 的代码

依赖倒置的做法是

游戏管理器(GameManager) -> 依赖一个抽象的音频播放器(IAudioPlayer)

这样 GameManager 根本不管你到底是 mp3 还是 ogg,它只认识 IAudioPlayer,可以随时切换

  • 高层模块(核心逻辑)不再被底层实现“绑架”

  • 底层模块可以轻松替换或拓展(比如你可以写个新播放器)

  • 抽象成为中间“契约”,代码结构更清晰,更解耦

例子

我们举个例子!比如你有一个 AnimalFeeder 的类,它要喂不同的动物,但不能死死地写死是喂“猫”还是“狗”:

csharp
// 抽象接口
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)和不使用抽象类:

gdscript
# 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,完全不知道 CatDog 的细节,就完美符合 DIP

Godot 4.4 及以下(不支持抽象类) 没有抽象类,

可以用 鸭子类型(duck typing)来近似实现:

gdscript
# 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)」而不是具体类,思想是对的!当然,这种写法缺少编译期保障

还有一种做法是常见的多态做法,让高层模块依赖于低层的基类,而不是某个子类,用普通类来当抽象类用,通过多态实现行为复用

gdscript
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() # 喂动物,不管具体是谁!