SOLID Principles in 59 Seconds

L — Liskov Substitution Principle

3 of 5
The principle

Subtypes should be substitutable for their base types.

A subtype must preserve the behavior callers rely on from its base type. A matching method signature is not enough if the returned value means something different.

Sender is the base type, and EmailSender is its first implementation. Its send method reports whether the email provider accepted the request. NotificationDispatcher relies on that meaning.

Now the app adds PushNotificationSender. The dispatcher calls it through the same Sender interface and expects the same answer: did the provider accept the request? The push sender passes along its provider’s answer.

Current service
OrderShipped eventEvent ID identifies this event
Email service v1Handles repeated event IDs
Replacement service
Same OrderShipped eventSame event ID
Email service v2Still avoids duplicate email
Replacing a service must preserve the behavior the rest of the system relies on.
Language

Unchanged contract and caller

interface Sender {
  // Report whether the provider accepted the request.
  send(message: string): boolean
}

class EmailSender implements Sender {
  send(message: string): boolean {
    return emailApi.send(message)
  }
}

class NotificationDispatcher {
  constructor(private senders: ReadonlyMap<string, Sender>) {}

  send(message: string, channel: string): boolean {
    const sender = this.senders.get(channel)
    if (!sender) throw new Error('Unknown channel: ' + channel)
    return sender.send(message)
  }
}

Email only

const senders = new Map<string, Sender>([
  ['email', new EmailSender()],
])
const dispatcher = new NotificationDispatcher(senders)
dispatcher.send('Order shipped', 'email')

The dispatcher receives the email provider’s acceptance result.

Email and push

class PushNotificationSender implements Sender {
  send(message: string): boolean {
    return pushApi.send(message)
  }
}

const senders = new Map<string, Sender>([
  ['email', new EmailSender()],
  ['push', new PushNotificationSender()],
])
const dispatcher = new NotificationDispatcher(senders)
dispatcher.send('Order shipped', 'email')
dispatcher.send('Order shipped', 'push')

The same dispatcher receives the push provider’s acceptance result.