Week 6 Lesson

SOLID Part 2: Interfaces, Dependency Inversion, and Testability

Design improves when high-level rules stop depending directly on low-level details.

Interface Segregation PrincipleDependency Inversion Principledependency injectionabstractionprovidermock

Learning Objectives

  • Apply Interface Segregation Principle.
  • Apply Dependency Inversion Principle.
  • Use dependency injection to make software easier to test.
  • Design a notification system with replaceable providers.

Lecture Notes

The Interface Segregation Principle says clients should not be forced to depend on methods they do not use. A giant Worker interface with print, scan, fax, staple, email, and archive methods creates problems for simple devices or services that only support one behavior. Smaller focused interfaces let classes depend only on the capabilities they need.

The Dependency Inversion Principle says high-level policy should not depend directly on low-level implementation details. A BillingService is high-level policy. Stripe, PayPal, file logs, SMTP servers, and SQL clients are details. If BillingService constructs those details directly, it becomes hard to test and hard to change. If BillingService depends on PaymentGateway and ReceiptSender abstractions, the design is more stable.

Dependency injection means dependencies are provided from the outside rather than created inside the class. This can be as simple as passing an object into a constructor. Students sometimes think dependency injection requires a large framework. It does not. The core idea is that an object should receive collaborators instead of secretly building them.

This principle strongly affects testing. If OrderService directly calls a real email provider, every test may send emails or require network access. If OrderService depends on a NotificationSender interface, tests can use a fake sender that records what would have been sent. Good design makes important behavior testable without expensive external systems.

Dependency inversion does not mean every dependency must be abstracted. Abstract what is volatile, expensive, external, or policy-sensitive. A Math.max call does not need an interface. A payment gateway probably does. Design is about judgment and tradeoffs, not rituals.

Worked Example: Injecting a notification dependency

Before
class OrderService {
  completeOrder(orderId: string) {
    // Business rule mixed with direct external detail
    const email = new SmtpEmailSender('smtp.example.com');
    email.send('customer@example.com', 'Your order is complete');
  }
}
After / Better
interface NotificationSender {
  send(to: string, message: string): Promise<void>;
}

class OrderService {
  constructor(private notifications: NotificationSender) {}

  async completeOrder(orderId: string, customerEmail: string) {
    // Business rule can be tested with a fake NotificationSender
    await this.notifications.send(customerEmail, 'Your order is complete');
  }
}
Design lesson: OrderService no longer knows how notifications are delivered. Email, SMS, push, and test fakes can all implement the same interface.

Lab: Notification System Design

Scenario: A campus app must send announcements by email today, SMS next semester, and push notifications later.

Steps

  1. Define a focused NotificationSender interface.
  2. Create at least two implementations.
  3. Create a service that depends on the interface, not implementations.
  4. Write a fake implementation for tests.
  5. Explain how ISP and DIP appear in your design.

Deliverables

  • Interface design
  • Implementation code or pseudocode
  • Test fake
  • Explanation

Assignment

Create a small service that depends on at least two abstractions. Explain which details are hidden behind those abstractions and why.

Discussion Prompts

  • When should you not create an interface?
  • How does dependency injection help tests?
  • What is the difference between a policy and a detail?

Week 6 Quiz

Use these as quick checks after the lesson.

Question 1

ISP says clients should not depend on:

Question 2

DIP says high-level policy should depend on:

Question 3

Dependency injection means:

Question 4

A good dependency to abstract is often:

Question 5

A fake object in tests can:

Student Notes

Saved in this browser only.