CodeGym /Các khóa học /JAVA 25 SELF /Ví dụ xây dựng trừu tượng hóa trong các bài toán thực tế

Ví dụ xây dựng trừu tượng hóa trong các bài toán thực tế

JAVA 25 SELF
Mức độ, Bài học
Có sẵn

1. Trừu tượng hóa trong ứng dụng thực tế: vì sao cần

Ở các bài trước, chúng ta đã làm quen với khái niệm trừu tượng hóa và xem các ví dụ đơn giản. Giờ hãy xem cách tiếp cận này hoạt động trong các bài toán thực tế hơn. Trong các dự án thực, hầu như luôn phải làm việc với các đối tượng khác nhau nhưng tương tự nhau. Ví dụ, các phương thức thanh toán khác nhau, các loại phương tiện khác nhau, các hình khác nhau trong trình biên tập đồ họa. Nếu không áp dụng trừu tượng hóa, mã nhanh chóng biến thành tập hợp “if-else” và copy-paste. Còn với trừu tượng hóa — mọi thứ chặt chẽ, đẹp và quan trọng nhất là thuận tiện cho phát triển và bảo trì.

Trừu tượng hóa cho phép:

  • Ẩn chi tiết hiện thực: làm việc với đối tượng qua một giao diện chung, không cần quan tâm bên trong nó được cài đặt ra sao.
  • Tránh trùng lặp mã: hành vi và trường chung được đưa lên lớp cơ sở.
  • Dễ dàng mở rộng hệ thống: thêm loại đối tượng mới không đòi hỏi viết lại mã cũ.
  • Làm cho mã linh hoạt: có thể thay thế một hiện thực bằng hiện thực khác mà không đổi phần còn lại của hệ thống.

Hãy cùng xem một vài ví dụ ở các lĩnh vực khác nhau.

2. Ví dụ 1: Hệ thống thanh toán

Đề bài

Giả sử bạn viết mô-đun cho cửa hàng trực tuyến. Nhiệm vụ của bạn là hiện thực xử lý các loại thanh toán khác nhau: thẻ ngân hàng, PayPal, tiền mã hóa. Tất cả đều phải “xử lý thanh toán”, nhưng mỗi loại có chi tiết riêng.

Trừu tượng hóa: lớp Payment

public abstract class Payment {
    protected double amount;

    public Payment(double amount) {
        this.amount = amount;
    }

    // Phương thức trừu tượng: cách xử lý thanh toán cụ thể — do các lớp con quyết định
    public abstract void process();

    // Phương thức chung cho mọi khoản thanh toán
    public void printAmount() {
        System.out.println("Số tiền thanh toán: " + amount + " RUB");
    }
}

Hiện thực cụ thể

public class CreditCardPayment extends Payment {
    private String cardNumber;

    public CreditCardPayment(double amount, String cardNumber) {
        super(amount);
        this.cardNumber = cardNumber;
    }

    @Override
    public void process() {
        System.out.println("Xử lý thanh toán bằng thẻ: " + cardNumber);
        // Ở đây có thể là tích hợp với ngân hàng :)
    }
}
public class PaypalPayment extends Payment {
    private String email;

    public PaypalPayment(double amount, String email) {
        super(amount);
        this.email = email;
    }

    @Override
    public void process() {
        System.out.println("Xử lý thanh toán PayPal cho tài khoản: " + email);
        // Và ở đây — gọi PayPal API
    }
}
public class CryptoPayment extends Payment {
    private String walletAddress;

    public CryptoPayment(double amount, String walletAddress) {
        super(amount);
        this.walletAddress = walletAddress;
    }

    @Override
    public void process() {
        System.out.println("Xử lý thanh toán tiền mã hóa tới ví: " + walletAddress);
        // Ở đây có thể có phép màu blockchain
    }
}

Sử dụng trừu tượng hóa

import java.util.*;

public class PaymentDemo {
    public static void main(String[] args) {
        List<Payment> payments = new ArrayList<>();
        payments.add(new CreditCardPayment(1500.0, "1234 5678 9012 3456"));
        payments.add(new PaypalPayment(500.0, "user@example.com"));
        payments.add(new CryptoPayment(0.05, "0xABCD..."));

        for (Payment payment : payments) {
            payment.printAmount();
            payment.process();
            System.out.println("---");
        }
    }
}

Kết quả chạy:

Số tiền thanh toán: 1500.0 RUB
Xử lý thanh toán bằng thẻ: 1234 5678 9012 3456
---
Số tiền thanh toán: 500.0 RUB
Xử lý thanh toán PayPal cho tài khoản: user@example.com
---
Số tiền thanh toán: 0.05 RUB
Xử lý thanh toán tiền mã hóa tới ví: 0xABCD...
---

Ưu điểm:

  • Có thể thêm phương thức thanh toán mới mà không đổi mã cũ (ví dụ, Apple Pay).
  • Mã làm việc với thanh toán không phụ thuộc vào kiểu cụ thể của chúng.
  • Logic chung (ví dụ, in số tiền qua printAmount()) được hiện thực một nơi.

3. Ví dụ 2: Phương tiện vận tải

Đề bài

Trong trò chơi hay mô phỏng, bạn có các loại phương tiện khác nhau: ô tô, xe đạp, tàu hỏa. Tất cả đều có thể “di chuyển”, nhưng cách thực hiện khác nhau. Một số cần tiếp nhiên liệu, số khác thì không.

Trừu tượng hóa: lớp Transport

public abstract class Transport {
    protected String name;

    public Transport(String name) {
        this.name = name;
    }

    public abstract void move();

    // Không phải mọi loại phương tiện đều cần tiếp nhiên liệu; mặc định là không
    public void fuelUp() {
        System.out.println(name + ": không cần tiếp nhiên liệu.");
    }
}

Hiện thực cụ thể

public class Car extends Transport {
    public Car(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " chạy trên đường.");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": tiếp nhiên liệu bằng xăng.");
    }
}
public class Bicycle extends Transport {
    public Bicycle(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " đạp bàn đạp.");
    }
    // không ghi đè fuelUp — xe đạp không cần tiếp nhiên liệu
}
public class Train extends Transport {
    public Train(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + " lao vùn vụt trên đường ray.");
    }

    @Override
    public void fuelUp() {
        System.out.println(name + ": tiếp nhiên liệu bằng dầu diesel hoặc điện.");
    }
}

Sử dụng trừu tượng hóa

import java.util.*;

public class TransportDemo {
    public static void main(String[] args) {
        List<Transport> vehicles = Arrays.asList(
            new Car("Toyota"),
            new Bicycle("Stels"),
            new Train("Sapsan")
        );

        for (Transport t : vehicles) {
            t.move();
            t.fuelUp();
            System.out.println("---");
        }
    }
}

Kết quả chạy:

Toyota chạy trên đường.
Toyota: tiếp nhiên liệu bằng xăng.
---
Stels đạp bàn đạp.
Stels: không cần tiếp nhiên liệu.
---
Sapsan lao vùn vụt trên đường ray.
Sapsan: tiếp nhiên liệu bằng dầu diesel hoặc điện.
---

Ưu điểm:

  • Có thể xử lý mọi phương tiện giống nhau mà không cần kiểm tra kiểu của chúng.
  • Dễ thêm loại phương tiện mới (ví dụ, xe scooter điện).

4. Ví dụ 3: Trình biên tập đồ họa

Đề bài

Bạn viết một trình biên tập đồ họa mini. Trong đó có đường thẳng, elip, đa giác — tất cả đều là “hình” có thể vẽ và thay đổi kích thước. Mỗi hình tự hiện thực các hành động này theo cách riêng.

Trừu tượng hóa: lớp Figure

public abstract class Figure {
    protected String color = "black";

    public abstract void draw();

    public abstract void resize(double factor);

    public void setColor(String color) {
        this.color = color;
    }
}

Hiện thực cụ thể

public class Line extends Figure {
    private double length;

    public Line(double length) {
        this.length = length;
    }

    @Override
    public void draw() {
        System.out.println("Vẽ một đường thẳng dài " + length + " với màu " + color);
    }

    @Override
    public void resize(double factor) {
        length *= factor;
        System.out.println("Độ dài mới của đường thẳng: " + length);
    }
}
public class Ellipse extends Figure {
    private double a, b;

    public Ellipse(double a, double b) {
        this.a = a;
        this.b = b;
    }

    @Override
    public void draw() {
        System.out.println("Vẽ một hình elip với các trục " + a + " và " + b + " với màu " + color);
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("Kích thước mới của elip: " + a + " x " + b);
    }
}
public class Polygon extends Figure {
    private int sides;

    public Polygon(int sides) {
        this.sides = sides;
    }

    @Override
    public void draw() {
        System.out.println("Vẽ một đa giác với " + sides + " cạnh, màu " + color);
    }

    @Override
    public void resize(double factor) {
        System.out.println("Thay đổi kích thước đa giác có " + sides + " cạnh theo " + factor);
    }
}

Sử dụng trừu tượng hóa

import java.util.*;

public class EditorDemo {
    public static void main(String[] args) {
        List<Figure> figures = new ArrayList<>();
        figures.add(new Line(10));
        figures.add(new Ellipse(5, 3));
        figures.add(new Polygon(6));

        for (Figure f : figures) {
            f.setColor("green");
            f.draw();
            f.resize(2);
            System.out.println("---");
        }
    }
}

Kết quả chạy:

Vẽ một đường thẳng dài 10.0 với màu green
Độ dài mới của đường thẳng: 20.0
---
Vẽ một hình elip với các trục 5.0 và 3.0 với màu green
Kích thước mới của elip: 10.0 x 6.0
---
Vẽ một đa giác với 6 cạnh, màu green
Thay đổi kích thước đa giác có 6 cạnh theo 2.0
---

Ưu điểm:

  • Tất cả các hình có thể lưu trong một danh sách và xử lý giống nhau.
  • Dễ thêm hình mới (ví dụ, ngôi sao hoặc trái tim).
  • Các phương thức chung (ví dụ, đặt màu qua setColor()) được hiện thực một lần.

5. Trừu tượng hóa giúp đơn giản hóa mã như thế nào

Trong mỗi ví dụ trên có một sơ đồ chung:

  • Lớp trừu tượng cơ sở đặt ra hợp đồng (đối tượng làm được gì).
  • Các lớp con cụ thể hiện thực chi tiết.
  • Mã làm việc với trừu tượng không phụ thuộc vào kiểu đối tượng, giúp hệ thống linh hoạt và dễ mở rộng.

Bảng so sánh cách tiếp cận

Không có trừu tượng hóa (if-else) Có trừu tượng hóa (OOP)
Nhiều điều kiện theo kiểu Kiểu mới — phải sửa mã
Trùng lặp logic Logic — tập trung một chỗ
Khó mở rộng Bổ sung — dễ dàng
Khó kiểm thử Dễ thay thế các triển khai

6. Các lỗi thường gặp khi thiết kế trừu tượng hóa

Lỗi số 1: Trừu tượng hóa vì trừu tượng hóa.
Nếu bạn chỉ có một kiểu đối tượng và không dự định mở rộng — không cần lớp trừu tượng. Đừng làm phức tạp mã mà không có lý do.

Lỗi số 2: Trừu tượng hóa quá chung chung.
Nếu lớp cơ sở quá “mơ hồ”, các lớp con có thể chẳng có điểm chung nào ngoài cái tên. Ví dụ, trừu tượng “Thing” cho mọi thứ tùy tiện. Điều này gây khó khăn cho việc bảo trì và hiểu mã.

Lỗi số 3: Trùng lặp mã trong các lớp con.
Nếu tất cả các lớp con đều có cùng một hiện thực của một phương thức, hãy đưa nó lên lớp cơ sở (không để trừu tượng).

Lỗi số 4: Vi phạm nguyên tắc “từ tổng quát đến cụ thể”.
Nếu trong lớp trừu tượng xuất hiện các chi tiết chỉ cần cho một lớp con — tức là bạn đã chọn trừu tượng hóa sai.

Lỗi số 5: Quên hiện thực các phương thức trừu tượng.
Nếu không hiện thực đầy đủ các phương thức trừu tượng trong lớp con, trình biên dịch sẽ buộc lớp đó cũng phải là trừu tượng. Đôi khi điều này khiến bạn bất ngờ :)

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Tính diện tích trong ứng dụng đồ họa 🎨
Tính diện tích trong ứng dụng đồ họa 🎨
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Hệ thống gửi thông báo đa năng 📧💬
Hệ thống gửi thông báo đa năng 📧💬
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Quản lý đội xe của công ty 🚚🚲
Quản lý đội xe của công ty 🚚🚲
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Mô-đun xử lý đơn hàng cho cửa hàng bán lẻ 🛒
Mô-đun xử lý đơn hàng cho cửa hàng bán lẻ 🛒
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION