Sau khi đã hiểu class, object, constructor, property và method, chúng ta tiếp tục với bốn khái niệm thường được gọi là “bốn trụ cột của OOP”: encapsulation, inheritance, polymorphism và abstraction. Tester chưa cần áp dụng kiến trúc phức tạp; mục tiêu là nhận ra các khái niệm này khi đọc framework automation và dùng đúng trong những class nhỏ.
Tổng quan bốn đặc điểm OOP
| Khái niệm | Tiếng Việt | Câu hỏi dễ nhớ |
|---|---|---|
| Encapsulation | Đóng gói | Ai được phép đọc hoặc sửa dữ liệu? |
| Inheritance | Kế thừa | Class con nhận lại những gì từ class cha? |
| Polymorphism | Đa hình | Cùng một lời gọi nhưng mỗi object xử lý thế nào? |
| Abstraction | Trừu tượng hóa | Người dùng cần biết điều gì và có thể bỏ qua chi tiết nào? |
1. Encapsulation – Đóng gói
Đóng gói nghĩa là giữ dữ liệu và logic liên quan trong cùng một class, đồng thời kiểm soát cách code bên ngoài truy cập hoặc thay đổi dữ liệu đó. Mục tiêu không phải “giấu mọi thứ”, mà là ngăn object rơi vào trạng thái không hợp lệ.
class ElectricFan {
private speed: number = 0; // speed là 1 private property. Bên ngoài không access được vào
setSpeed(newSpeed: number): void { // muốn chỉnh sửa speed thì phải thông qua hàm này
if (newSpeed < 0 || newSpeed > 3) {
console.log("Tốc độ chỉ được từ 0 đến 3");
return;
}
this.speed = newSpeed;
}
getSpeed(): number { //muốn lấy giá trị của speed ra thì phải thông qua hàm này
return this.speed;
}
}
const fan = new ElectricFan();
fan.setSpeed(2);
console.log(fan.getSpeed()); // 2
fan.setSpeed(5); // Không hợp lệ
console.log(fan.getSpeed()); // Vẫn là 2
// fan.speed = 10; // Lỗi: speed là privateclass ElectricFan:
def __init__(self) -> None:
self.__speed = 0
def set_speed(self, new_speed: int) -> None:
if new_speed < 0 or new_speed > 3:
print("Tốc độ chỉ được từ 0 đến 3")
return
self.__speed = new_speed
def get_speed(self) -> int:
return self.__speed
fan = ElectricFan()
fan.set_speed(2)
print(fan.get_speed()) # 2
fan.set_speed(5) # Không hợp lệ
print(fan.get_speed()) # Vẫn là 2Code bên ngoài không thể gán trực tiếp tốc độ tùy ý. Mọi thay đổi phải đi qua setSpeed(), nên chiếc quạt luôn giữ tốc độ trong khoảng hợp lệ từ 0 đến 3.
Ba access modifier thường gặp
| Modifier | Bên trong class | Class con | Code bên ngoài |
|---|---|---|---|
public | Có | Có | Có |
protected | Có | Có | Không |
private | Có | Không | Không |
Chúng ta sẽ làm rõ hơn ở phần 2 dưới đây.
2. Inheritance – Kế thừa
Kế thừa cho phép class con nhận lại property và method của class cha, sau đó bổ sung hoặc thay đổi hành vi. Trong TypeScript, class con dùng extends; super(...) gọi constructor của class cha.
class Animal {
constructor(public name: string) {}
eat(): void {
console.log(this.name + " đang ăn");
}
}
class Dog extends Animal {
bark(): void {
console.log(this.name + " sủa: Gâu gâu!");
}
}
const milu = new Dog("Milu");
milu.eat(); // Method kế thừa từ Animal (lớp tổng quát), động vật nào cũng biết ăn
milu.bark(); // Method riêng của Dog, không phải Animal nào cũng sủa đượcclass Animal:
def __init__(self, name: str) -> None:
self.name = name
def eat(self) -> None:
print(f"{self.name} đang ăn")
class Dog(Animal):
def bark(self) -> None:
print(f"{self.name} sủa: Gâu gâu!")
milu = Dog("Milu")
milu.eat() # Method kế thừa từ Animal
milu.bark() # Method riêng của DogDog không cần viết lại name và eat(). Nó kế thừa hai thành phần này từ Animal, rồi bổ sung hành vi riêng là bark().
Ví dụ: gọi constructor của class cha bằng super()
Khi class con có constructor riêng, nó dùng super(...) để gọi constructor của class cha. Ví dụ, Student cần class Person khởi tạo name, sau đó mới khởi tạo thêm className của riêng mình.
class Person {
constructor(public name: string) {}
}
class Student extends Person {
constructor(
name: string,
public className: string,
) {
super(name); // Gọi constructor của Person để gán name
}
introduce(): void {
console.log("Mình là " + this.name + ", học lớp " + this.className);
}
}
const an = new Student("An", "10A1");
an.introduce();class Person:
def __init__(self, name: str) -> None:
self.name = name
class Student(Person):
def __init__(self, name: str, class_name: str) -> None:
super().__init__(name) # Gọi __init__ của Person để gán name
self.class_name = class_name
def introduce(self) -> None:
print(f"Mình là {self.name}, học lớp {self.class_name}")
an = Student("An", "10A1")
an.introduce()Trong TypeScript, super(name) phải được gọi trước khi sử dụng this trong constructor của class con.
3. Polymorphism – Đa hình
Đa hình cho phép nhiều object cùng được sử dụng thông qua một kiểu chung, nhưng mỗi object thực hiện hành vi theo cách riêng. Method ở class con có thể override method của class cha.
class Animal {
makeSound(): void {
console.log("Động vật phát ra âm thanh");
}
}
class Dog extends Animal {
override makeSound(): void { //hàm này trùng tên với hàm của class cha, và "đè" lên hàm đó
console.log("Gâu gâu!");
}
}
class Cat extends Animal {
override makeSound(): void { //hàm này trùng tên với hàm của class cha, và "đè" lên hàm đó
console.log("Meo meo!");
}
}
const animals: Animal[] = [new Dog(), new Cat()];
for (const animal of animals) {
animal.makeSound();
}class Animal:
def make_sound(self) -> None:
print("Động vật phát ra âm thanh")
class Dog(Animal):
def make_sound(self) -> None:
print("Gâu gâu!")
class Cat(Animal):
def make_sound(self) -> None:
print("Meo meo!")
animals: list[Animal] = [Dog(), Cat()]
for animal in animals:
animal.make_sound()Vòng lặp chỉ gọi animal.makeSound(), nhưng object Dog sủa “Gâu gâu!” còn object Cat kêu “Meo meo!”. Code gọi không cần viết if/else để kiểm tra từng loài.
override, miễn trùng tên là được. Tuy nhiên nên sử dụng vì compiler có thể phát hiện trường hợp bạn viết sai tên method hoặc class cha đã thay đổi.4. Abstraction – Trừu tượng hóa
Trừu tượng hóa chỉ đưa ra những gì bên sử dụng cần biết và ẩn chi tiết triển khai. Một abstract class có thể quy định: “mọi phương tiện đều phải có method move()”, nhưng để từng class con tự quyết định cách di chuyển.
abstract class Vehicle {
constructor(public name: string) {}
abstract move(): void; //viết sẵn để đấy, chưa cần nội dung bên trong hàm
stop(): void {
console.log(this.name + " đã dừng lại");
}
}
class Car extends Vehicle {
move(): void {
console.log(this.name + " chạy bằng động cơ"); //bây giờ mới viết detail nội dung logic
}
}
const car = new Car("Ô tô");
car.move();
car.stop();
// const vehicle = new Vehicle("Xe"); // Lỗi: không thể tạo object, vì Vehicle là class trừu tượngfrom abc import ABC, abstractmethod
class Vehicle(ABC):
def __init__(self, name: str) -> None:
self.name = name
@abstractmethod
def move(self) -> None:
pass
def stop(self) -> None:
print(f"{self.name} đã dừng lại")
class Car(Vehicle):
def move(self) -> None:
print(f"{self.name} chạy bằng động cơ")
car = Car("Ô tô")
car.move()
car.stop()
# vehicle = Vehicle("Xe") # Lỗi: không thể tạo objectKhông thể tạo trực tiếp object từ Vehicle vì đây là abstract class. Class con phải triển khai move(). Trong khi đó, stop() đã có logic dùng chung nên các class con có thể sử dụng luôn.
Interface – một dạng hợp đồng trong TypeScript
Interface mô tả object hoặc class cần có những thành phần nào, nhưng không chứa phần triển khai method. Đây là một cách phổ biến để tạo hợp đồng mà không bắt các class phải cùng kế thừa một class cha.
interface Switchable {
turnOn(): void; //địng nghĩa sẵn cái vỏ
turnOff(): void; // //địng nghĩa sẵn cái vỏ
}
class Lamp implements Switchable {
turnOn(): void { //implement nội dung cho cái vỏ
console.log("Đèn đã bật");
}
turnOff(): void { //implement nội dung cho cái vỏ
console.log("Đèn đã tắt");
}
}
function useDevice(device: Switchable): void {
device.turnOn();
device.turnOff();
}
useDevice(new Lamp());from abc import ABC, abstractmethod
class Switchable(ABC):
@abstractmethod
def turn_on(self) -> None:
pass
@abstractmethod
def turn_off(self) -> None:
pass
class Lamp(Switchable):
def turn_on(self) -> None:
print("Đèn đã bật")
def turn_off(self) -> None:
print("Đèn đã tắt")
def use_device(device: Switchable) -> None:
device.turn_on()
device.turn_off()
use_device(Lamp())useDevice() chỉ cần biết object nhận vào tuân theo hợp đồng Switchable. Sau này có thể truyền thêm quạt, TV hoặc thiết bị khác có khả năng bật và tắt mà không phải sửa function.Phân biệt nhanh bốn khái niệm
| Nếu bạn đang… | Khái niệm chính |
|---|---|
| Không cho code bên ngoài gán tốc độ quạt tùy ý | Encapsulation |
Tạo Dog từ Animal | Inheritance |
Gọi cùng makeSound() nhưng mỗi con vật kêu khác nhau | Polymorphism |
Quy định mọi phương tiện phải có move() mà không lộ chi tiết | Abstraction |
Bài tập nhỏ
- Tạo abstract class
Mobilecó method abstractsendSMS(message). - Tạo hai class con
appleDevicevàandroidDevicekế thừa class cha bên trên - Mỗi class chứa 1 vài private properties riêng ví dụ như
batteryPercentage,screenSize - Có các pubblic methods để tương tác với các properties bên trên
- Mỗi class override
send()theo cách riêng. - Đưa hai object vào một array kiểu
Notification[]rồi gọisend()trong vòng lặp.
Tóm tắt
- Encapsulation kiểm soát quyền truy cập và bảo vệ state của object.
- Inheritance giúp class con nhận lại thành phần chung từ class cha.
- Polymorphism cho phép cùng một lời gọi tạo ra hành vi phù hợp với từng object.
- Abstraction tạo hợp đồng rõ ràng và ẩn chi tiết triển khai.
- OOP là công cụ tổ chức code; không phải càng nhiều class và kế thừa càng tốt.




