# SOLID Exercises

## Exercise 1: SRP — Split the User Module

**Task:** This class does validation, persistence, and notification. Split it into 3 classes (Validator, Repository, Notifier) and have a service orchestrate them.

```typescript
class UserService {
  createUser(data: any) {
    if (!data.email?.includes('@')) throw new Error('Invalid email');
    const user = { id: generateId(), ...data };
    db.save('users', user);
    emailService.send(user.email, 'Welcome!');
    return user;
  }
}
```

**Validation:**
- [ ] At least 3 focused classes
- [ ] Service orchestrates, doesn't implement validation/DB/email
- [ ] Each class has one clear responsibility

**Hints:**
1. UserValidator.validate(data) throws or returns
2. UserRepository.save(user)
3. EmailNotifier.sendWelcome(email)
4. UserService.createUser calls all three

---

## Exercise 2: OCP — Pluggable Exporters

**Task:** You have a Report class that exports to CSV. Add JSON and XML export without modifying the Report class. Use a strategy/exporter interface.

**Validation:**
- [ ] New exporters added without changing Report
- [ ] Report depends on an exporter interface
- [ ] At least 2 exporter implementations

**Hints:**
1. interface Exporter { export(data: Data): string }
2. class CsvExporter implements Exporter
3. class JsonExporter implements Exporter
4. Report receives Exporter in constructor

---

## Exercise 3: LSP — Fix the Violation

**Task:** This violates LSP. Redesign so Bird subtypes are substitutable, or explain why a different hierarchy is better.

```typescript
class Bird { fly() { console.log('Flying'); } }
class Sparrow extends Bird { }
class Penguin extends Bird { fly() { throw new Error("Can't fly"); } }
```

**Validation:**
- [ ] No subtype surprises callers of the base
- [ ] Either fix hierarchy or use composition

**Hints:**
1. Penguin shouldn't extend Bird if Bird guarantees fly()
2. Consider: FlyingBird vs Bird, or Bird has optional fly capability
3. Composition: Bird has FlyBehavior (strategy)

---

## Exercise 4: ISP — Segregate the Interface

**Task:** This interface forces printers to implement scan/fax. Split it so a simple printer only implements print.

```typescript
interface Machine {
  print(doc: string): void;
  scan(doc: string): void;
  fax(doc: string): void;
}
```

**Validation:**
- [ ] At least Printer, Scanner, Faxer (or combined) interfaces
- [ ] SimplePrinter implements only Printer
- [ ] No no-op methods required

**Hints:**
1. interface Printer { print(doc: string): void }
2. interface Scanner { scan(doc: string): void }
3. MultiFunctionDevice implements both
4. SimplePrinter implements Printer only

---

## Exercise 5: DIP — Inject the Logger

**Task:** Refactor OrderProcessor to receive its logger via constructor. Define a Logger interface. Create ConsoleLogger and MockLogger implementations.

**Validation:**
- [ ] OrderProcessor depends on Logger interface
- [ ] Logger injected in constructor
- [ ] Easy to test with MockLogger

**Hints:**
1. interface Logger { log(msg: string): void }
2. constructor(private logger: Logger)
3. MockLogger stores messages for assertions
