Bốn tính chất OOP
Bốn tính chất OOP — overview
1. Encapsulation — đóng gói
State private + API public có validation = encapsulation.
public class BankAccount {
// State được giấu kín
private decimal _balance;
private readonly string _owner;
public BankAccount(string owner, decimal initial) {
_owner = owner;
_balance = initial >= 0 ? initial : 0;
}
// API expose có kiểm soát
public decimal Balance => _balance;
public string Owner => _owner;
public void Deposit(decimal amount) {
if (amount <= 0) throw new ArgumentException("Must be positive");
_balance += amount;
}
public bool TryWithdraw(decimal amount) {
if (amount <= 0 || amount > _balance) return false;
_balance -= amount;
return true;
}
}
class BankAccount {
// # = TRULY private (runtime enforced)
#balance: number;
readonly #owner: string;
constructor(owner: string, initial: number) {
this.#owner = owner;
this.#balance = initial >= 0 ? initial : 0;
}
get balance(): number { return this.#balance; }
get owner(): string { return this.#owner; }
deposit(amount: number): void {
if (amount <= 0) throw new Error("Must be positive");
this.#balance += amount;
}
tryWithdraw(amount: number): boolean {
if (amount <= 0 || amount > this.#balance) return false;
this.#balance -= amount;
return true;
}
}
- C#
privateenforce ở compile + runtime IL. - TS
privatechỉ check compile-time — runtime vẫn truy cập được. Dùng#để truly private runtime (JS native).
Vì sao quan trọng?
// ❌ Nếu không encapsulate
public class BankAccount {
public decimal Balance;
}
account.Balance = -1000000; // Negative balance? Bug!
account.Balance += 1.0/3; // Floating point chia số → mất tiền
→ Encapsulation = invariant (bất biến của class) được bảo vệ. Balance không bao giờ âm. Validation luôn chạy.
2. Abstraction — trừu tượng
Định nghĩa "cái gì" mà không nói "làm sao".
// Abstraction qua interface
public interface IPaymentGateway {
Task<PaymentResult> ChargeAsync(decimal amount, string currency);
Task<bool> RefundAsync(string transactionId);
}
// Implementation cụ thể — Caller không cần biết
public class StripeGateway : IPaymentGateway {
public Task<PaymentResult> ChargeAsync(decimal amount, string currency) {
// gọi Stripe API
}
public Task<bool> RefundAsync(string transactionId) { ... }
}
public class VnPayGateway : IPaymentGateway {
public Task<PaymentResult> ChargeAsync(decimal amount, string currency) {
// gọi VnPay API
}
public Task<bool> RefundAsync(string transactionId) { ... }
}
// Service chỉ phụ thuộc abstraction
public class CheckoutService {
private readonly IPaymentGateway _gateway;
public CheckoutService(IPaymentGateway gateway) => _gateway = gateway;
public async Task Checkout(Order order) {
var result = await _gateway.ChargeAsync(order.Total, "VND");
// ...
}
}
interface PaymentGateway {
charge(amount: number, currency: string): Promise<PaymentResult>;
refund(transactionId: string): Promise<boolean>;
}
class StripeGateway implements PaymentGateway {
async charge(amount: number, currency: string) { /* Stripe API */ }
async refund(transactionId: string) { /* ... */ return true; }
}
class VnPayGateway implements PaymentGateway {
async charge(amount: number, currency: string) { /* VnPay API */ }
async refund(transactionId: string) { /* ... */ return true; }
}
class CheckoutService {
constructor(private gateway: PaymentGateway) {}
async checkout(order: Order) {
const result = await this.gateway.charge(order.total, "VND");
}
}
"Encapsulation là mechanism (cách che data —
private). Abstraction là concept (che complexity — chỉ exposecharge()không expose chi tiết gọi HTTP, parse JSON, retry, etc.).Hai khái niệm bổ trợ: encapsulation phục vụ abstraction. Khi gộp lại — bạn cho user 1 API đơn giản và an toàn."
3. Inheritance — kế thừa
Class con tái sử dụng + chuyên biệt class cha. Quan hệ "is-a".
public abstract class Animal {
public string Name { get; }
protected Animal(string name) => Name = name;
// Hành vi chung
public void Sleep() => Console.WriteLine($"{Name} is sleeping");
// Hành vi con phải define
public abstract string Speak();
}
public class Dog : Animal {
public Dog(string name) : base(name) {}
public override string Speak() => "Woof!";
public void Fetch() => Console.WriteLine($"{Name} fetches the ball");
}
public class Cat : Animal {
public Cat(string name) : base(name) {}
public override string Speak() => "Meow!";
public void Scratch() => Console.WriteLine($"{Name} scratches");
}
abstract class Animal {
constructor(public readonly name: string) {}
sleep(): void { console.log(`${this.name} is sleeping`); }
abstract speak(): string;
}
class Dog extends Animal {
constructor(name: string) { super(name); }
speak(): string { return "Woof!"; }
fetch(): void { console.log(`${this.name} fetches the ball`); }
}
class Cat extends Animal {
constructor(name: string) { super(name); }
speak(): string { return "Meow!"; }
scratch(): void { console.log(`${this.name} scratches`); }
}
Nhiều cấp kế thừa — nên dừng ở 2-3 cấp
Animal
├─ Mammal
│ ├─ Dog
│ └─ Cat
└─ Bird
└─ Penguin
Multiple inheritance?
- C#: chỉ 1 base class, nhiều interface.
- TypeScript: chỉ 1 base class, nhiều interface. Có
mixinđể mô phỏng multiple inheritance.
// TS mixin pattern
type Constructor<T = {}> = new (...args: any[]) => T;
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
};
}
function Tagged<TBase extends Constructor>(Base: TBase) {
return class extends Base {
tags: string[] = [];
};
}
class User { constructor(public name: string) {} }
class TaggedTimestampedUser extends Timestamped(Tagged(User)) {}
const u = new TaggedTimestampedUser("Alice");
u.tags.push("vip");
u.createdAt; // có sẵn
4. Polymorphism — đa hình
Cùng API, nhiều behavior — runtime quyết.
2 loại chính
virtual/abstract ở cha, override ở con. Runtime quyết qua vtable.Animal[] zoo = { new Dog("Rex"), new Cat("Tom") };
foreach (var a in zoo) {
Console.WriteLine($"{a.Name}: {a.Speak()}");
// Runtime gọi Dog.Speak() hoặc Cat.Speak() đúng,
// dù biến a kiểu Animal
}
const zoo: Animal[] = [new Dog("Rex"), new Cat("Tom")];
for (const a of zoo) {
console.log(`${a.name}: ${a.speak()}`);
}
public class Calculator {
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
public string Add(string a, string b) => a + b;
}
class Calculator {
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: any, b: any): any { return a + b; }
}
TS overload chỉ ở signature, body một cái xử lý hết.
"Runtime polymorphism qua vtable (virtual method table). Mỗi class có 1 vtable lưu pointer tới method virtual. Khi gọi
a.Speak():
- Runtime lookup vtable của object thực (Dog/Cat) qua type tag.
- Tìm slot tương ứng method
Speak.- Gọi function pointer trong slot đó.
Đó là vì sao virtual method chậm hơn rất ít static — phải qua 1 cấp indirection."
Ad-hoc polymorphism (overloading) vs Subtype polymorphism (overriding)
- Ad-hoc: cùng tên, signature khác, compile chọn.
- Subtype: cùng signature, type khác, runtime chọn.
(Còn 1 loại nữa — Parametric polymorphism = generic. Xem section Generic.)
Bonus — Composition vs Inheritance
- Inheritance "is-a": Dog IS-A Animal.
- Composition "has-a": Car HAS-A Engine.
// ❌ Lạm dụng inheritance — hierarchy phình to
public class Vehicle { ... }
public class Car : Vehicle { ... }
public class ElectricCar : Car { ... }
public class TeslaCar : ElectricCar { ... }
// 4 cấp → fragile, khó test, khó thay đổi
// ✅ Composition — Car HAS-A IEngine
public interface IEngine { void Start(); }
public class GasEngine : IEngine { public void Start() {} }
public class ElectricEngine : IEngine { public void Start() {} }
public class Car {
private readonly IEngine _engine;
public Car(IEngine engine) => _engine = engine;
public void Drive() => _engine.Start();
}
// Đổi Car điện = inject ElectricEngine, không sửa Car
Bốn tính chất trong 1 ví dụ
Đây là code minh hoạ đủ 4 tính chất trong cùng 1 mini-app:
// Abstraction
public interface IShape {
double Area();
double Perimeter();
}
// Abstract + Inheritance + Encapsulation
public abstract class ShapeBase : IShape {
public string Name { get; } // encapsulation
protected ShapeBase(string name) => Name = name;
public abstract double Area();
public abstract double Perimeter();
public override string ToString() =>
$"{Name}: area={Area():F2}, perimeter={Perimeter():F2}";
}
// Inheritance + Polymorphism
public class Circle : ShapeBase {
private readonly double _r;
public Circle(double r) : base("Circle") => _r = r;
public override double Area() => Math.PI * _r * _r;
public override double Perimeter() => 2 * Math.PI * _r;
}
public class Rectangle : ShapeBase {
private readonly double _w, _h;
public Rectangle(double w, double h) : base("Rectangle") { _w = w; _h = h; }
public override double Area() => _w * _h;
public override double Perimeter() => 2 * (_w + _h);
}
// Polymorphism in action
IShape[] shapes = { new Circle(5), new Rectangle(3, 4) };
foreach (var s in shapes) Console.WriteLine(s);