依赖倒置原则

144 阅读3分钟

依赖倒置原则

依赖倒置原则(Dependence Inversion Principle,DIP)

核心思想:依赖于抽象,不能依赖于具体实现。高层模块不应该依赖底层模块,二者都该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象。

高层模块就是调用端,低层模块就是具体实现类,抽象就是指接口或抽象类,细节也是指具体的实现类,也就是说只依赖抽象编程。

案例分析:

案例一:

“开放封闭原则”的例子中,把各种类型的动物抽象成一个抽象类 Animal,并定义统一的方法 moving(),这也是遵循了依赖倒置原则。 Zoo类是一个高层模块,Zoo 类中的 displayActivity 方法依赖的是动物的抽象类 Animal 和其定义的抽象方法 moving(),这就是高层模块依赖其抽象,而不是依赖细节的表现。

案例二:

狗吃肉,鱼吃草,鸟吃虫。设计如下:

class Meat {}
class Grass {}
class Dog {
  eat(meat: Meat) {}
}
class Fish {
  eat(grass: Grass) {}
}

这样设计有几个问题:

  • 每一种动物,都需要为其定义一个食物类,高度依赖于细节
  • 每一种动物只能吃一种东西,这与实现想违背;如猫不仅喜欢吃老鼠,还喜欢吃鱼;不仅鱼喜欢吃草,牛也喜欢吃草。

修改成符合依赖倒置原则的设计:抽象出一个食物(Food)类,动物(Animal)依赖食物的抽象类 Food,而不应该依赖基体的细节(具体的食物)。

abstract class Food {
  constructor(public name: string) {}
  getName() {
    return this.name
  }
  abstract category(): string
  abstract nutrient(): string
}
abstract class Animal {
  constructor(public name: string) {}
  abstract checkFood(food: Food): boolean
  eat(food: Food) {
    if (this.checkFood(food)) {
      console.log(`${this.name} eat ${food.getName()}`)
      return
    }
    console.log(`${this.name} not eat ${food.getName()}`)
  }
}
class Dog extends Animal {
  constructor() {
    super('狗')
  }
  checkFood(food: Food) {
    return food.category() === 'meat'
  }
}
class Swallow extends Animal {
  constructor() {
    super('燕子')
  }
  checkFood(food: Food) {
    return food.category() === 'insect'
  }
}
class Meat extends Food {
  constructor() {
    super('肉')
  }
  category() {
    return 'meat'
  }
  nutrient() {
    return 'protein'
  }
}
class Insect extends Food {
  constructor() {
    super('虫子')
  }
  category() {
    return 'insect'
  }
  nutrient() {
    return 'protein'
  }
}
function test() {
  const dog = new Dog()
  const swallow = new Swallow()
  const meat = new Meat()
  const insect = new Insect()
  dog.eat(meat)
  dog.eat(insect)
  swallow.eat(meat)
  swallow.eat(insect)
}
test()

在上面的例子中,动物抽象出一个父类 Animal,食物也抽象出一个抽象类 Food。Animal 抽象不依赖于细节(具体的食物类),具体的动物(如 Dog)也不依赖于细节(具体的食物类),只依赖抽象编程。

案例二:

一个高层模块(MessageService)直接依赖于低层模块(EmailServiceSMSService),违反了依赖倒置原则。

class EmailService {
    sendEmail(message: string, to: string) {
        console.log(`Sending email to ${to}: ${message}`);
    }
}
​
class SMSService {
    sendSMS(message: string, to: string) {
        console.log(`Sending SMS to ${to}: ${message}`);
    }
}
​
class MessageService {
    private emailService: EmailService = new EmailService();
    private smsService: SMSService = new SMSService();
​
    sendMessage(message: string, to: string, messageType: 'email' | 'sms') {
        if (messageType === 'email') {
            this.emailService.sendEmail(message, to);
        } else if (messageType === 'sms') {
            this.smsService.sendSMS(message, to);
        }
    }
}

要遵循依赖倒置原则,引入一个抽象层(IMessageService接口),使高层模块(MessageService)依赖于抽象而不是具体的细节。

interface IMessageService {
    sendMessage(message: string, to: string): void;
}
​
class EmailService implements IMessageService {
    sendMessage(message: string, to: string) {
        console.log(`Sending email to ${to}: ${message}`);
    }
}
​
class SMSService implements IMessageService {
    sendMessage(message: string, to: string) {
        console.log(`Sending SMS to ${to}: ${message}`);
    }
}
​
class MessageService {
    private messageService: IMessageService;
​
    constructor(messageService: IMessageService) {
        this.messageService = messageService;
    }
​
    sendMessage(message: string, to: string) {
        this.messageService.sendMessage(message, to);
    }
}
​

使用依赖倒置原则的代码更灵活、易于扩展。例如,如果需要引入一个新的消息服务(如推送通知),只需创建一个新的类实现IMessageService接口,而不需要修改MessageService类。