Nguyên tắc cốt lõi:
Không build system bằng cách
extendsnhiều lớp. Thay vào đó, build bằng cách ghép (compose) nhiều behavior nhỏ lại.
#Phần 1: Hiểu bản chất qua PHP thuần
#Cách sai: Inheritance
<?php
class Bird {
public function fly() {
echo "Flying...";
}
}
class Penguin extends Bird {
public function fly() {
// ❌ Sai hoàn toàn về design
throw new Exception("Penguin cannot fly");
}
}
#Vấn đề gì xảy ra?
- Penguin không nên có method fly ngay từ đầu
- Nhưng vì kế thừa → bị ép phải có
- Đây là vi phạm LSP (Liskov Substitution Principle)
#Cách đúng: Composition
#Step 1: Tách behavior thành interface
interface Flyer {
public function fly(): void;
}
#Step 2: Tạo implementation cụ thể
class BirdFlyer implements Flyer {
public function fly(): void {
echo "Flying with wings";
}
}
#Step 3: Compose vào object
class Duck {
private Flyer $flyer;
public function __construct(Flyer $flyer) {
// Inject behavior từ bên ngoài
$this->flyer = $flyer;
}
public function fly(): void {
// Delegate: không tự xử lý, mà gọi sang behavior
$this->flyer->fly();
}
}
#Step 4: Sử dụng
$duck = new Duck(new BirdFlyer());
$duck->fly();
#Penguin (không bay)
class Penguin {
// Không có Flyer → không có fly()
}
→ Thiết kế đúng domain
#Phân tích cực sâu
#1. Đây chính là Dependency Injection
new Duck(new BirdFlyer());
- Không hardcode logic bên trong
- Inject từ ngoài vào
#2. Đây chính là Strategy Pattern
$duck = new Duck(new JetFlyer());
→ đổi behavior runtime
#3. Đây chính là DIP
public function __construct(Flyer $flyer)
→ phụ thuộc abstraction
#4. Runtime vs Compile time
| Kiểu | Inheritance | Composition |
|---|---|---|
| Quyết định | Compile time | Runtime |
| Flexibility | Thấp | Cao |
#Phần 2: Nâng cấp design (real-world PHP)
Ví dụ: Payment System
Inheritance
class BasePayment {
public function pay() {}
}
class StripePayment extends BasePayment {}
class PaypalPayment extends BasePayment {}
→ Không linh hoạt
Composition + Strategy
interface PaymentStrategy {
public function pay(int $amount): void;
}
class StripePayment implements PaymentStrategy {
public function pay(int $amount): void {
echo "Pay with Stripe: $amount";
}
}
class PaypalPayment implements PaymentStrategy {
public function pay(int $amount): void {
echo "Pay with Paypal: $amount";
}
}
class PaymentService {
private PaymentStrategy $strategy;
public function __construct(PaymentStrategy $strategy) {
$this->strategy = $strategy;
}
public function pay(int $amount): void {
// Delegate
$this->strategy->pay($amount);
}
}
Sử dụng
$service = new PaymentService(new StripePayment());
$service->pay(100);
$service = new PaymentService(new PaypalPayment());
$service->pay(200);
#Phần 3: Mapping sang Laravel
#1. Service Container
$this->app->bind(PaymentStrategy::class, StripePayment::class);
→ Laravel inject tự động
#2. Controller
class PaymentController {
public function __construct(private PaymentStrategy $payment) {}
public function pay() {
$this->payment->pay(100);
}
}
#3. Runtime swap (config-based)
$this->app->bind(PaymentStrategy::class, function () {
return config('payment.driver') === 'stripe'
? new StripePayment()
: new PaypalPayment();
});
#4. Middleware = Composition
// mỗi middleware = 1 behavior
Pipeline = chain behavior
#5. Event System
- Listener = behavior độc lập
- Event = trigger
→ Composition
#Phần 4: Khi nào nên dùng?
- Behavior thay đổi runtime
- Có nhiều strategy
- System cần scale
- Cần test isolation
#Khi nào không nên dùng?
- Code đơn giản
- Không cần abstraction
- YAGNI
#Pitfalls
#1. Over-engineering
- Interface quá nhiều
#2. Class explosion
- 1 feature → 10 class
#3. Sai abstraction
- Interface không rõ ràng
#Best Practices
- Luôn inject qua constructor
- Không new cứng trong class
- Interface nhỏ, rõ ràng
- Delegate logic
#Advanced Insight
#1. Composition = nền tảng của Clean Architecture
- Use case độc lập
- Infrastructure tách rời
#2. Composition = enable microservice thinking
- Module hóa system
#3. Composition = giảm blast radius
- Change 1 component → không ảnh hưởng hệ thống
#Câu hỏi phỏng vấn
1. Composition Over Inheritance là gì?
Summary:
- Ghép behavior thay vì kế thừa
Deep:
- Runtime flexibility
- Loose coupling
- Testable
2. Composition khác gì Strategy?
Summary:
- Strategy là 1 dạng composition
Deep:
- Encapsulate behavior
- Swap runtime
3. Laravel dùng composition ở đâu?
Summary:
- Khắp nơi
Deep:
- Container
- Middleware
- Events
- Jobs
#Kết luận
Composition không phải là option.
Nó là default mindset khi thiết kế system hiện đại.
Nếu bạn thấy mình đang viết extends lần thứ 2 → hãy dừng lại. 90% khả năng bạn đang design sai.