Added AI skills
This commit is contained in:
317
.agents/skills/clean-code-principles/rules/core-composition.md
Normal file
317
.agents/skills/clean-code-principles/rules/core-composition.md
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
id: core-composition
|
||||
title: Composition Over Inheritance
|
||||
category: core-principles
|
||||
priority: critical
|
||||
tags: [composition, inheritance, flexibility, design]
|
||||
related: [solid-srp-class, solid-dip-injection, core-encapsulation]
|
||||
---
|
||||
|
||||
# Composition Over Inheritance
|
||||
|
||||
Favor composing objects from smaller, focused pieces over building deep inheritance hierarchies. Composition provides more flexibility, better encapsulation, and avoids the fragile base class problem.
|
||||
|
||||
## Bad Example
|
||||
|
||||
```typescript
|
||||
// Anti-pattern: Deep inheritance hierarchy
|
||||
|
||||
class Animal {
|
||||
protected name: string;
|
||||
protected energy: number = 100;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
eat(amount: number): void {
|
||||
this.energy += amount;
|
||||
console.log(`${this.name} is eating. Energy: ${this.energy}`);
|
||||
}
|
||||
|
||||
sleep(hours: number): void {
|
||||
this.energy += hours * 10;
|
||||
console.log(`${this.name} slept for ${hours} hours. Energy: ${this.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
class Bird extends Animal {
|
||||
fly(): void {
|
||||
this.energy -= 20;
|
||||
console.log(`${this.name} is flying. Energy: ${this.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
class Duck extends Bird {
|
||||
swim(): void {
|
||||
this.energy -= 5;
|
||||
console.log(`${this.name} is swimming. Energy: ${this.energy}`);
|
||||
}
|
||||
|
||||
quack(): void {
|
||||
console.log(`${this.name} says quack!`);
|
||||
}
|
||||
}
|
||||
|
||||
class FlyingFish extends Animal {
|
||||
// Problem: Can't inherit from both Bird and Fish
|
||||
// Must duplicate flying code or create awkward hierarchy
|
||||
|
||||
swim(): void {
|
||||
this.energy -= 5;
|
||||
console.log(`${this.name} is swimming. Energy: ${this.energy}`);
|
||||
}
|
||||
|
||||
// Duplicated from Bird class!
|
||||
fly(): void {
|
||||
this.energy -= 20;
|
||||
console.log(`${this.name} is flying. Energy: ${this.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
class Penguin extends Bird {
|
||||
// Problem: Penguins can't fly but inherit fly()
|
||||
// Must override to throw error - LSP violation
|
||||
|
||||
fly(): void {
|
||||
throw new Error('Penguins cannot fly!');
|
||||
}
|
||||
|
||||
swim(): void {
|
||||
this.energy -= 5;
|
||||
console.log(`${this.name} is swimming. Energy: ${this.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
// More problems:
|
||||
// - What about a robot bird? It doesn't eat or sleep.
|
||||
// - What about a bat? It flies but isn't a bird.
|
||||
// - Every change to Animal affects all subclasses.
|
||||
// - Testing requires understanding entire hierarchy.
|
||||
```
|
||||
|
||||
## Good Example
|
||||
|
||||
```typescript
|
||||
// Correct approach: Composition with focused behaviors
|
||||
|
||||
// Define behaviors as interfaces
|
||||
interface Eater {
|
||||
eat(amount: number): void;
|
||||
}
|
||||
|
||||
interface Sleeper {
|
||||
sleep(hours: number): void;
|
||||
}
|
||||
|
||||
interface Flyer {
|
||||
fly(): void;
|
||||
}
|
||||
|
||||
interface Swimmer {
|
||||
swim(): void;
|
||||
}
|
||||
|
||||
interface Speaker {
|
||||
speak(): void;
|
||||
}
|
||||
|
||||
// Implement behaviors as standalone classes
|
||||
class StandardEater implements Eater {
|
||||
constructor(private entity: { name: string; energy: number }) {}
|
||||
|
||||
eat(amount: number): void {
|
||||
this.entity.energy += amount;
|
||||
console.log(`${this.entity.name} is eating. Energy: ${this.entity.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
class StandardSleeper implements Sleeper {
|
||||
constructor(private entity: { name: string; energy: number }) {}
|
||||
|
||||
sleep(hours: number): void {
|
||||
this.entity.energy += hours * 10;
|
||||
console.log(`${this.entity.name} slept for ${hours} hours. Energy: ${this.entity.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
class WingedFlyer implements Flyer {
|
||||
constructor(
|
||||
private entity: { name: string; energy: number },
|
||||
private energyCost: number = 20
|
||||
) {}
|
||||
|
||||
fly(): void {
|
||||
this.entity.energy -= this.energyCost;
|
||||
console.log(`${this.entity.name} is flying. Energy: ${this.entity.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
class AquaticSwimmer implements Swimmer {
|
||||
constructor(
|
||||
private entity: { name: string; energy: number },
|
||||
private energyCost: number = 5
|
||||
) {}
|
||||
|
||||
swim(): void {
|
||||
this.entity.energy -= this.energyCost;
|
||||
console.log(`${this.entity.name} is swimming. Energy: ${this.entity.energy}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Compose animals from behaviors
|
||||
class Duck implements Eater, Sleeper, Flyer, Swimmer, Speaker {
|
||||
public name: string;
|
||||
public energy: number = 100;
|
||||
|
||||
private eater: Eater;
|
||||
private sleeper: Sleeper;
|
||||
private flyer: Flyer;
|
||||
private swimmer: Swimmer;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.eater = new StandardEater(this);
|
||||
this.sleeper = new StandardSleeper(this);
|
||||
this.flyer = new WingedFlyer(this);
|
||||
this.swimmer = new AquaticSwimmer(this);
|
||||
}
|
||||
|
||||
eat(amount: number): void {
|
||||
this.eater.eat(amount);
|
||||
}
|
||||
|
||||
sleep(hours: number): void {
|
||||
this.sleeper.sleep(hours);
|
||||
}
|
||||
|
||||
fly(): void {
|
||||
this.flyer.fly();
|
||||
}
|
||||
|
||||
swim(): void {
|
||||
this.swimmer.swim();
|
||||
}
|
||||
|
||||
speak(): void {
|
||||
console.log(`${this.name} says quack!`);
|
||||
}
|
||||
}
|
||||
|
||||
// Penguin: swims but doesn't fly - no problem!
|
||||
class Penguin implements Eater, Sleeper, Swimmer, Speaker {
|
||||
public name: string;
|
||||
public energy: number = 100;
|
||||
|
||||
private eater: Eater;
|
||||
private sleeper: Sleeper;
|
||||
private swimmer: Swimmer;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.eater = new StandardEater(this);
|
||||
this.sleeper = new StandardSleeper(this);
|
||||
this.swimmer = new AquaticSwimmer(this);
|
||||
}
|
||||
|
||||
eat(amount: number): void {
|
||||
this.eater.eat(amount);
|
||||
}
|
||||
|
||||
sleep(hours: number): void {
|
||||
this.sleeper.sleep(hours);
|
||||
}
|
||||
|
||||
swim(): void {
|
||||
this.swimmer.swim();
|
||||
}
|
||||
|
||||
speak(): void {
|
||||
console.log(`${this.name} says squawk!`);
|
||||
}
|
||||
}
|
||||
|
||||
// Flying fish: swims and flies - easy!
|
||||
class FlyingFish implements Swimmer, Flyer {
|
||||
public name: string;
|
||||
public energy: number = 100;
|
||||
|
||||
private swimmer: Swimmer;
|
||||
private flyer: Flyer;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.swimmer = new AquaticSwimmer(this);
|
||||
this.flyer = new WingedFlyer(this, 30); // Different energy cost
|
||||
}
|
||||
|
||||
swim(): void {
|
||||
this.swimmer.swim();
|
||||
}
|
||||
|
||||
fly(): void {
|
||||
this.flyer.fly();
|
||||
}
|
||||
}
|
||||
|
||||
// Robot bird: flies but doesn't eat or sleep
|
||||
class RobotBird implements Flyer, Speaker {
|
||||
public name: string;
|
||||
public energy: number = 100;
|
||||
|
||||
private flyer: Flyer;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
this.flyer = new WingedFlyer(this, 10); // Efficient robot
|
||||
}
|
||||
|
||||
fly(): void {
|
||||
this.flyer.fly();
|
||||
}
|
||||
|
||||
speak(): void {
|
||||
console.log(`${this.name} says BEEP BOOP!`);
|
||||
}
|
||||
|
||||
recharge(): void {
|
||||
this.energy = 100;
|
||||
console.log(`${this.name} recharged to full energy.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Functions work with any entity that has the required behavior
|
||||
function makeEntityFly(flyer: Flyer): void {
|
||||
flyer.fly();
|
||||
}
|
||||
|
||||
function feedEntity(eater: Eater, amount: number): void {
|
||||
eater.eat(amount);
|
||||
}
|
||||
|
||||
// Works with duck, flying fish, or robot bird
|
||||
makeEntityFly(new Duck('Donald'));
|
||||
makeEntityFly(new FlyingFish('Nemo'));
|
||||
makeEntityFly(new RobotBird('R2D2'));
|
||||
|
||||
// Works with duck or penguin, but not robot bird (correctly!)
|
||||
feedEntity(new Duck('Donald'), 50);
|
||||
feedEntity(new Penguin('Pingu'), 50);
|
||||
// feedEntity(new RobotBird('R2D2'), 50); // Type error - RobotBird isn't an Eater
|
||||
```
|
||||
|
||||
## Why
|
||||
|
||||
1. **Flexibility**: Compose any combination of behaviors. No artificial hierarchy constraints.
|
||||
|
||||
2. **Avoids Diamond Problem**: No multiple inheritance issues. Just implement multiple interfaces.
|
||||
|
||||
3. **LSP Compliance**: No need to override methods to throw errors. Types only have methods they actually support.
|
||||
|
||||
4. **Reusability**: Behaviors can be reused across unrelated types.
|
||||
|
||||
5. **Testability**: Test behaviors in isolation. Mock specific behaviors easily.
|
||||
|
||||
6. **Runtime Flexibility**: Can change behaviors at runtime by swapping implementations.
|
||||
|
||||
7. **Stable Dependencies**: Behavior implementations are stable. Adding new composed types doesn't affect existing code.
|
||||
Reference in New Issue
Block a user