Refactoring Task
Overviewβ
In this exercise, you will analyze a Notifier class implementation in which an external dependency (Mailer) is instantiated with new each time it is needed.
Identify the design issues this introduces, and propose refactoring strategies that improve flexibility and testability.
Initial Codeβ
The following code defines a Notifier class responsible for sending notifications to users.
It directly instantiates the Mailer internally, which works initially, but could lead to maintainability and extensibility problems over time.
- TypeScript
- PHP
- Python
class Mailer {
send(to: string, message: string) {
console.log(`γ‘γΌγ«ιδΏ‘: ${to} => ${message}`);
}
}
class Notifier {
notify(userId: string, content: string) {
const mailer = new Mailer();
const formatted = `[ιη₯] ${content}`;
mailer.send(`${userId}@example.com`, formatted);
}
}
<?php
class Mailer {
public function send(string $to, string $message): void {
echo "γ‘γΌγ«ιδΏ‘: {$to} => {$message}\n";
}
}
class Notifier {
public function notify(string $userId, string $content): void {
$mailer = new Mailer();
$formatted = "[ιη₯] {$content}";
$mailer->send("{$userId}@example.com", $formatted);
}
}
class Mailer:
def send(self, to: str, message: str):
print(f"γ‘γΌγ«ιδΏ‘: {to} => {message}")
class Notifier:
def notify(self, user_id: str, content: str):
mailer = Mailer()
formatted = f"[ιη₯] {content}"
mailer.send(f"{user_id}@example.com", formatted)
Question 1: What are the design issues in this code?β
List and explain the design problems based on the following aspects:
- Testability (Is it possible to replace
Mailerfor testing?) - Flexibility (What if the type of mailer needs to be changed?)
- Dependency management (Is
Notifiertoo tightly coupled toMailer?) - Conformance to design principles (e.g., the Dependency Inversion Principle)
Question 2: How should the design be improved?β
Propose improvements based on the following considerations:
- How can
Notifierbe decoupled from the internal instantiation ofMailer? - How can the
Mailerbe replaced during unit testing? - What benefits arise from applying Dependency Injection?
Example: Design Pattern Candidatesβ
| Pattern Name | Purpose and Effects |
|---|---|
Factory Method | Extracts object creation logic and allows switching implementations as needed |
Abstract Factory | Provides a group of related objects (e.g., Mailer, Logger) as a unified interface |
Builder | Separates construction steps and enables flexible, step-by-step initialization |
Singleton | Ensures only one instance exists globally and is reused across the system |
Dependency Injection | Injects dependencies externally for better flexibility, testability, and decoupling |
Optional Extensionsβ
- If notification methods include
SlackorLINEin addition to email, how would you adapt the design? - If the test environment requires a mock mailer that logs instead of sending, how would you design for this flexibility?
Suggested Output Format (for review or study groups)β
- List of at least three structural problems
- Refactoring approach and the reason for choosing specific patterns
- Design rationale and improvements (with optional sketches or pseudocode)