七爪源码:依赖倒置原则

软件易变。 事情一直在变化。 库过时了,框架被替换了。 当您的代码与这些第三方工具高度耦合时,此类更改会很痛苦。 依赖倒置原则帮助我们减轻这种痛苦。

识别违规行为

DIP 可以概括为“不依赖于可以改变的事物”。

您的组件(类、函数等)不应依赖于其他组件,而应依赖于接口。

检查这个例子:

import { TwilioNotificationService } from 'src/infra/notification-service'

class PaymentService {
  async pay (userId: string) {
    // { ... executes payment logic }
    // call external service
    await TwilioNotificationService.sendPaymentReceivedNotification(userId)
  }
}

// infra/notification-service.ts
class TwilioNotificationService {
  async sendPaymentReceivedNotification(userId: string) { // sends notification }
}

众所周知,软件是易变的。 一旦您将这个对 Twilio API 的直接调用添加到您的代码中,您就与他们签署了一份合同,表明您将响应他们对其合同所做的任何更改。 例如,如果 userId 更改为对象而不是字符串,则您有责任更改调用此 API 的每一行代码。

Twilio 团队不会(也绝对不能)更改他们的 API,因为您需要它是一个字符串。


重构

让我们反转这个依赖

首先,我们必须按照我们需要的方式定义我们的合约。 接收字符串的 NotificationService? 那好吧。

export interface PaymentNotificationService {
  sendPaymentReceivedNotification: (userId: string) => Promise<{ ok: boolean }>
}

现在,而不是在我们的服务中导入具体的实现。 我们将从类构造函数接收它

import { PaymentNotificationService } from 'src/contracts/notification-service.ts'

class PaymentService {
  private readonly notificationService: PaymentNotificationService
  
  // the dependency
  constructor (notificationService: PaymentNotificationService) {
    this.notificationService = notificationService
  }

  async pay (userId: string) {
    // execute any business rules
    
    // call the dependency service
    await this.notificationService.sendPaymentReceivedNotification(userId)
  }
}

// contracts/notification-service.ts
export interface PaymentNotificationService {
  sendPaymentReceivedNotification: (userId: string) => Promise<{ ok: boolean }>
}

现在我们的服务不需要知道通知服务是如何或由谁来实现的。 但它知道它不会改变。 无论在哪里实施,都必须履行合同。

import { PaymentNotificationService } from 'src/contracts/notification-service'

// educational purposes, this is not a real lib I guess
import TwilioSDK from 'twilio-sdk'

export class TwilioNotificationService implements PaymentNotificationService {
  async sendPaymentReceivedNotification (userId: string): Promise<{ ok: boolean }> {
    const user = { userId: userId }
    try {
      const sendSuccess = await TwilioSDK.sendNotification('payment-received', user)
      if (sendSuccess) return { ok: true }
      return { ok: false }
    } catch {
      return { ok: false }
    }
  }
}

使用它对我们有利,我们可以替换 Twilio 服务来实现该合约的任何其他实现。

import TwilioNotificationService from './twilio'
import MailChimpNotificationService from './mailChimp'
import WhatsAppNotificationService from './whatsapp'

import PaymentService from './payment.ts'

const twilioService = new TwilioNotificationService()
const mailChimpService = new MailChimpNotificationService()
const whatsAppService = new WhatsAppNotificationService()

// all the following implementations are valid
const PaymentServiceWithTwilio = new PaymentService(twilioService)
const PaymentServiceWithMailChimp = new PaymentService(mailChimpService)
const PaymentServiceWithWhatsApp = new PaymentService(whatsAppService)


多少才够?

用鲍勃叔叔的话来说,这个原则背后的目的很简单:“不要提及任何具体和易变的东西的名字”。

最后一个词非常重要,因此我们不会弄错整个事情并为 String 或 Array 等语言类创建抽象。

这些东西不是易变的,也许你的一些组件也不是。组件是否易失由您决定。

您可能决定反转在应用程序的整个生命周期中都不会改变的类的依赖关系,但相反的情况也是有效的。想想看。是否值得创建接口然后将实例传递给类的构造函数的复杂性?只有你能说。


结论

坚实的原则是帮助您构建可扩展软件的指导方针。指导方针,而不是规则。使用它对你有利,而不是对你不利。所有 SOLID 原则的实现都将涉及复杂性和可读性之间的某种权衡。

在这种情况下,我们正在准备我们的代码,这样我们就不会受到通知发送基础设施未来变化的影响。为此,我们的服务必须不知道该通知是如何在基础设施层发送的。好吗?

在这种情况下,它是。但对你来说可能不是这样。当您认为隐藏细节有利于灵活性时使用它。


关注七爪网,获取更多APP/小程序/网站源码资源!

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章