Skip to content

Commit 45b207e

Browse files
committed
Position OOP as deliberate choice, add FP contrasts
- Main README: Add FP vs OOP comparison with pragmatism callout - Classes: Position as one option among many - Interfaces: Add function vs class comparison - Polymorphism: Add discriminated unions as FP alternative - Composition: Include function composition alongside object composition
1 parent e446bfe commit 45b207e

File tree

6 files changed

+139
-16
lines changed

6 files changed

+139
-16
lines changed

exercises/01.classes/README.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ class User {
4343
```
4444

4545
<callout-info>
46-
Classes in TypeScript are similar to classes in Java or C#. If you know those
47-
languages, you'll feel right at home.
46+
Classes aren't the only way to organize code. Many modern codebases (React,
47+
functional libraries) prefer plain objects and functions. This workshop
48+
teaches OOP as one powerful option—valuable knowledge even if you primarily
49+
use functional patterns.
4850
</callout-info>
4951

5052
In this exercise, you'll create and use classes.

exercises/03.interfaces-and-classes/01.solution.implementing-interfaces/README.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,27 @@ they can be used interchangeably.
66
🦉 The interface defines the contract (`pay()` method), and each class provides
77
its own implementation. This allows different payment methods to work through the
88
same interface.
9+
10+
## Interfaces: Classes vs Functions
11+
12+
Interfaces can define contracts for both classes and functions:
13+
14+
```ts
15+
// OOP approach - interface with classes
16+
interface PaymentProcessor {
17+
process(amount: number): boolean
18+
}
19+
class StripeProcessor implements PaymentProcessor {
20+
process(amount: number) {
21+
return true
22+
}
23+
}
24+
25+
// FP approach - function type
26+
type PaymentProcessor = (amount: number) => boolean
27+
const stripeProcessor: PaymentProcessor = (amount) => true
28+
```
29+
30+
Both approaches achieve polymorphism—just through different mechanisms. The OOP
31+
approach bundles state and behavior; the FP approach keeps functions separate
32+
from data.

exercises/05.polymorphism/README.mdx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,35 @@ function printArea(shape: Shape) {
5656
printArea(new Circle(5)) // ✅ Circle is substitutable for Shape
5757
```
5858

59+
## Polymorphism: OOP vs FP
60+
61+
OOP achieves polymorphism through class hierarchies. Functional programming
62+
achieves it through discriminated unions:
63+
64+
```ts
65+
// OOP approach - class hierarchy
66+
abstract class Shape {
67+
abstract area(): number
68+
}
69+
class Circle extends Shape {
70+
area() {
71+
return Math.PI * this.radius ** 2
72+
}
73+
}
74+
75+
// FP approach - discriminated union
76+
type Shape =
77+
| { type: 'circle'; radius: number }
78+
| { type: 'rectangle'; width: number; height: number }
79+
80+
const area = (s: Shape) =>
81+
s.type === 'circle' ? Math.PI * s.radius ** 2 : s.width * s.height
82+
```
83+
84+
**OOP** is extensible: add new classes without changing existing code.
85+
**FP** makes all cases explicit: add a new variant and the compiler tells you
86+
everywhere you need to handle it.
87+
5988
## Benefits
6089

6190
- **Flexibility** - Write code that works with multiple types

exercises/06.composition-vs-inheritance/FINISHED.mdx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,26 @@
55
You learned:
66

77
- 🔗 **Inheritance** - "is-a" relationships, code reuse through hierarchy
8-
- 🧩 **Composition** - "has-a" relationships, combining behaviors
8+
- 🧩 **Object Composition** - "has-a" relationships, combining behaviors
9+
- 🔄 **Function Composition** - combining functions into pipelines (FP style)
910
- 🎯 **When to use each** - Choose based on the relationship type
1011
- 💡 **Favor composition** - More flexible and easier to change
1112

1213
🦉 Key differences:
1314

1415
- **Inheritance**: Tight coupling, fixed at compile time, "is-a"
15-
- **Composition**: Loose coupling, flexible at runtime, "has-a"
16+
- **Composition**: Loose coupling, flexible at runtime, "has-a" or "transforms"
1617

1718
💰 General rule: Use inheritance when you have a clear "is-a" relationship and
18-
want to share implementation. Use composition when you need flexibility or have
19-
a "has-a" relationship.
20-
21-
🦉 Both are valuable tools—the key is choosing the right one for each situation.
22-
23-
Congratulations! You've completed the Object-Oriented TypeScript workshop!
19+
want to share implementation. Use composition (object or function) when you need
20+
flexibility.
21+
22+
<callout-info>
23+
OOP is a tool, not a religion. Many successful TypeScript codebases use a mix
24+
of OOP and functional patterns. Use the right tool for each situation—sometimes
25+
that's a class, sometimes it's a function, and often it's a combination.
26+
</callout-info>
27+
28+
Congratulations! You've completed the Object-Oriented TypeScript workshop! You
29+
now understand both OOP patterns and when functional alternatives might be a
30+
better fit.

exercises/06.composition-vs-inheritance/README.mdx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,32 @@ Use composition when:
4747
- You need to combine multiple behaviors
4848
- The relationship is "has-a" not "is-a"
4949

50+
## Three Types of Composition
51+
52+
1. **Inheritance** (is-a): `Dog extends Animal`
53+
2. **Object Composition** (has-a): `Car` has an `Engine`
54+
3. **Function Composition** (transforms): combine functions into pipelines
55+
56+
```ts
57+
// Object composition (OOP)
58+
class EmailService {
59+
constructor(private logger: Logger) {}
60+
send(email: Email) {
61+
this.logger.log('Sending...')
62+
// ...
63+
}
64+
}
65+
66+
// Function composition (FP)
67+
const sendEmail = (logger: Logger) => (email: Email) => {
68+
logger.log('Sending...')
69+
// ...
70+
}
71+
```
72+
73+
Both achieve the same goal—loose coupling and flexibility—through different
74+
mechanisms.
75+
5076
## When to Use Each
5177

5278
**Inheritance:**
@@ -55,16 +81,17 @@ Use composition when:
5581
- `Circle` is a `Shape`
5682
- `Manager` is an `Employee`
5783

58-
**Composition:**
84+
**Composition (object or function):**
5985

6086
- `Car` has an `Engine`
6187
- `Computer` has a `CPU`
6288
- `User` has a `Profile`
89+
- Data transformation pipelines ✅
6390

6491
<callout-info>
65-
Favor composition over inheritance when possible. Composition is more flexible
66-
and easier to change. Use inheritance when you truly have an "is-a"
67-
relationship.
92+
Favor composition over inheritance when possible. Both object composition and
93+
function composition are more flexible and easier to change than inheritance.
94+
Use inheritance when you truly have an "is-a" relationship.
6895
</callout-info>
6996

7097
In this exercise, you'll choose between composition and inheritance.

exercises/README.mdx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,42 @@ We'll cover:
1515
5. **Polymorphism** - Substitutability and why it matters
1616
6. **Composition vs Inheritance** - Practical decision-making
1717

18+
## OOP vs Functional Programming
19+
20+
TypeScript supports multiple paradigms. This workshop teaches OOP because it's
21+
valuable knowledge—you'll encounter it in many codebases and libraries. But you
22+
should know the alternative:
23+
24+
**OOP organizes around objects** - data and behavior bundled together:
25+
26+
```ts
27+
class ShoppingCart {
28+
private items: Product[] = []
29+
addItem(product: Product) {
30+
this.items.push(product)
31+
}
32+
getTotal() {
33+
return this.items.reduce((sum, p) => sum + p.price, 0)
34+
}
35+
}
36+
```
37+
38+
**FP organizes around functions** - data and transformations separate:
39+
40+
```ts
41+
type Cart = readonly Product[]
42+
const addItem = (cart: Cart, product: Product): Cart => [...cart, product]
43+
const getTotal = (cart: Cart): number => cart.reduce((sum, p) => sum + p.price, 0)
44+
```
45+
46+
**Both are valid.** OOP excels at encapsulation and polymorphism. FP excels at
47+
data transformation and immutability. Choose based on your problem domain.
48+
1849
<callout-info>
19-
OOP isn't about using classes everywhere—it's about organizing code so that
20-
complexity is manageable and changes are isolated.
50+
Pragmatism over purity! JavaScript isn't Haskell—no JS program is 100% purely
51+
functional. If a class solves your problem elegantly, use it. The goal is to
52+
have both tools in your toolkit and choose wisely. Modern React codebases
53+
often lean functional, but many successful projects use classes too.
2154
</callout-info>
2255

2356
By the end of this workshop, you'll be able to:
@@ -27,5 +60,6 @@ By the end of this workshop, you'll be able to:
2760
- Apply inheritance appropriately
2861
- Understand when to prefer composition
2962
- Write polymorphic code that's flexible and testable
63+
- **Make informed decisions** about when OOP or FP is the better fit
3064

3165
Let's build some objects! 🧱

0 commit comments

Comments
 (0)