| Inheritance ("is-a") | Composition ("has-a") | |
|---|---|---|
| Cơ chế | class B extends A | class B { A a; } |
| Coupling | Chặt — biết internal parent | Lỏng — chỉ phụ thuộc interface |
| Đổi runtime | Không | Có (đổi member) |
Nguyên tắc Effective Java: Favor composition over inheritance.
Ví dụ kinh điển vì sao inheritance dễ sai:
// ❌ Extends HashSet để đếm — DOUBLE COUNT bug
class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override public boolean add(E e) { addCount++; return super.add(e); }
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c); // super.addAll gọi NỘI BỘ add() → đếm 2 lần
}
}Composition là fix:
// ✅ Delegation — không phụ thuộc internal
class InstrumentedSet<E> implements Set<E> {
private final Set<E> delegate;
private int addCount = 0;
public boolean add(E e) { addCount++; return delegate.add(e); }
}Khi dùng inheritance: quan hệ "is-a" thật sự, cùng team/package, parent designed for extension (vd AbstractList).
Khi dùng composition: quan hệ "has-a"/"uses-a", cần đổi behavior runtime, class third-party, Decorator/Adapter/Proxy pattern.
Quy tắc cuối: extends class không cùng package mà parent không nói "designed for extension" → gần như chắc chắn nên composition.
| Inheritance ("is-a") | Composition ("has-a") | |
|---|---|---|
| Mechanism | class B extends A | class B { A a; } |
| Coupling | Tight — knows parent internals | Loose — depends on the interface |
| Change at runtime | No | Yes (swap the member) |
Effective Java principle: Favor composition over inheritance.
Classic example — why inheritance is easy to get wrong:
// ❌ Extending HashSet to count — DOUBLE COUNT bug
class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override public boolean add(E e) { addCount++; return super.add(e); }
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c); // super.addAll internally calls add() → counted twice
}
}Composition fixes it:
// ✅ Delegation — does not depend on internals
class InstrumentedSet<E> implements Set<E> {
private final Set<E> delegate;
private int addCount = 0;
public boolean add(E e) { addCount++; return delegate.add(e); }
}Use inheritance when: a true "is-a" relationship, same team/package, parent designed for extension (e.g. AbstractList).
Use composition when: "has-a"/"uses-a", need runtime behaviour swap, third-party class, Decorator/Adapter/Proxy patterns.
Final rule: about to extends a class outside your package that does not say "designed for extension" → almost always prefer composition.