Khái niệm cơ bản về lập trình hướng đối tượng
Ghi chú học tập – OOP (Object-Oriented Programming)
- Tại sao cần phải sử dụng lập trình hướng đối tượng?
Lý do chính: Đóng gói, Kế thừa, Đa hình
Ví dụ minh họa lợi ích của OOP:
Giả sử một cửa hàng thú cưng có hai chú chó:
- Chú chó đầu tên là "Bạch", 2 tuổi, màu trắng.
- Chú chó thứ hai tên là "Hồng", 3 tuổi, màu đỏ.
Yêu cầu hệ thống cho phép người dùng nhập tên chó để hiển thị thông tin chi tiết. Nếu không tìm thấy, in ra thông báo "Cửa hàng không có con chó này".
1.1 Cách tiếp cận phi hướng đối tượng
Không dùng OOP:
package study;
import java.util.Scanner;
public class PetManager {
public static void main(String[] args) {
// Thông tin chó 1
String name1 = "Bạch";
int age1 = 2;
String color1 = "trắng";
// Thông tin chó 2
String name2 = "Hồng";
int age2 = 3;
String color2 = "đỏ";
Scanner input = new Scanner(System.in);
System.out.print("Nhập tên chó: ");
String query = input.nextLine();
if (query.equals(name1)) {
System.out.println("Tên: " + name1 + "\nTuổi: " + age1 + "\nMàu: " + color1);
} else if (query.equals(name2)) {
System.out.println("Tên: " + name2 + "\nTuổi: " + age2 + "\nMàu: " + color2);
} else {
System.out.println("Cửa hàng không có con chó này");
}
}
}
Hoặc dùng mảng để quản lý:
class DogList {
public static void main(String[] args) {
String[] names = {"Bạch", "Hồng"};
int[] ages = {2, 3};
String[] colors = {"trắng", "đỏ"};
Scanner scanner = new Scanner(System.in);
System.out.print("Nhập tên chó: ");
String query = scanner.nextLine();
boolean found = false;
for (int i = 0; i < names.length; i++) {
if (query.equals(names[i])) {
System.out.println("Tên: " + names[i] + "\nTuổi: " + ages[i] + "\nMàu: " + colors[i]);
found = true;
}
}
if (!found) {
System.out.println("Cửa hàng không có con chó này");
}
}
}
Kết quả đầu ra:
- Nhập "Bạch" → Hiển thị thông tin Bạch
- Nhập "Hồng" → Hiển thị thông tin Hồng
- Nhập tên khác → In ra thông báo lỗi
1.2 Cách tiếp cận hướng đối tượng
Sử dụng OOP:
class Dog {
private String name;
private int age;
private String color;
public Dog(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public void displayInfo() {
System.out.println("Tên: " + name + "\nTuổi: " + age + "\nMàu: " + color);
}
public String getName() {
return name;
}
public static void main(String[] args) {
Dog whiteDog = new Dog("Bạch", 2, "trắng");
Dog redDog = new Dog("Hồng", 3, "đỏ");
Scanner scanner = new Scanner(System.in);
System.out.print("Nhập tên chó: ");
String query = scanner.nextLine();
if (query.equals(whiteDog.getName())) {
whiteDog.displayInfo();
} else if (query.equals(redDog.getName())) {
redDog.displayInfo();
} else {
System.out.println("Cửa hàng không có con chó này");
}
}
}
So sánh từ góc nhìn mã nguồn:
Khi không dùng OOP:
- Mỗi đối tượng (chó) được khai báo riêng lẻ hoặc qua mảng.
- Khi số lượng chó tăng lên hàng nghìn, việc khai báo và khởi tạo biến sẽ lặp lại nhiều lần → lỗi trùng lặp, code dài dòng.
- Dễ gây nhầm lẫn khi đọc, khó theo dõi từng thuộc tính.
- Thay đổi cấu trúc (thêm thuộc tính như độ dài lông) đòi hỏi sửa hàng ngàn dòng code → khó bảo trì.
- Không có cấu trúc rõ ràng, mã nguồn trở nên hỗn loạn.
Ngược lại, với OOP:
- Tất cả dữ liệu và hành vi liên quan đến một đối tượng được gom vào một lớp (
Dog). - Mỗi đối tượng được tạo từ mẫu (template) này → giảm thiểu sự trùng lặp.
- Có thể mở rộng dễ dàng bằng cách thêm thuộc tính mới vào lớp
Dog.
- Lợi ích chính của lập trình hướng đối tượng
- Đóng gói (Encapsulation): Các trường dữ liệu được khai báo
private, chỉ truy cập qua phương thức công khai (getter/setter). Điều này đảm bảo an toàn dữ liệu và kiểm soát tốt hơn. - Tính tái sử dụng: Một lớp có thể được dùng nhiều lần để tạo các đối tượng khác nhau mà không cần viết lại code.
- Khả năng mở rộng & bảo trì cao: Thêm thuộc tính mới chỉ cần chỉnh sửa trong lớp gốc, tất cả đối tượng đều hưởng lợi.
- Kế thừa (Inheritance): Cho phép một lớp kế thừa thuộc tính và phương thức từ lớp cha, giúp giảm lặp code.
- Đa hình (Polymorphism): Cùng một phương thức có thể hoạt động khác nhau tùy đối tượng thực thi, tăng tính linh hoạt.
- Cấu trúc rõ ràng: Gần gũi với thực tế: mỗi đối tượng đại diện cho một thực thể trong thế giới thực.
- Hợp tác nhóm hiệu quả: Mỗi thành viên có thể phát triển riêng biệt một lớp mà không ảnh hưởng đến người khác.
- So sánh: Hướng đối tượng vs Hướng quy trình
| Đặc điểm | Hướng quy trình | Hướng đối tượng |
|---|---|---|
| Tư duy | Xử lý từng bước tuần tự | Xây dựng mô hình đối tượng |
| Phù hợp | Nhiệm vụ đơn giản (tính toán, di chuyển) | Hệ thống phức tạp (quản lý tài sản, phần mềm lớn) |
| Cấu trúc | Hàm và biến phân tán | Lớp chứa dữ liệu và hành vi |
| Mở rộng | Khó thay đổi, ảnh hưởng toàn bộ | Dễ mở rộng nhờ kế thừa và đa hình |
"Tương tự như sinh vật tiến hóa, ngôn ngữ lập trình cũng phát triển theo nguyên tắc ‘vật cùng loại tụ lại’. Những câu lệnh giống nhau được gom lại thành hàm, những biến tương tự được nhóm thành cấu trúc, rồi sau đó cả hàm và biến được kết hợp thành lớp – nơi bắt đầu xuất hiện đối tượng."
- Minh họa OOP: Đóng gói, Kế thừa, Đa hình
abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public abstract double calculateArea();
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * Math.pow(radius, 2);
}
}
class Rectangle extends Shape {
private double length;
private double width;
public Rectangle(String color, double length, double width) {
super(color);
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
}
public class ShapeDemo {
public static void main(String[] args) {
Shape circle = new Circle("Đỏ", 5.0);
Shape rectangle = new Rectangle("Xanh", 4.0, 6.0);
System.out.println("Diện tích hình tròn: " + circle.calculateArea());
System.out.println("Màu sắc: " + circle.getColor());
System.out.println("Diện tích hình chữ nhật: " + rectangle.calculateArea());
System.out.println("Màu sắc: " + rectangle.getColor());
}
}
4.1 Đóng gói (Encapsulation)
- Trường
colorđược khai báo làprotected, chỉ truy cập qua phương thứcgetColor(). - Tránh truy cập trực tiếp, đảm bảo an toàn dữ liệu.
4.2 Kế thừa (Inheritance)
CirclevàRectangleđều kế thừa từShape.- Được kế thừa trường
colorvà phương thứcgetColor()→ tránh viết lại code.
4.3 Đa hình (Polymorphism)
- Phương thức
calculateArea()được định nghĩa trừu tượng trongShape. - Mỗi lớp con triển khai riêng → khi gọi qua tham chiếu
Shape, hệ thống tự chọn đúng phiên bản thực thi.
Đây là ví dụ điển hình của đa hình: cùng một interface nhưng hành vi khác nhau tùy đối tượng.