Chuyển tới nội dung chính

Giới thiệu Lập trình Hướng đối tượng (OOP)

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Hiểu khái niệm OOP và lý do cần sử dụng paradigm này
  • Nắm vững 4 tính chất cơ bản của OOP: Encapsulation, Inheritance, Polymorphism, Abstraction
  • Phân biệt được procedural programming và OOP với ưu nhược điểm của từng approach
  • Áp dụng tư duy OOP để mô hình hóa các đối tượng thế giới thực
  • Biết được lịch sử phát triển của OOP và tại sao Java chọn OOP

OOP là gì?

Object-Oriented Programming (OOP) - Lập trình hướng đối tượng là một paradigm (mô hình) lập trình dựa trên khái niệm "đối tượng" (objects), trong đó mỗi đối tượng chứa:

  • Dữ liệu (data/attributes/fields): Đặc điểm, trạng thái của đối tượng
  • Hành vi (behaviors/methods): Các hành động mà đối tượng có thể thực hiện
Ẩn dụ thực tế

Hãy nghĩ về một chiếc xe hơi trong thế giới thực:

  • Dữ liệu: màu sắc, hãng xe, số km đã đi, mức nhiên liệu
  • Hành vi: khởi động, tăng tốc, phanh, bật đèn

Tại sao cần OOP?

1. Mô hình hóa thế giới thực

OOP giúp lập trình viên mô hình hóa các đối tượng trong thế giới thực một cách tự nhiên.

// Mô hình hóa một sinh viên
class Student {
// Dữ liệu
String name;
int age;
String studentId;
double gpa;

// Hành vi
void study() {
System.out.println(name + " is studying...");
}

void takeExam() {
System.out.println(name + " is taking an exam...");
}
}

2. Tái sử dụng code (Reusability)

Một khi đã định nghĩa class, bạn có thể tạo nhiều objects từ class đó.

3. Dễ bảo trì và mở rộng (Maintainability & Extensibility)

Code được tổ chức thành các module độc lập, dễ sửa đổi mà không ảnh hưởng toàn bộ hệ thống.

4. Che giấu thông tin (Information Hiding)

Bảo vệ dữ liệu quan trọng khỏi truy cập trực tiếp từ bên ngoài.

Composition over Inheritance

Trong thực tế, composition (chứa đựng) thường được ưu tiên hơn inheritance (kế thừa). Thay vì kế thừa behavior, ta "ghép" các thành phần lại với nhau. Nguyên tắc này sẽ được thảo luận chi tiết ở các bài sau.

4 Tính chất cơ bản của OOP

OOP được xây dựng trên 4 trụ cột chính (Four Pillars of OOP):

💡 Cách nhớ 4 trụ cột OOP (theo phong cách Head First Java)

Hãy tưởng tượng bạn đang xây một ngôi nhà thông minh:

  • Encapsulation (Đóng gói): Giống như két sắt trong nhà - giấu và bảo vệ tài sản quan trọng, chỉ mở bằng mã PIN
  • Inheritance (Kế thừa): Giống như con thừa hưởng tài sản từ bố mẹ - không cần làm lại từ đầu
  • Polymorphism (Đa hình): Giống như công tắc điện - cùng một công tắc, nhưng có thể bật đèn, quạt, hay tivi (cùng giao diện, khác hành vi)
  • Abstraction (Trừu tượng): Giống như remote TV - bạn chỉ cần biết nút nào làm gì, không cần biết cách hoạt động bên trong

1. Encapsulation (Đóng gói)

Định nghĩa: Gói gọn dữ liệu (fields) và các phương thức (methods) liên quan vào một đơn vị (class), đồng thời che giấu chi tiết triển khai bên trong.

class BankAccount {
// Private - che giấu khỏi bên ngoài
private double balance;

// Public methods - giao diện công khai
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}

public double getBalance() {
return balance;
}
}

Lợi ích: Bảo vệ dữ liệu, kiểm soát truy cập, dễ thay đổi implementation.

2. Inheritance (Kế thừa)

Định nghĩa: Cho phép một class (subclass/child) kế thừa các thuộc tính và phương thức từ class khác (superclass/parent).

// Parent class
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}

// Child class kế thừa từ Animal
class Dog extends Animal {
void bark() {
System.out.println("Dog is barking");
}
}

Lợi ích: Tái sử dụng code, tạo cấu trúc phân cấp.

3. Polymorphism (Đa hình)

Định nghĩa: Khả năng một đối tượng có thể có nhiều hình thái khác nhau, hoặc một phương thức có thể hoạt động khác nhau tùy ngữ cảnh.

class Shape {
void draw() {
System.out.println("Drawing a shape");
}
}

class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}

class Rectangle extends Shape {
@Override
void draw() {
System.out.println("Drawing a rectangle");
}
}

Lợi ích: Code linh hoạt, dễ mở rộng.

4. Abstraction (Trừu tượng hóa)

Định nghĩa: Ẩn các chi tiết phức tạp, chỉ hiển thị những gì cần thiết cho người dùng.

// Abstract class - chỉ định nghĩa "cái gì", không "làm thế nào"
abstract class Vehicle {
abstract void start(); // Không có implementation

void stop() {
System.out.println("Vehicle stopped");
}
}

class Car extends Vehicle {
@Override
void start() {
System.out.println("Car starts with ignition key");
}
}

Lợi ích: Giảm độ phức tạp, tập trung vào logic quan trọng.

So sánh Procedural Programming vs OOP

Tiêu chíProcedural ProgrammingObject-Oriented Programming
Cấu trúcTổ chức theo functions/proceduresTổ chức theo objects chứa data + methods
Dữ liệuDữ liệu và functions tách biệtDữ liệu và methods đóng gói trong class
Truy cậpDữ liệu thường public, dễ bị sửa đổiDữ liệu được bảo vệ qua encapsulation
Tái sử dụngTái sử dụng qua functionsTái sử dụng qua inheritance và polymorphism
Ví dụ ngôn ngữC, Pascal, FortranJava, C++, Python, C#
Độ phức tạpPhù hợp cho chương trình nhỏ, đơn giảnPhù hợp cho hệ thống lớn, phức tạp
Bảo trìKhó bảo trì khi code phình toDễ bảo trì nhờ modularity

Coupling vs Cohesion (Khớp nối và Gắn kết)

Hai khái niệm quan trọng trong thiết kế OOP:

Coupling (Khớp nối / Độ phụ thuộc)

Coupling đo lường mức độ phụ thuộc giữa các classes với nhau.

Ẩn dụ thực tế

Hãy nghĩ về dây sạc điện thoại:

  • Tight Coupling (Khớp nối chặt): Dây sạc chỉ cắm được 1 loại điện thoại cụ thể (iPhone Lightning) - thay điện thoại phải thay cả dây
  • Loose Coupling (Khớp nối lỏng): Dây sạc USB-C cắm được nhiều thiết bị khác nhau - linh hoạt, dễ thay thế

Mục tiêu: Giảm Coupling (Loose Coupling)

// ❌ Tight Coupling - Class A phụ thuộc trực tiếp vào Class B
class EmailService {
private MySQLDatabase database; // Chỉ dùng được MySQL

public void sendEmail(String email) {
database.connect();
database.saveEmail(email);
}
}

// ✅ Loose Coupling - Sử dụng interface, dễ thay đổi
class EmailService {
private Database database; // Interface - có thể là MySQL, MongoDB, PostgreSQL...

public EmailService(Database database) {
this.database = database; // Inject từ bên ngoài
}

public void sendEmail(String email) {
database.connect();
database.saveEmail(email);
}
}

Lợi ích của Loose Coupling:

  • Dễ thay đổi implementation mà không ảnh hưởng code khác
  • Dễ test (có thể mock dependencies)
  • Dễ tái sử dụng

Cohesion (Gắn kết / Tính tập trung)

Cohesion đo lường mức độ tập trung của một class vào một nhiệm vụ cụ thể.

Ẩn dụ thực tế

Hãy nghĩ về cửa hàng:

  • Low Cohesion (Gắn kết thấp): Cửa hàng tạp hóa bán cả quần áo, thực phẩm, điện tử, đồ chơi - mỗi thứ một ít, không chuyên
  • High Cohesion (Gắn kết cao): Cửa hàng chuyên điện thoại - chỉ bán điện thoại và phụ kiện liên quan, chuyên nghiệp và tập trung

Mục tiêu: Tăng Cohesion (High Cohesion)

// ❌ Low Cohesion - Class làm quá nhiều thứ không liên quan
class User {
private String name;
private String email;

public void saveToDatabase() { /* ... */ }
public void sendEmail() { /* ... */ }
public void generatePDF() { /* ... */ }
public void calculateTax() { /* ... */ }
// Quá nhiều trách nhiệm không liên quan!
}

// ✅ High Cohesion - Mỗi class tập trung vào một nhiệm vụ
class User {
private String name;
private String email;
// Chỉ quản lý dữ liệu user
}

class UserRepository {
public void save(User user) { /* ... */ }
// Chỉ quản lý lưu trữ
}

class EmailService {
public void sendEmail(String email) { /* ... */ }
// Chỉ quản lý email
}

class PDFGenerator {
public void generateReport(User user) { /* ... */ }
// Chỉ quản lý PDF
}

Lợi ích của High Cohesion:

  • Dễ hiểu (class làm một việc rõ ràng)
  • Dễ bảo trì
  • Dễ tái sử dụng
  • Ít bug hơn

Quy tắc vàng

  • Low Coupling + High Cohesion = Good Design
  • Classes ít phụ thuộc nhau, mỗi class tập trung vào một nhiệm vụ

SOLID Principles (Nguyên lý thiết kế OOP)

SOLID là 5 nguyên lý cơ bản giúp thiết kế code OOP tốt hơn. Chúng ta sẽ học chi tiết ở module sau, nhưng đây là tổng quan:

Nguyên lýTên đầy đủÝ nghĩa ngắn gọn
SSingle Responsibility PrincipleMột class chỉ nên có một lý do để thay đổi (một trách nhiệm)
OOpen/Closed PrincipleClass nên mở cho mở rộng, nhưng đóng cho sửa đổi
LLiskov Substitution PrincipleObject của class con có thể thay thế object của class cha mà không làm hỏng chương trình
IInterface Segregation PrincipleKhông nên bắt class implement các methods nó không cần
DDependency Inversion PrinciplePhụ thuộc vào abstraction (interface), không phụ thuộc vào concrete classes
Về SOLID Principles

Các nguyên lý SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) sẽ được học chi tiết trong Module Design Patterns.

💡 Cách nhớ SOLID

S - Simple: Mỗi class làm 1 việc đơn giản O - Open: Mở rộng được, không sửa code cũ L - Liên hệ: Class con liên hệ chặt chẽ với class cha I - Ignore: Bỏ qua những gì không cần (interface nhỏ gọn) D - Dependency: Phụ thuộc vào trừu tượng, không phụ thuộc chi tiết

Ví dụ so sánh: Tính diện tích hình chữ nhật

Procedural approach:

public class ProceduralExample {
public static void main(String[] args) {
double width = 5.0;
double height = 3.0;

// Function tính diện tích
double area = calculateArea(width, height);
System.out.println("Area: " + area);
}

static double calculateArea(double w, double h) {
return w * h;
}
}

OOP approach:

class Rectangle {
private double width;
private double height;

public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

public double calculateArea() {
return width * height;
}
}

public class OOPExample {
public static void main(String[] args) {
Rectangle rect = new Rectangle(5.0, 3.0);
System.out.println("Area: " + rect.calculateArea());
}
}
Khi nào dùng OOP?
  • Dự án lớn, phức tạp, nhiều người cùng làm
  • Cần tái sử dụng code nhiều lần
  • Hệ thống cần dễ mở rộng và bảo trì
  • Mô hình hóa các đối tượng thế giới thực

Ví dụ so sánh thực tế: Quản lý thư viện sách

Bài toán: Quản lý thư viện với sách, độc giả, và mượn/trả sách.

Procedural approach (C-style):

public class LibraryProcedural {
// Dữ liệu tách biệt
static String[] bookTitles = new String[100];
static String[] bookAuthors = new String[100];
static boolean[] bookAvailable = new boolean[100];
static int bookCount = 0;

static String[] readerNames = new String[50];
static String[] readerEmails = new String[50];
static int readerCount = 0;

// Functions xử lý
public static void addBook(String title, String author) {
bookTitles[bookCount] = title;
bookAuthors[bookCount] = author;
bookAvailable[bookCount] = true;
bookCount++;
}

public static void borrowBook(int bookIndex, int readerIndex) {
if (bookAvailable[bookIndex]) {
bookAvailable[bookIndex] = false;
System.out.println(readerNames[readerIndex] + " borrowed " + bookTitles[bookIndex]);
}
}

public static void displayBooks() {
for (int i = 0; i < bookCount; i++) {
System.out.println(bookTitles[i] + " by " + bookAuthors[i] +
(bookAvailable[i] ? " (Available)" : " (Borrowed)"));
}
}
}

Nhược điểm:

  • Dữ liệu và logic tách biệt, dễ sai sót
  • Khó mở rộng (thêm thông tin sách phải thêm nhiều arrays)
  • Không bảo vệ dữ liệu (ai cũng sửa được arrays)
  • Khó tái sử dụng

OOP approach:

// Class Book - Đóng gói dữ liệu và hành vi
class Book {
private String title;
private String author;
private String isbn;
private boolean available;

public Book(String title, String author, String isbn) {
this.title = title;
this.author = author;
this.isbn = isbn;
this.available = true;
}

public void borrow() {
if (available) {
available = false;
System.out.println(title + " has been borrowed");
} else {
System.out.println(title + " is not available");
}
}

public void returnBook() {
available = true;
System.out.println(title + " has been returned");
}

public boolean isAvailable() {
return available;
}

public void displayInfo() {
System.out.println(title + " by " + author +
(available ? " [Available]" : " [Borrowed]"));
}
}

// Class Reader
class Reader {
private String name;
private String email;
private String readerId;

public Reader(String name, String email, String readerId) {
this.name = name;
this.email = email;
this.readerId = readerId;
}

public void borrowBook(Book book) {
System.out.println(name + " is borrowing...");
book.borrow();
}

public void returnBook(Book book) {
System.out.println(name + " is returning...");
book.returnBook();
}
}

// Class Library - Quản lý tập hợp
class Library {
private List<Book> books = new ArrayList<>();
private List<Reader> readers = new ArrayList<>();

public void addBook(Book book) {
books.add(book);
}

public void registerReader(Reader reader) {
readers.add(reader);
}

public void displayAllBooks() {
System.out.println("=== Library Books ===");
for (Book book : books) {
book.displayInfo();
}
}
}

// Sử dụng
public class LibraryOOP {
public static void main(String[] args) {
Library library = new Library();

Book book1 = new Book("Clean Code", "Robert Martin", "ISBN001");
Book book2 = new Book("Effective Java", "Joshua Bloch", "ISBN002");
library.addBook(book1);
library.addBook(book2);

Reader reader = new Reader("Nguyễn Văn An", "[email protected]", "R001");
library.registerReader(reader);

reader.borrowBook(book1);
library.displayAllBooks();

reader.returnBook(book1);
library.displayAllBooks();
}
}

Ưu điểm OOP:

  • ✅ Dữ liệu và hành vi gắn kết trong class
  • ✅ Dễ mở rộng (thêm fields mới trong class)
  • ✅ Bảo vệ dữ liệu (private fields)
  • ✅ Dễ hiểu, gần với thế giới thực
  • ✅ Dễ bảo trì và test

Output:

Nguyễn Văn An is borrowing...
Clean Code has been borrowed
=== Library Books ===
Clean Code by Robert Martin [Borrowed]
Effective Java by Joshua Bloch [Available]
Nguyễn Văn An is returning...
Clean Code has been returned
=== Library Books ===
Clean Code by Robert Martin [Available]
Effective Java by Joshua Bloch [Available]

Ví dụ thực tế: Mô hình hóa bằng OOP

Ví dụ 1: Xe hơi (Car)

class Car {
// Thuộc tính (data)
private String brand;
private String color;
private double fuelLevel;
private int speed;

// Constructor
public Car(String brand, String color) {
this.brand = brand;
this.color = color;
this.fuelLevel = 0;
this.speed = 0;
}

// Hành vi (behaviors)
public void refuel(double liters) {
fuelLevel += liters;
System.out.println("Refueled " + liters + " liters");
}

public void accelerate(int increment) {
if (fuelLevel > 0) {
speed += increment;
fuelLevel -= 0.1; // Tiêu thụ nhiên liệu
System.out.println("Speed: " + speed + " km/h");
} else {
System.out.println("Out of fuel!");
}
}

public void brake() {
speed = 0;
System.out.println("Car stopped");
}
}

Ví dụ 2: Tài khoản ngân hàng (BankAccount)

class BankAccount {
// Thuộc tính
private String accountNumber;
private String ownerName;
private double balance;

// Constructor
public BankAccount(String accountNumber, String ownerName) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = 0.0;
}

// Hành vi
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: $" + amount);
System.out.println("New balance: $" + balance);
} else {
System.out.println("Invalid amount!");
}
}

public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: $" + amount);
System.out.println("New balance: $" + balance);
} else {
System.out.println("Insufficient funds or invalid amount!");
}
}

public double getBalance() {
return balance;
}
}

Ví dụ 3: Sinh viên (Student)

class Student {
// Thuộc tính
private String studentId;
private String name;
private int age;
private String major;
private double gpa;

// Constructor
public Student(String studentId, String name, int age, String major) {
this.studentId = studentId;
this.name = name;
this.age = age;
this.major = major;
this.gpa = 0.0;
}

// Hành vi
public void study(String subject) {
System.out.println(name + " is studying " + subject);
}

public void takeExam(String subject, double score) {
System.out.println(name + " took " + subject + " exam");
System.out.println("Score: " + score);
}

public void updateGPA(double newGPA) {
this.gpa = newGPA;
System.out.println(name + "'s GPA updated to: " + gpa);
}

public void displayInfo() {
System.out.println("Student ID: " + studentId);
System.out.println("Name: " + name);
System.out.println("Major: " + major);
System.out.println("GPA: " + gpa);
}
}

Lịch sử phát triển OOP

1. Simula (1960s) - Ngôn ngữ OOP đầu tiên

  • Được phát triển bởi Ole-Johan DahlKristen Nygaard tại Na Uy
  • Giới thiệu khái niệm classobject
  • Mục đích ban đầu: mô phỏng (simulation)

2. Smalltalk (1970s) - OOP thuần túy

  • Phát triển tại Xerox PARC bởi Alan Kay
  • Mọi thứ đều là object (pure OOP)
  • Ảnh hưởng lớn đến các ngôn ngữ hiện đại

3. C++ (1980s) - OOP + Performance

  • Được tạo bởi Bjarne Stroustrup
  • Kết hợp OOP với C (hiệu suất cao)
  • Thêm features: multiple inheritance, templates

4. Java (1995) - OOP cho mọi nền tảng

  • Phát triển bởi James Gosling tại Sun Microsystems
  • "Write Once, Run Anywhere" - chạy trên JVM
  • Loại bỏ những phức tạp của C++ (pointers, multiple inheritance)
  • Thêm: garbage collection, platform independence

5. Ngôn ngữ hiện đại (2000s+)

  • C# (Microsoft): Kết hợp Java + C++
  • Python, Ruby: OOP với dynamic typing
  • Kotlin, Swift: Modern OOP cho mobile
Timeline OOP
1960s → Simula (first OOP)
1970s → Smalltalk (pure OOP)
1980s → C++ (OOP + performance)
1995 → Java (platform-independent OOP)
2000s → C#, Python, Ruby (modern OOP)
2010s → Kotlin, Swift (mobile-first OOP)

Tại sao Java chọn OOP?

Java được thiết kế với mục tiêu:

  1. Simple: Loại bỏ phức tạp của C++ (pointers, operator overloading, multiple inheritance)
  2. Robust: Garbage collection tự động, exception handling mạnh mẽ
  3. Secure: Bytecode verification, security manager
  4. Platform Independent: JVM - "Write Once, Run Anywhere"
  5. Object-Oriented: Mọi thứ đều là object (trừ primitives)
Lưu ý

Java không phải pure OOP như Smalltalk vì:

  • primitive types (int, double, boolean...) không phải objects
  • static methods không thuộc object nào

Tổng kết

Key Takeaways

  • OOP là paradigm lập trình dựa trên objects chứa data và methods
  • 4 tính chất OOP: Encapsulation, Inheritance, Polymorphism, Abstraction
  • OOP vs Procedural: OOP tốt hơn cho hệ thống lớn, phức tạp
  • Java là ngôn ngữ OOP phổ biến nhất với slogan "Write Once, Run Anywhere"

Bài tập

  1. Phân tích: Hãy mô tả 3 đối tượng trong thế giới thực (ví dụ: điện thoại, sách, phòng học) với thuộc tính và hành vi của chúng.

  2. So sánh: Giải thích bằng lời của bạn sự khác biệt giữa Procedural và OOP.

  3. Tư duy: Trong 4 tính chất OOP, tính chất nào bạn nghĩ là quan trọng nhất? Tại sao?

Bài tiếp theo

Trong bài tiếp theo, chúng ta sẽ tìm hiểu chi tiết về Class và Object - nền tảng của OOP trong Java.

Bài 2: Class và Object

Đọc thêm


"Object-oriented programming is an exceptionally bad idea which could only have originated in California." — Edsger W. Dijkstra (Computer Scientist)

Nhưng hãy tự mình trải nghiệm và đánh giá! 😊