依赖倒置原则
依赖倒置原则(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)直接依赖于低层模块(EmailService和SMSService),违反了依赖倒置原则。
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类。