Week 9 Lesson
Design Patterns Part 1: Strategy, Factory, Observer, Singleton
Patterns are tools for recurring design problems, not decorations.
Learning Objectives
- Explain what a design pattern is and when to avoid forcing one.
- Use Strategy for interchangeable behavior.
- Use Factory for object creation decisions.
- Use Observer for event notification.
- Explain why Singleton should be used carefully.
Lecture Notes
A design pattern is a named solution to a common design problem. Patterns help developers talk about design quickly. If someone says “use Strategy here,” the team understands that behavior should be interchangeable behind a shared interface. But patterns are not trophies. A forced pattern makes code harder to understand.
Strategy is useful when an algorithm or behavior varies. In a game, enemies may move differently: walking, flying, teleporting, swarming. In a store, discounts may vary: student discount, seasonal discount, loyalty discount. Instead of giant conditionals, a strategy interface lets each behavior live in its own class or function.
Factory handles object creation when creation logic is complex or should be centralized. A factory might create different enemy types based on level configuration or create payment processors based on user choice. The goal is to avoid scattering construction decisions across the entire system.
Observer is useful when one part of the system needs to notify multiple others without knowing exactly who they are. A TaskService might publish TaskCompleted events. A badge system, notification system, and analytics system can subscribe. This reduces coupling, but it can also make behavior harder to trace if overused.
Singleton ensures only one instance of something exists. It is often overused. Global singletons can hide dependencies and make tests difficult. Some singletons are reasonable for configuration or logging, but students should be cautious. A dependency passed explicitly is usually easier to understand and test than a hidden global.
Worked Example: Strategy for enemy movement
function moveEnemy(enemy: Enemy) {
if (enemy.type === 'walker') enemy.x += 1;
else if (enemy.type === 'flyer') { enemy.x += 2; enemy.y -= 1; }
else if (enemy.type === 'teleporter') enemy.x += 10;
}interface MovementStrategy {
move(enemy: Enemy): void;
}
class WalkMovement implements MovementStrategy {
move(enemy: Enemy) { enemy.x += 1; }
}
class FlyMovement implements MovementStrategy {
move(enemy: Enemy) { enemy.x += 2; enemy.y -= 1; }
}
class Enemy {
constructor(public movement: MovementStrategy) {}
update() { this.movement.move(this); }
}Lab: Pattern Choice Lab
Scenario: A mini game needs enemies with different movement and attack styles.
Steps
- Identify which behavior varies.
- Choose Strategy or Factory where appropriate.
- Implement two movement behaviors.
- Create a factory that builds enemies from configuration.
- Explain one pattern you chose not to use.
Deliverables
- Pattern implementation
- Short pattern justification
- One warning about overuse
Assignment
Implement Strategy, Factory, or Observer in a small example. Include a paragraph explaining what problem the pattern solves.
Discussion Prompts
- Why are patterns sometimes overused?
- When is a switch statement acceptable?
- Why can Singleton hurt tests?
Week 9 Quiz
Use these as quick checks after the lesson.
Question 1
Strategy is useful when:
Question 2
Factory helps manage:
Question 3
Observer is useful for:
Question 4
Singleton should be used carefully because:
Question 5
A design pattern should be used when:
Student Notes
Saved in this browser only.