Lập trình Hướng Đối Tượng (OOP) trong JavaScript
— Javascript — 9 min read
Nếu bạn đang học JavaScript và muốn viết mã nguồn gọn gàng, dễ bảo trì, thì Lập trình Hướng Đối Tượng (OOP) là kỹ năng không thể bỏ qua. Đừng lo nếu bạn mới bắt đầu – bài viết này sẽ dẫn bạn qua các khái niệm OOP một cách dễ hiểu, với những ví dụ gần gũi như quán phở hay xe máy ngoài đường phố Việt Nam. Hãy cùng khám phá và bắt tay vào code ngay nhé!
OOP là gì? Tại sao bạn cần nó?
Hãy tưởng tượng bạn đang mở một quán phở. Mỗi tô phở (phở bò, phở gà) là một object (đối tượng), còn công thức làm phở (nguyên liệu, cách nấu) là một class (lớp) – bản thiết kế để tạo ra các tô phở. OOP giúp bạn tổ chức mã nguồn giống như cách bạn quản lý quán phở: mọi thứ được sắp xếp khoa học, dễ tái sử dụng, và dễ mở rộng (thêm món phở mới chẳng hạn!).
Trong JavaScript, OOP dựa trên cơ chế prototype, khác với các ngôn ngữ như Java hay C++. Điều này làm JavaScript linh hoạt nhưng cũng hơi "lắt léo" với người mới. Đừng lo, chúng ta sẽ đi từng bước để làm sáng tỏ!
OOP mang lại các lợi ích sau:
- Tái sử dụng mã: Viết một lần, dùng nhiều nơi.
- Dễ bảo trì: Mã được tổ chức gọn gàng, dễ sửa đổi.
- Mô phỏng thế giới thực: Dễ dàng biểu diễn các khái niệm như học sinh, xe cộ, hay đơn hàng.
Class và Object: Nền tảng của OOP
Class: Bản thiết kế
Một class giống như công thức nấu phở – nó không phải là tô phở thật, mà là hướng dẫn để tạo ra tô phở. Trong JavaScript, bạn định nghĩa class bằng từ khóa class
, với các properties (thuộc tính) và methods (phương thức).
Ví dụ: Hãy tạo một class Pho
cho quán phở của bạn:
class Pho { constructor(loai, gia, topping) { this.loai = loai; // Phở bò, phở gà, v.v. this.gia = gia; // Giá tiền this.topping = topping; // Quẩy, hành, ớt, v.v. }
moTa() { console.log(`Tô ${this.loai} giá ${this.gia} VNĐ, có ${this.topping}.`); }}
Object: Sản phẩm từ bản thiết kế
Một object là một instance (thể hiện) của class. Từ class Pho
, bạn có thể tạo ra nhiều tô phở khác nhau:
const phoBo = new Pho('Phở bò tái', 50000, 'quẩy, hành lá');const phoGa = new Pho('Phở gà', 45000, 'hành phi, chanh');
phoBo.moTa(); // Tô Phở bò tái giá 50000 VNĐ, có quẩy, hành lá.phoGa.moTa(); // Tô Phở gà giá 45000 VNĐ, có hành phi, chanh.
Hãy thử ngay: Mở trình duyệt, nhấn F12, vào tab Console, sao chép code trên và thử tạo một tô phở theo ý bạn!
Bốn nguyên tắc OOP
OOP dựa trên 4 nguyên tắc cốt lõi, giúp bạn viết mã như một lập trình viên chuyên nghiệp. Hãy cùng khám phá qua các ví dụ thực tế:
1. Abstraction (Tính Trừu tượng)
Abstraction là việc ẩn đi các chi tiết phức tạp và chỉ hiển thị những gì cần thiết. Khi bạn gọi món phở qua app, bạn chỉ cần chọn "Phở bò tái, thêm quẩy", mà không cần biết quán nấu thế nào (đun nước dùng, thái thịt, v.v.).
Trong code, phương thức moTa()
là một dạng abstraction – bạn không cần biết chi tiết cách hiển thị thông tin, chỉ cần gọi là xong.
2. Encapsulation (Tính Đóng gói)
Encapsulation giống như việc giữ công thức phở trong két sắt – chỉ quán được biết, khách không thể can thiệp. Trong JavaScript, bạn dùng ký hiệu #
để tạo private properties hoặc private methods (riêng tư), đảm bảo chúng không bị truy cập từ bên ngoài.
Ví dụ:
class Pho { #congThucBiMat = 'Nước dùng ninh từ xương bò 12 tiếng'; // Private
constructor(loai, gia, topping) { this.loai = loai; this.gia = gia; this.topping = topping; }
moTa() { console.log(`Tô ${this.loai} giá ${this.gia} VNĐ, có ${this.topping}.`); }
#tinhToanNguyenLieu() { // Private method console.log('Tính toán nguyên liệu bí mật...'); }}
const phoBo = new Pho('Phở bò tái', 50000, 'quẩy');console.log(phoBo.#congThucBiMat); // Lỗi: Không thể truy cậpphoBo.#tinhToanNguyenLieu(); // Lỗi: Không thể gọi
Bạn cũng có thể dùng getter và setter để kiểm soát việc truy cập thuộc tính:
class Pho { #gia;
constructor(loai, gia, topping) { this.loai = loai; this.#gia = gia; this.topping = topping; }
get gia() { return this.#gia; }
set gia(giaMoi) { if (giaMoi < 0) { console.log('Giá không thể âm!'); } else { this.#gia = giaMoi; } }}
const phoBo = new Pho('Phở bò tái', 50000, 'quẩy');console.log(phoBo.gia); // 50000phoBo.gia = -1000; // Giá không thể âm!phoBo.gia = 60000;console.log(phoBo.gia); // 60000
3. Inheritance (Tính Kế thừa)
Inheritance cho phép một class (lớp con) kế thừa các properties và methods từ một class khác (lớp cha), giúp tiết kiệm công sức. Ví dụ, nếu quán phở thêm món bún bò, bạn không cần viết lại công thức từ đầu mà có thể tận dụng công thức phở.
class BunBo extends Pho { constructor(loai, gia, topping, saTe) { super(loai, gia, topping); // Gọi constructor của lớp cha this.saTe = saTe; // Thêm thuộc tính mới }
moTa() { console.log( `Tô ${this.loai} giá ${this.gia} VNĐ, có ${this.topping} và ${this.saTe}.` ); }}
const bunBoHue = new BunBo('Bún bò Huế', 55000, 'hành lá', 'sa tế cay');bunBoHue.moTa(); // Tô Bún bò Huế giá 55000 VNĐ, có hành lá và sa tế cay.
4. Polymorphism (Tính Đa hình)
Polymorphism có nghĩa là "nhiều hình dạng". Nó cho phép các lớp con override (ghi đè) methods của lớp cha để thực hiện hành vi khác nhau. Trong ví dụ trên, phương thức moTa()
của BunBo
khác với Pho
, dù cả hai cùng tên.
Một ví dụ khác:
class KhachHang { datMon() { console.log('Khách hàng đang đặt món...'); }}
class KhachVip extends KhachHang { datMon() { console.log('Khách VIP đặt món với ưu tiên đặc biệt!'); }}
const khachThuong = new KhachHang();const khachVip = new KhachVip();khachThuong.datMon(); // Khách hàng đang đặt món...khachVip.datMon(); // Khách VIP đặt món với ưu tiên đặc biệt!
Prototype: Linh hồn của OOP trong JavaScript
Không như các ngôn ngữ OOP cổ điển, JavaScript sử dụng prototype-based inheritance thay vì class-based. Mỗi object trong JavaScript đều liên kết với một prototype, và nó có thể "mượn" các methods hoặc properties từ prototype đó. Đây là cơ chế cốt lõi của OOP trong JavaScript.
Ví dụ, khi bạn dùng array.push()
, bạn đang gọi method từ Array.prototype
:
const mang = [1, 2, 3];console.log(mang.__proto__ === Array.prototype); // true
Triển khai OOP với Prototype
Trước khi có cú pháp class
(ES6), JavaScript dùng constructor functions để tạo object và thiết lập prototype:
function XeMay(hang, mauSac) { this.hang = hang; this.mauSac = mauSac;}
XeMay.prototype.chay = function () { console.log(`Chiếc ${this.hang} màu ${this.mauSac} đang chạy vèo vèo!`);};
const wave = new XeMay('Honda Wave', 'đỏ');wave.chay(); // Chiếc Honda Wave màu đỏ đang chạy vèo vèo!
Cú pháp class
trong ES6 chỉ là syntactic sugar (cách viết gọn) cho constructor functions, nhưng nó dễ đọc hơn:
class XeMay { constructor(hang, mauSac) { this.hang = hang; this.mauSac = mauSac; }
chay() { console.log(`Chiếc ${this.hang} màu ${this.mauSac} đang chạy vèo vèo!`); }}
const wave = new XeMay('Honda Wave', 'đỏ');wave.chay(); // Chiếc Honda Wave màu đỏ đang chạy vèo vèo!
Sử dụng Object.create()
Một cách khác để tạo object với prototype là dùng Object.create()
:
const Xe = { chay() { console.log(`Chiếc ${this.hang} màu ${this.mauSac} đang chạy!`); },};
const wave = Object.create(Xe);wave.hang = 'Honda Wave';wave.mauSac = 'đỏ';wave.chay(); // Chiếc Honda Wave màu đỏ đang chạy!
Static Methods: Công cụ của lớp
Static methods là các phương thức thuộc về class, không phải instance. Chúng hữu ích khi bạn muốn thực hiện chức năng liên quan đến class mà không cần tạo object.
Ví dụ:
class Pho { constructor(loai, gia) { this.loai = loai; this.gia = gia; }
static tinhGiaTrungBinh(dsPho) { const tongGia = dsPho.reduce((tong, pho) => tong + pho.gia, 0); return tongGia / dsPho.length; }}
const phoBo = new Pho('Phở bò', 50000);const phoGa = new Pho('Phở gà', 45000);console.log(Pho.tinhGiaTrungBinh([phoBo, phoGa])); // 47500
Thực hành: Xây dựng hệ thống quản lý quán phở
Hãy thử áp dụng những gì bạn đã học vào một bài tập thực tế:
- Tạo một class
QuanPho
với các thuộc tínhtenQuan
,diaChi
, và một mảngdanhSachMon
(chứa các objectPho
). - Thêm phương thức
themMon(loai, gia, topping)
để thêm món phở mới. - Tạo một class
QuanPhoCaoCap
kế thừa từQuanPho
, với phương thứckhuyenMai()
để giảm giá 10% cho tất cả món. - (Nâng cao) Thêm private property
#doanhThu
và phương thứctinhDoanhThu()
để tính tổng giá các món đã bán.
Gợi ý code khởi đầu:
class QuanPho { #doanhThu = 0;
constructor(tenQuan, diaChi) { this.tenQuan = tenQuan; this.diaChi = diaChi; this.danhSachMon = []; }
themMon(loai, gia, topping) { const monMoi = new Pho(loai, gia, topping); this.danhSachMon.push(monMoi); this.#doanhThu += gia; }}
Hãy thử ngay: Sao chép code, hoàn thiện các phần còn lại, và chạy trên JSFiddle hoặc Console trình duyệt.
Tổng kết
OOP trong JavaScript giống như học nấu phở: ban đầu hơi lạ lẫm, nhưng khi nắm được công thức, bạn sẽ tạo ra những "tô phở" mã nguồn ngon lành! Với các khái niệm như class, object, prototype, và 4 nguyên tắc OOP, bạn đã có nền tảng để viết mã linh hoạt và dễ bảo trì.
Hãy tiếp tục thực hành:
- Thử tạo các class mô phỏng các tình huống thực tế (như quản lý học sinh, xe cộ, hay đơn hàng).
- Tìm hiểu sâu hơn về getter/setter, static methods, hoặc Object.create().
- Đọc thêm tài liệu từ MDN Object-oriented programming (OOP) để nắm vững prototype.
Cảm ơn bạn đã đồng hành qua bài viết này! Hãy tiếp tục hành trình lập trình đầy thú vị và đừng ngại mắc lỗi – đó là cách bạn học nhanh nhất!