Week 4 Lesson
Object-Oriented Design That Does Not Fight You
Classes should protect rules, express concepts, and collaborate cleanly.
Learning Objectives
- Design classes around responsibilities rather than random data containers.
- Use encapsulation to protect invariants.
- Choose composition when inheritance would create fragile hierarchies.
- Model a small domain with objects and relationships.
Lecture Notes
Object-oriented design is often taught as syntax first: class, object, constructor, method, inheritance. Syntax matters, but the deeper goal is modeling responsibility. A class should represent an idea in the system and protect the rules connected to that idea. A BankAccount class is not just a bag of fields. It should protect the rule that withdrawals cannot violate the account policy.
Encapsulation means hiding internal details and exposing safe operations. Public fields make it easy for any part of the program to create invalid state. If any code can set account.balance = -500, then the account object does not protect its own rules. A better design uses methods such as deposit and withdraw, where validation can live next to the state it protects.
Inheritance is useful when there is a true substitutable relationship. A SavingsAccount may be a kind of Account if it can be used wherever an Account is expected. But students often use inheritance just to share code. This creates awkward hierarchies: FlyingEnemy extends Enemy, ArmoredFlyingEnemy extends FlyingEnemy, PoisonArmoredFlyingEnemy extends ArmoredFlyingEnemy. Composition can be cleaner: an Enemy has MovementBehavior, DefenseBehavior, and AttackBehavior.
Polymorphism lets different objects respond to the same message in different ways. This is powerful when paired with interfaces. A ReportExporter can work with CsvExporter, PdfExporter, or HtmlExporter without knowing their internal details. This makes the system easier to extend and easier to test.
A strong object-oriented design has objects that do real work. A common beginner mistake is creating data-only classes and then putting all behavior in a giant manager class. This is called an anemic model in many contexts. Not every project needs a rich domain model, but important rules should not be scattered randomly across controllers, UI handlers, and utility functions.
Worked Example: Protecting object rules
class BankAccount {
public balance = 0;
}
const account = new BankAccount();
account.balance = -1000; // Invalid state, but nothing stops it.class BankAccount {
private balance = 0;
deposit(amount: number) {
if (amount <= 0) throw new Error('Deposit must be positive');
this.balance += amount;
}
withdraw(amount: number) {
if (amount <= 0) throw new Error('Withdrawal must be positive');
if (amount > this.balance) throw new Error('Insufficient funds');
this.balance -= amount;
}
currentBalance() {
return this.balance;
}
}Lab: Domain Modeling with Objects
Scenario: Design an inventory system for a small campus bookstore.
Steps
- Identify core objects: Product, InventoryItem, Supplier, PurchaseOrder.
- Write the rules each object must protect.
- Create a class diagram.
- Implement at least three classes with validation.
- Write a short note on where composition is better than inheritance.
Deliverables
- Class diagram
- Starter code
- Rule list
- Composition explanation
Assignment
Design a mini game object model with Player, Enemy, Weapon, Ability, and Level. Use composition for at least one behavior.
Discussion Prompts
- When is inheritance appropriate?
- What rules should an object protect?
- Are manager classes always bad?
Week 4 Quiz
Use these as quick checks after the lesson.
Question 1
Encapsulation helps by:
Question 2
Composition means:
Question 3
Inheritance is risky when:
Question 4
An invariant is:
Question 5
A data-only class plus one giant manager can lead to:
Student Notes
Saved in this browser only.