class Vehicle:
def __init__(self, make: str, model: str):
self.make = make
self.model = model
class Car(Vehicle):
def __init__(self, make: str, model: str, doors: int):
super().__init__(make, model)
self.doors = doors
class Vehicle {
protected:
std::string make_;
std::string model_;
public:
Vehicle(std::string make, std::string model)
: make_(std::move(make)), model_(std::move(model)) {}
};
class Car : public Vehicle {
int doors_;
public:
Car(std::string make, std::string model, int doors)
: Vehicle(std::move(make), std::move(model)), doors_(doors) {}
};
public class Vehicle {
protected final String make;
protected final String model;
public Vehicle(String make, String model) {
this.make = make;
this.model = model;
}
}
public final class Car extends Vehicle {
private final int doors;
public Car(String make, String model, int doors) {
super(make, model);
this.doors = doors;
}
}
public class Vehicle {
protected string Make { get; }
protected string Model { get; }
public Vehicle(string make, string model) {
Make = make;
Model = model;
}
}
public sealed class Car : Vehicle {
public int Doors { get; }
public Car(string make, string model, int doors)
: base(make, model) {
Doors = doors;
}
}
✅ Good fits: - Clear "is-a" relationship (Dog is an Animal) - Shared behavior + specialized extensions - Framework extension points
❌ Avoid: - Code reuse only (use composition) - "Has-a" relationships - Deep hierarchies (>3 levels)
class Engine:
def start(self) -> None:
print("Engine started")
class Car:
def __init__(self):
self._engine = Engine() # Composition
def start(self) -> None:
self._engine.start()
class Engine {
public:
void start() { std::cout << "Engine started\n"; }
};
class Car {
Engine engine_; // Composition
public:
void start() { engine_.start(); }
};
public final class Engine {
public void start() { System.out.println("Engine started"); }
}
public final class Car {
private final Engine engine = new Engine();
public void start() { engine.start(); }
}
public sealed class Engine {
public void Start() => Console.WriteLine("Engine started");
}
public sealed class Car {
private readonly Engine _engine = new Engine();
public void Start() => _engine.Start();
}
| Aspect | Inheritance | Composition |
|---|---|---|
| Coupling | Tight (parent changes break child) | Loose |
| Flexibility | Fixed at compile time | Runtime swappable |
| Reusability | Library-specific | Any object with interface |
| Testing | Harder to mock | Easy to mock/inject |
"Prefer composition over inheritance" — GoF Design Patterns
# Instead of inheritance:
class LoggingDatabase(Database):
def query(self, sql: str) -> None:
log(sql)
super().query(sql)
# Prefer composition:
class Database:
def __init__(self, logger=None):
self.logger = logger
def query(self, sql: str) -> None:
if self.logger:
self.logger.log(sql)
# execute query
// Instead of inheritance:
class LoggingDatabase : public Database {
public:
void query(const std::string& sql) override {
log(sql);
Database::query(sql);
}
};
// Prefer composition:
class Database {
Logger* logger_;
public:
explicit Database(Logger* logger = nullptr) : logger_(logger) {}
void query(const std::string& sql) {
if (logger_) logger_->log(sql);
// execute query
}
};
// Instead of inheritance:
class LoggingDatabase extends Database {
@Override
public void query(String sql) {
log(sql);
super.query(sql);
}
}
// Prefer composition:
public final class Database {
private final Logger logger;
public Database(Logger logger) { this.logger = logger; }
public void query(String sql) {
if (logger != null) logger.log(sql);
// execute query
}
}
// Instead of inheritance:
class LoggingDatabase : Database {
public override void Query(string sql) {
Log(sql);
base.Query(sql);
}
}
// Prefer composition:
public sealed class Database {
private readonly ILogger _logger;
public Database(ILogger logger) => _logger = logger;
public void Query(string sql) {
_logger?.Log(sql);
// execute query
}
}
If
Sis a subtype ofT, objects ofTmust be replaceable with objects ofSwithout breaking the program.
class Rectangle:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
def area(self) -> int:
return self.width * self.height
class Square(Rectangle):
def __init__(self, size: int):
super().__init__(size, size)
@property
def width(self) -> int:
return self._width
@width.setter
def width(self, value: int) -> None:
self._width = self._height = value
@property
def height(self) -> int:
return self._height
@height.setter
def height(self, value: int) -> None:
self._width = self._height = value
class Rectangle {
protected:
int width_;
int height_;
public:
Rectangle(int w, int h) : width_(w), height_(h) {}
virtual int width() const { return width_; }
virtual int height() const { return height_; }
virtual void width(int w) { width_ = w; }
virtual void height(int h) { height_ = h; }
int area() const { return width_ * height_; }
};
class Square : public Rectangle {
public:
Square(int size) : Rectangle(size, size) {}
void width(int w) override { width_ = height_ = w; }
void height(int h) override { width_ = height_ = h; }
};
public class Rectangle {
protected int width, height;
public Rectangle(int w, int h) { width = w; height = h; }
public int getWidth() { return width; }
public int getHeight() { return height; }
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
public int area() { return width * height; }
}
public class Square extends Rectangle {
public Square(int size) { super(size, size); }
@Override public void setWidth(int w) { width = height = w; }
@Override public void setHeight(int h) { width = height = h; }
}
public class Rectangle {
protected int Width { get; set; }
protected int Height { get; set; }
public Rectangle(int w, int h) { Width = w; Height = h; }
public virtual int Area() => Width * Height;
}
public class Square : Rectangle {
public Square(int size) : base(size, size) {}
public override int Width { set => Width = Height = value; }
public override int Height { set => Width = Height = value; }
}
def resize(rect: Rectangle) -> None:
rect.width = 10
rect.height = 5
assert rect.area() == 50 # FAILS for Square! 10×10=100
void resize(Rectangle& rect) {
rect.width(10);
rect.height(5);
assert(rect.area() == 50); // FAILS for Square! 10×10=100
}
void resize(Rectangle rect) {
rect.setWidth(10);
rect.setHeight(5);
assert rect.area() == 50; // FAILS for Square! 10×10=100
}
void Resize(Rectangle rect) {
rect.Width = 10;
rect.Height = 5;
Debug.Assert(rect.Area() == 50); // FAILS for Square! 10×10=100
}
Fix: Don't inherit Square from Rectangle. Use composition or make Square a factory function.
from abc import ABC, abstractmethod
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, data: str) -> str:
pass
class ZipStrategy(CompressionStrategy):
def compress(self, data: str) -> str:
return f"zip:{data}"
class GzipStrategy(CompressionStrategy):
def compress(self, data: str) -> str:
return f"gzip:{data}"
class Compressor:
def __init__(self, strategy: CompressionStrategy):
self.strategy = strategy
def compress(self, data: str) -> str:
return self.strategy.compress(data)
def set_strategy(self, strategy: CompressionStrategy) -> None:
self.strategy = strategy
# Usage — swap at runtime!
c = Compressor(ZipStrategy())
print(c.compress("data")) # "zip:data"
c.set_strategy(GzipStrategy())
print(c.compress("data")) # "gzip:data"
#include <memory>
#include <string>
class CompressionStrategy {
public:
virtual std::string compress(const std::string& data) = 0;
virtual ~CompressionStrategy() = default;
};
class ZipStrategy : public CompressionStrategy {
std::string compress(const std::string& data) override { return "zip:" + data; }
};
class GzipStrategy : public CompressionStrategy {
std::string compress(const std::string& data) override { return "gzip:" + data; }
};
class Compressor {
std::unique_ptr<CompressionStrategy> strategy_;
public:
explicit Compressor(std::unique_ptr<CompressionStrategy> s) : strategy_(std::move(s)) {}
std::string compress(const std::string& data) { return strategy_->compress(data); }
void setStrategy(std::unique_ptr<CompressionStrategy> s) { strategy_ = std::move(s); }
};
// Usage — swap at runtime!
Compressor c(std::make_unique<ZipStrategy>());
c.compress("data"); // "zip:data"
c.setStrategy(std::make_unique<GzipStrategy>());
c.compress("data"); // "gzip:data"
interface CompressionStrategy {
String compress(String data);
}
class ZipStrategy implements CompressionStrategy {
public String compress(String data) { return "zip:" + data; }
}
class GzipStrategy implements CompressionStrategy {
public String compress(String data) { return "gzip:" + data; }
}
public final class Compressor {
private CompressionStrategy strategy;
public Compressor(CompressionStrategy s) { this.strategy = s; }
public String compress(String data) { return strategy.compress(data); }
public void setStrategy(CompressionStrategy s) { this.strategy = s; }
}
// Usage — swap at runtime!
Compressor c = new Compressor(new ZipStrategy());
c.compress("data"); // "zip:data"
c.setStrategy(new GzipStrategy());
c.compress("data"); // "gzip:data"
public interface ICompressionStrategy {
string Compress(string data);
}
public sealed class ZipStrategy : ICompressionStrategy {
public string Compress(string data) => "zip:" + data;
}
public sealed class GzipStrategy : ICompressionStrategy {
public string Compress(string data) => "gzip:" + data;
}
public sealed class Compressor {
private ICompressionStrategy _strategy;
public Compressor(ICompressionStrategy s) => _strategy = s;
public string Compress(string data) => _strategy.Compress(data);
public void SetStrategy(ICompressionStrategy s) => _strategy = s;
}
// Usage — swap at runtime!
var c = new Compressor(new ZipStrategy());
c.Compress("data"); // "zip:data"
c.SetStrategy(new GzipStrategy());
c.Compress("data"); // "gzip:data"