Lịch sử ra đời OOP
Ngày xưa, tầm những năm 1950-1960, lập trình bao gồm data và function rời rạc. Code ban đầu rất ổn. Tuy nhiên khi máy tính ứng dụng vào các lĩnh vực như Ngân Hàng, Quân Sự, Hàng Không thì khối lượng logic business khổng lồ làm việc bảo trì code rất khó khăn (sửa 1 chỗ hư 10 chỗ).
Do vậy đến những năm 1970, việc lập trình có xu hướng đóng gói data và function vào 1 object để tiện quản lý logic và nâng tầm OOP thành triết lý: mọi thứ đều là Object, các Object tương tác với nhau và che dấu việc implement bên trong. Điều này thuận lợi cho việc bảo trì: đọc code, fix bug.
Một ngôn ngữ được gọi là OOP khi sở hữu 4 tính chất:
- Tính đóng gói: hàm và biến cần đóng gói lại, và expose method cho các module khác xài, tránh khai báo lung tung dẫn đến nổ bug domino.
- Tính trừu tượng: mô tả lập trình như thế giới thật, dễ hiểu, dễ bảo trì.
- Tính kế thừa: tái sử dụng hành vi (function), tránh duplicate code.
- Tính đa hình: thay đổi hành vi linh hoạt, mở rộng mà không cần sửa code cũ.
Phải hiểu OOP không phải là class hay function, nó là cách tư duy có hệ thống.
Lấy ví dụ như trong Java hoặc PHP đều có ReflectionClass, cho phép can thiệp vào compiler để hack variable mang thuộc tính private trong runtime trở thành public, do vậy không thể nói Java/PHP tuân thủ lập trình hướng đối tượng vì vi phạm Tính Đóng Gói.
Một ví dụ khác như Javascript cho phép định nghĩa function vào object ngay lúc runtime (trái ngược hoàn toàn so với Java/PHP), cũng không thể nói Javascript không tuân thủ OOP vì vi phạm Tính Đóng Gói.
Bởi vì đây là 2 trường phái implement OOP khác nhau.
Trường phái của Java/PHP là OOP theo class-based. Phải có Class trước, rồi mới có Object.
Ngược lại Javascript là trường phái OOP theo prototype-based. Không cần class, Object sinh ra Object.
Ví dụ:
//javascript có thể định nghĩa object trước rồi mới định nghĩa method
const animal = {
eat() { console.log("đang ăn..."); }
};
const dog = Object.create(animal); // dog kế thừa từ animal
dog.bark = () => console.log("Gâu gâu!");
dog.eat(); // kế thừa từ animal
dog.bark(); // method riêng của dog
//php bắt buộc khai báo class, mới định nghĩa object được
class Animal
{
public function eat()
{
echo "đang ăn...";
}
}
// bắt buộc khai báo class Dog mới có method bark
class Dog extends Animal
{
public function bark()
{
echo "Gâu gâu...";
}
}
$mrGold = new Dog();//tạo object cậu vàng
$mrGold->bark();
Cách “hack” private trong runtime biến thành public sử dụng PHP ReflectionClass:
<?php
class User
{
private string $password = "day_la_password";
}
$dev = new User();
echo $dev->password;
// lỗi truy cập private bên ngoài class vì nguyên tắc tính đóng gói
Sử dụng ReflectionClass
<?php
$dev = new User();
$ref = new ReflectionClass(User::class);
// lấy property private
$prop = $ref->getProperty('password');
// bật chế độ cho phép truy cập private
$prop->setAccessible(true);
// Đọc giá trị private
echo $prop->getValue($dev); // in ra day_la_password
Rõ ràng PHP đã vi phạm OOP khi cho phép truy cập giá trị private trong lúc runtime mà không cần nội hàm public getPassword().
Trong khi đó, đối với javascript, tính đóng gói cực mạnh cho phép không ai có thể can thiệp vào private.
function createCounter() {
let count = 0; // private
return {
inc() { count++; },
value() { return count; } // public API
};
}
const c = createCounter();
c.inc();
console.log(c.value()); // 1
console.log(c.count); // undefined, không thể truy cập
Tuy nhiên, hãy nhớ đây là 2 trường phái implement OOP khác nhau.
Tìm hiểu về OOP là tìm hiểu về mô hình tư duy có hệ thống, chứ không phải soi cú pháp mà bắt lỗi.