🧩
Composition
Chapter 9 · OOP Series
Composition
& Design Patterns
Ghép các object lại với nhau thay vì kế thừa — và học 2 design pattern phổ biến nhất!
Phần 1 — Has-a vs Is-a
Hai quan hệ cốt lõi trong OOP
🧬 Is-a (Inheritance)
🐕 Cho is-a DongVat
👨💼 QuanLy is-a NhanVien
🏃 VanDongVien is-a NguoiDung
→ Dùng kế thừa khi B là một loại của A
🧩 Has-a (Composition)
🚗 XeHoi has-a DongCo
👤 NguoiDung has-a DiaChi
📱 DienThoai has-a ManHinh
→ Dùng composition khi B là một phần của A
💡 Nguyên tắc vàng: "Favor Composition over Inheritance" — Khi không chắc chắn, hãy dùng composition. Nó linh hoạt hơn và ít gây rắc rối hơn về sau!
Phần 2 — Composition trong thực tế
XeHoi = Engine + GPS + Radio + ...
class DongCo: def __init__(self, cc): self.cc = cc self.dang_chay = False def khoi_dong(self): self.dang_chay = True return f"🔥 Động cơ {self.cc}cc nổ máy!" def tat(self): self.dang_chay = False return "💤 Động cơ tắt." class GPS: def dan_duong(self, dich): return f"🗺️ GPS: Đang dẫn đường đến {dich}..." class DanAmThanh: def phat(self, bai): return f"🎵 Đang phát: {bai}" # ── COMPOSITION ────────────────────────── class XeHoi: def __init__(self, hang, cc): self.hang = hang self.dong_co = DongCo(cc) ← HAS-A self.gps = GPS() ← HAS-A self.am_thanh = DanAmThanh() ← HAS-A def bat_xe(self): print(self.dong_co.khoi_dong()) def di_den(self, noi): print(self.gps.dan_duong(noi)) def nghe_nhac(self, bai): print(self.am_thanh.phat(bai)) xe = XeHoi("Toyota", 2000) xe.bat_xe() xe.di_den("Hà Nội") xe.nghe_nhac("Việt Nam Quê Hương Tôi")
🔥 Động cơ 2000cc nổ máy!
🗺️ GPS: Đang dẫn đường đến Hà Nội...
🎵 Đang phát: Việt Nam Quê Hương Tôi
🗺️ GPS: Đang dẫn đường đến Hà Nội...
🎵 Đang phát: Việt Nam Quê Hương Tôi
Phần 3 — Strategy Pattern
🎯 Strategy Pattern
Đổi thuật toán lúc runtime không cần sửa code!
Strategy Pattern cho phép bạn thay đổi "cách làm" của một hành động mà không đụng vào class chính:
from abc import ABC, abstractmethod # ── Các "chiến lược" sắp xếp ───────────── class ChienLuocSapXep(ABC): @abstractmethod def sap_xep(self, ds): pass class TangDan(ChienLuocSapXep): def sap_xep(self, ds): print("📈 Sắp xếp tăng dần") return sorted(ds) class GiamDan(ChienLuocSapXep): def sap_xep(self, ds): print("📉 Sắp xếp giảm dần") return sorted(ds, reverse=True) class TheoDoTuoi(ChienLuocSapXep): def sap_xep(self, ds): print("📊 Sắp xếp theo độ tuổi") return sorted(ds, key=lambda x: x["tuoi"]) # ── Class dùng strategy ────────────────── class DanhSachHocSinh: def __init__(self): self.ds = [] self.chien_luoc = TangDan() ← mặc định def doi_chien_luoc(self, cl): self.chien_luoc = cl ← đổi runtime! def them(self, hs): self.ds.append(hs) def xuat(self): ketqua = self.chien_luoc.sap_xep(self.ds) for hs in ketqua: print(f" {hs['ten']:10} {hs['tuoi']} tuổi") ds = DanhSachHocSinh() for hs in [ {"ten":"Nam", "tuoi":14}, {"ten":"An", "tuoi":13}, {"ten":"Bình", "tuoi":15}, ]: ds.them(hs) ds.xuat() ds.doi_chien_luoc(TheoDoTuoi()) ds.xuat()
📈 Sắp xếp tăng dần
An 13 tuổi
Bình 15 tuổi
Nam 14 tuổi
📊 Sắp xếp theo độ tuổi
An 13 tuổi
Nam 14 tuổi
Bình 15 tuổi
An 13 tuổi
Bình 15 tuổi
Nam 14 tuổi
📊 Sắp xếp theo độ tuổi
An 13 tuổi
Nam 14 tuổi
Bình 15 tuổi
Phần 4 — Builder Pattern
🏗️ Builder Pattern
Xây dựng object phức tạp từng bước
class NhanVatRPG: def __init__(self): self.ten = "Unnamed" self.nghe = "Warrior" self.hp = 100 self.vu_khi = "Kiếm gỗ" self.ky_nang = [] def __str__(self): return f"⚔️ [{self.nghe}] {self.ten} | HP:{self.hp} | {self.vu_khi}" class NhanVatBuilder: def __init__(self): self.nv = NhanVatRPG() def ten(self, ten): self.nv.ten = ten return self ← trả về self để chain! def nghe(self, nghe): self.nv.nghe = nghe return self def hp(self, hp): self.nv.hp = hp return self def vu_khi(self, vk): self.nv.vu_khi = vk return self def them_ky_nang(self, kn): self.nv.ky_nang.append(kn) return self def build(self): return self.nv ← trả về object hoàn chỉnh # ✨ Method chaining — đọc như văn xuôi! anh_hung = ( NhanVatBuilder() .ten("Arthur") .nghe("Chiến Binh") .hp(200) .vu_khi("Kiếm Excalibur") .them_ky_nang("Chém lốc xoáy") .them_ky_nang("Khiên thần") .build() ) print(anh_hung) print("Kỹ năng:", anh_hung.ky_nang)
⚔️ [Chiến Binh] Arthur | HP:200 | Kiếm Excalibur
Kỹ năng: ['Chém lốc xoáy', 'Khiên thần']
Kỹ năng: ['Chém lốc xoáy', 'Khiên thần']
Method Chaining — Builder Pattern! Mỗi method trả về
self để có thể gọi tiếp — tạo ra cú pháp "nối chuỗi" cực kỳ dễ đọc. Rất phổ biến trong các thư viện Python hiện đại!🧩 Kiểm tra nhanh!
Chapter 9 — Composition & Design Patterns
Khác nhau giữa Is-a và Has-a?
→ Is-a = kế thừa, Has-a = composition
→ Is-a = kế thừa, Has-a = composition
Strategy Pattern giải quyết vấn đề gì?
→ Đổi thuật toán lúc runtime
→ Đổi thuật toán lúc runtime
Builder Pattern dùng khi nào?
→ Tạo object phức tạp nhiều bước
→ Tạo object phức tạp nhiều bước
Method chaining hoạt động nhờ gì?
→ Mỗi method return self
→ Mỗi method return self