Lập trình hướng đối tượng (OOP) là gì?
Trong thế giới công nghệ không ngừng phát triển, lập trình hướng đối tượng (OOP) đã trở thành một trong những phương pháp nền tảng giúp các nhà phát triển xây dựng phần mềm mạnh mẽ, dễ bảo trì và mở rộng. OOP tập trung vào việc tổ chức mã nguồn thành các “đối tượng” – những thực thể có thuộc tính (dữ liệu) và hành vi (phương thức). Việc này giúp mô phỏng thế giới thực một cách trực quan hơn, từ đó đơn giản hóa quá trình phát triển các hệ thống phức tạp.
Các trụ cột chính của OOP bao gồm:
- Tính đóng gói (Encapsulation): Gói gọn dữ liệu và phương thức xử lý dữ liệu vào một đơn vị duy nhất, đồng thời ẩn đi các chi tiết triển khai bên trong.
- Tính kế thừa (Inheritance): Cho phép một lớp (lớp con) kế thừa các thuộc tính và phương thức từ một lớp khác (lớp cha), giúp tái sử dụng mã nguồn hiệu quả.
- Tính đa hình (Polymorphism): Khả năng một đối tượng có thể mang nhiều hình thái khác nhau, hoặc một phương thức có thể thực hiện các hành vi khác nhau tùy thuộc vào ngữ cảnh.
- Tính trừu tượng (Abstraction): Tập trung vào những gì đối tượng làm thay vì cách nó làm, giúp quản lý sự phức tạp.
Trong bài viết này, chúng ta sẽ đi sâu vào hai khía cạnh quan trọng của tính đa hình: Overloading và Overriding – những khái niệm thường gây nhầm lẫn nhưng lại cực kỳ hữu ích trong lập trình.

Overloading là gì?
Overloading (nạp chồng) là một kỹ thuật trong lập trình hướng đối tượng cho phép bạn định nghĩa nhiều phương thức (hoặc hàm tạo) có cùng tên trong cùng một lớp. Điểm khác biệt mấu chốt giữa các phương thức này nằm ở danh sách tham số của chúng – số lượng tham số, kiểu dữ liệu của tham số, hoặc thứ tự của chúng phải khác nhau.
Mục đích chính của Overloading là cung cấp sự linh hoạt và dễ đọc cho mã nguồn. Thay vì phải tạo ra nhiều tên phương thức khác nhau cho các tác vụ tương tự (ví dụ: tinhTongHaiSo, tinhTongBaSo), bạn có thể sử dụng cùng một tên phương thức (ví dụ: tinhTong) và để trình biên dịch tự động chọn phiên bản phù hợp dựa trên các đối số được truyền vào.
Đặc điểm của Overloading:
- Xảy ra trong cùng một lớp.
- Các phương thức có cùng tên nhưng khác nhau về chữ ký phương thức (signature) – tức là khác nhau về số lượng, kiểu dữ liệu hoặc thứ tự của các tham số.
- Kiểu trả về (return type) của phương thức có thể giống hoặc khác nhau, nhưng không phải là yếu tố quyết định để phân biệt các phương thức Overload.
- Được quyết định tại thời điểm biên dịch (compile-time polymorphism).
Ví dụ minh họa Overloading:
Hãy tưởng tượng bạn có một phương thức để tính tổng. Với Overloading, bạn có thể định nghĩa nó như sau:
public int tinhTong(int a, int b) { ... }
public int tinhTong(int a, int b, int c) { ... }
public double tinhTong(double a, double b) { ... }
Khi bạn gọi tinhTong(5, 10), trình biên dịch sẽ tự động chọn phương thức đầu tiên. Nếu bạn gọi tinhTong(5.5, 10.5), phương thức thứ ba sẽ được sử dụng. Điều này giúp mã của bạn trở nên gọn gàng và dễ hiểu hơn rất nhiều.

Overriding là gì?
Overriding (ghi đè) là một kỹ thuật trong lập trình hướng đối tượng cho phép một lớp con (subclass) cung cấp một triển khai cụ thể cho một phương thức đã được định nghĩa trong lớp cha (superclass) của nó. Khi một phương thức được ghi đè, lớp con sẽ thay thế hành vi mặc định của phương thức đó bằng hành vi mới của riêng nó.
Overriding là một phần quan trọng của tính kế thừa và đa hình, cho phép các đối tượng thuộc các lớp khác nhau (nhưng có cùng lớp cha) phản ứng khác nhau với cùng một lời gọi phương thức. Điều này rất hữu ích khi bạn muốn một lớp con có hành vi đặc biệt hơn so với lớp cha của nó, nhưng vẫn giữ nguyên tên phương thức để duy trì tính nhất quán.
Đặc điểm của Overriding:
- Xảy ra giữa lớp cha và lớp con (trong mối quan hệ kế thừa).
- Các phương thức phải có cùng tên, cùng chữ ký phương thức (số lượng, kiểu dữ liệu và thứ tự tham số), và cùng kiểu trả về (hoặc kiểu trả về covariant).
- Phương thức ghi đè trong lớp con không được có quyền truy cập yếu hơn phương thức trong lớp cha (ví dụ: nếu phương thức cha là
public, phương thức con không thể làprivate). - Được quyết định tại thời điểm chạy (runtime polymorphism).
- Thường sử dụng từ khóa
@Override(trong Java) hoặcvirtual/override(trong C#) để đánh dấu rõ ràng.
Ví dụ minh họa Overriding:
Giả sử bạn có một lớp DongVat với phương thức tiengKeu(). Lớp này có thể có một triển khai chung chung như “Động vật kêu”.
class DongVat { public void tiengKeu() { System.out.println("Động vật kêu."); } }
Bây giờ, bạn có các lớp con như Cho và Meo. Mỗi loài động vật này có tiếng kêu riêng. Bạn có thể ghi đè phương thức tiengKeu() trong từng lớp con:
class Cho extends DongVat { @Override public void tiengKeu() { System.out.println("Gâu gâu!"); } }
class Meo extends DongVat { @Override public void tiengKeu() { System.out.println("Meo meo!"); } }
Khi bạn tạo một đối tượng Cho và gọi tiengKeu(), nó sẽ phát ra tiếng “Gâu gâu!”. Tương tự, một đối tượng Meo sẽ phát ra tiếng “Meo meo!”. Đây chính là sức mạnh của Overriding.

Phân biệt Overloading và Overriding: Những điểm cốt lõi
Mặc dù cả Overloading và Overriding đều là các kỹ thuật đa hình, chúng phục vụ các mục đích khác nhau và có những điểm khác biệt rõ rệt:
- Mối quan hệ:
– Overloading: Xảy ra trong cùng một lớp.
– Overriding: Xảy ra giữa lớp cha và lớp con (trong mối quan hệ kế thừa). - Chữ ký phương thức:
– Overloading: Các phương thức có cùng tên nhưng khác nhau về chữ ký (số lượng, kiểu, thứ tự tham số).
– Overriding: Các phương thức phải có cùng tên và cùng chữ ký phương thức. - Thời điểm quyết định:
– Overloading: Được quyết định tại thời điểm biên dịch (compile-time polymorphism). Trình biên dịch biết phiên bản nào sẽ được gọi dựa trên các đối số.
– Overriding: Được quyết định tại thời điểm chạy (runtime polymorphism). JVM/CLR sẽ xác định phiên bản nào của phương thức sẽ được gọi dựa trên kiểu đối tượng thực tế. - Mục đích:
– Overloading: Cung cấp nhiều cách để thực hiện một hành động tương tự với các đầu vào khác nhau, tăng tính linh hoạt và dễ đọc.
– Overriding: Thay đổi hành vi của một phương thức được kế thừa từ lớp cha, cho phép lớp con có triển khai riêng biệt.

Nâng tầm kỹ năng lập trình với Overloading và Overriding
Việc nắm vững Overloading và Overriding không chỉ giúp bạn hiểu sâu hơn về lập trình hướng đối tượng mà còn là chìa khóa để viết mã nguồn sạch hơn, linh hoạt hơn và dễ bảo trì hơn. Overloading giúp bạn tạo ra các API trực quan và dễ sử dụng, trong khi Overriding cho phép bạn tùy chỉnh hành vi của các lớp con mà không làm thay đổi cấu trúc của lớp cha. Hãy thực hành và áp dụng hai khái niệm này vào các dự án của mình để thấy được sức mạnh thực sự của chúng trong việc xây dựng các ứng dụng phần mềm chất lượng cao!




Leave a Comment