CodeGym /Các khóa học /JAVA 25 SELF /Triển khai các trừu tượng và hệ thống phân cấp

Triển khai các trừu tượng và hệ thống phân cấp

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

1. Xây dựng hệ phân cấp: từ trừu tượng đến chi tiết

Triển khai trừu tượng và hệ phân cấp — là cách cấu trúc mã từ các quy tắc chung đến chi tiết cụ thể. Trước hết, ta mô tả những gì mọi đối tượng phải có thể làm (trừu tượng), sau đó làm rõ mỗi lớp cụ thể thực hiện điều đó như thế nào.

Trong lập trình cũng như trong cuộc sống, mọi thứ bắt đầu bằng câu hỏi. Ví dụ: “Điểm chung giữa hình tròn và hình chữ nhật là gì?” Câu trả lời: chúng đều là hình. Điểm chung của các hình là gì? Thường thì chúng có diện tích và có thể được vẽ.

Trong Java, điều này được thể hiện bằng lớp abstract:

public abstract class Shape {
    public abstract double area();
    public abstract void draw();
}

Ở đây chúng ta nói rằng:

  • Bất kỳ hình nào cũng phải biết tính diện tích của mình (area()).
  • Bất kỳ hình nào cũng phải biết tự vẽ (draw()).
  • Cách thức thực hiện cụ thể — chưa phải việc của chúng ta (ít nhất là hiện tại).

Bây giờ tạo các hình cụ thể:

public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Vẽ hình tròn với bán kính " + radius);
    }
}

public class Rectangle extends Shape {
    private double width, height;

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

    @Override
    public double area() {
        return width * height;
    }

    @Override
    public void draw() {
        System.out.println("Vẽ hình chữ nhật " + width + "x" + height);
    }
}

Chúng ta đã làm gì?

  • Đưa phần chung vào lớp trừu tượng.
  • Chi tiết hóa hành vi trong các lớp con.

Sơ đồ:


.          Shape
         /     \
     Circle   Rectangle

Bảng: phần nào được triển khai ở đâu

Lớp area() draw() Trường riêng
Shape
abstract
abstract
-
Circle đã triển khai đã triển khai
radius
Rectangle đã triển khai đã triển khai
width, height

2. Tại sao cách này tiện lợi? (và vì sao nó hoạt động)

Giao diện thống nhất để làm việc với các đối tượng khác nhau

Giả sử bạn có một tập hợp các hình:

Shape[] shapes = {
    new Circle(5),
    new Rectangle(3, 4),
    new Circle(2.5)
};

Bạn có thể duyệt chúng theo cùng một cách, không cần bận tâm đến kiểu:

for (Shape shape : shapes) {
    shape.draw();
    System.out.println("Diện tích: " + shape.area());
}

Hãy để JVM tự xác định đâu là hình tròn, đâu là hình chữ nhật! Đó chính là tính đa hình (chúng ta đã nói về nó, và sẽ còn bàn kỹ hơn — ở phần tiếp theo).

Dễ dàng mở rộng

Muốn thêm tam giác? Chỉ cần viết (kiểu mới — không phải sửa mã cũ): Triangle extends Shape.

public class Triangle extends Shape {
    private double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }

    @Override
    public void draw() {
        System.out.println("Vẽ tam giác: đáy " + base + ", chiều cao " + height);
    }
}

Phần mã còn lại (ví dụ, vòng lặp duyệt danh sách hình) không cần thay đổi.

Tránh lặp code

Nếu tất cả các hình có một thuộc tính chung (ví dụ, màu sắc), thật tiện để đưa nó vào lớp trừu tượng:

public abstract class Shape {
    private String color = "đen";

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

    public abstract double area();
    public abstract void draw();
}

Giờ thì mọi lớp con — dù là hình tròn hay tam giác — đều “kế thừa” màu sắc.

3. Thực hành: xây dựng mini‑trình biên tập đồ họa

Hãy thử ghép tất cả lại. Giả sử bạn đang làm một trình biên tập đồ họa đơn giản.

Lớp trừu tượng Figure

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

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

    public abstract void draw();
    public abstract void resize(double factor);
}

Các hình 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ẽ đoạn thẳng dài " + length + " với màu " + getColor());
    }

    @Override
    public void resize(double factor) {
        length *= factor;
        System.out.println("Độ dài mới của đoạn 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ẽ hình elip với các trục " + a + " và " + b + " với màu " + getColor());
    }

    @Override
    public void resize(double factor) {
        a *= factor;
        b *= factor;
        System.out.println("Các trục mới của hình elip: " + a + ", " + b);
    }
}

Muốn thêm công cụ mới, chẳng hạn Polygon? Chỉ cần tạo một lớp mới — toàn bộ mã của trình biên tập hoạt động thông qua lớp trừu tượng Figure.

Sử dụng trong mã

Figure[] figures = {
    new Line(10),
    new Ellipse(5, 3)
};

for (Figure figure : figures) {
    figure.setColor("red");
    figure.draw();
    figure.resize(1.5);
}

Kết quả:

Vẽ đoạn thẳng dài 10.0 với màu red
Độ dài mới của đoạn thẳng: 15.0
Vẽ hình elip với các trục 5.0 và 3.0 với màu red
Các trục mới của hình elip: 7.5, 4.5

Trực quan hóa hệ phân cấp


.            Figure
             /    \
          Line   Ellipse

4. Cách tránh lặp lại: các trường và phương thức chung

Đôi khi tất cả các lớp con không chỉ có các phương thức chung mà còn có các trường chung (ví dụ, tọa độ tâm). Lớp trừu tượng là nơi lý tưởng cho điều đó:

public abstract class Figure {
    private double x, y; // tọa độ tâm

    public Figure(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public void moveTo(double newX, double newY) {
        x = newX;
        y = newY;
        System.out.println("Hình được chuyển tới điểm (" + x + ", " + y + ")");
    }

    public abstract void draw();
}

Giờ thì bất kỳ Line hoặc Ellipse nào cũng có thể di chuyển mà không cần tự hiện thực lại phương thức này.

5. Một ví dụ khác: hệ thống thanh toán

Trừu tượng không chỉ dành cho hình học! Hãy tưởng tượng bạn đang viết một hệ thống xử lý thanh toán.

Lớp trừu tượng Payment

public abstract class Payment {
    public abstract void process();
}

Các hiện thực cụ thể

public class CreditCardPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Xử lý thanh toán bằng thẻ tín dụng");
    }
}

public class PaypalPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Xử lý thanh toán qua PayPal");
    }
}

Sử dụng

Payment[] payments = {
    new CreditCardPayment(),
    new PaypalPayment()
};

for (Payment payment : payments) {
    payment.process();
}

Kết quả:

Xử lý thanh toán bằng thẻ tín dụng
Xử lý thanh toán qua PayPal

6. Ưu điểm của cách tiếp cận này

  • Giao diện thống nhất: có thể làm việc với các đối tượng khác nhau theo cùng một cách.
  • Khả năng mở rộng: thêm loại đối tượng mới không yêu cầu viết lại mã cũ.
  • Giảm thiểu trùng lặp: phần chung được đưa vào lớp trừu tượng cơ sở.
  • Linh hoạt: có thể dùng các tập hợp kiểu trừu tượng mà không phải bận tâm đến chi tiết.

7. Ví dụ thực tế: vận tải

Trừu tượng không chỉ có trong sách giáo khoa. Ví dụ, nếu bạn thiết kế hệ thống quản lý phương tiện:

public abstract class Transport {
    public abstract void move();
    public abstract void fuelUp();
}

Các loại phương tiện cụ thể hiện thực chi tiết:

public class Car extends Transport {
    @Override
    public void move() {
        System.out.println("Ô tô chạy trên đường");
    }

    @Override
    public void fuelUp() {
        System.out.println("Đổ xăng");
    }
}

public class Bicycle extends Transport {
    @Override
    public void move() {
        System.out.println("Xe đạp đạp bàn đạp");
    }

    @Override
    public void fuelUp() {
        System.out.println("Xe đạp không cần nhiên liệu, chỉ cần bánh mì kẹp cho người đạp thôi!");
    }
}

8. Sơ đồ hữu ích: cách xây dựng hệ phân cấp trừu tượng

[Lớp trừu tượng]
        |
   [Lớp con cụ thể]
        |
   [Lớp con còn cụ thể hơn] (nếu cần)

- Mọi thứ chung — ở trên!
- Mọi thứ riêng — ở dưới!

9. Những lỗi thường gặp khi triển khai trừu tượng và hệ phân cấp

Lỗi số 1: Trùng lặp mã trong các lớp con.
Nếu bạn nhận thấy rằng trong mỗi lớp con đều viết cùng các trường hoặc phương thức — đó là tín hiệu nên đưa chúng lên lớp trừu tượng. Đừng ngại làm trừu tượng “rộng hơn” nếu điều đó giảm trùng lặp.

Lỗi số 2: Vi phạm nguyên tắc “từ chung đến riêng”.
Đôi khi người mới bắt đầu xây dựng hệ phân cấp “từ chi tiết”, quên mất phần chung. Kết quả là xuất hiện các lớp kỳ quặc như RedCircleWithShadow, khó hòa nhập vào cấu trúc tổng thể. Luôn luôn xác định trừu tượng trước, rồi mới đến chi tiết.

Lỗi số 3: Hệ phân cấp quá sâu.
Nếu chuỗi kế thừa dài hơn 3–4 cấp, hãy cân nhắc: có lẽ nên dùng composition hoặc interface thay vì kế thừa?

Lỗi số 4: Ép hiện thực các phương thức không liên quan.
Nếu lớp trừu tượng chứa quá nhiều phương thức trừu tượng không phù hợp với một số lớp con, có lẽ bạn nên xem lại cấu trúc. Ví dụ, không phải mọi phương tiện đều cần phương thức fuelUp() (xe đạp thì không).

Lỗi số 5: Nhầm lẫn giữa lớp trừu tượng và interface.
Dùng lớp trừu tượng khi có trạng thái chung và/hoặc hiện thực một phần. Interface — khi chỉ cần “cam kết” sự tồn tại của các phương thức, không lưu trữ dữ liệu và không hiện thực hành vi. Đừng trộn lẫn hai cách tiếp cận này nếu không cần thiết.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Trình mô phỏng giao thông: Xe ô tô đang chạy 🛣️
Trình mô phỏng giao thông: Xe ô tô đang chạy 🛣️
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Thư viện hình học 🔵⬛
Thư viện hình học 🔵⬛
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Hệ thống quản lý nhân sự cơ bản 🧑‍💼
Hệ thống quản lý nhân sự cơ bản 🧑‍💼
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Hệ thống thanh toán linh hoạt cho cửa hàng 💰
Hệ thống thanh toán linh hoạt cho cửa hàng 💰
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION