Khám phá công nghệ

Khám phá các công cụ và kỹ thuật hữu ich cho Lập trình viên

Tính trừu tượng (Abstraction) trong Java - Java OOP

Thắng Nguyễn

Wed, 14 May 2025

Tính trừu tượng (Abstraction) trong Java - Java OOP

Giới thiệu

Trong lập trình hướng đối tượng (OOP), tính trừu tượng là một trong bốn trụ cột chính, cùng với tính đóng gói (encapsulation), tính kế thừa (inheritance) và tính đa hình (polymorphism). Tính trừu tượng tập trung vào việc che giấu các chi tiết triển khai phức tạpchỉ hiển thị các tính năng cần thiết của đối tượng cho thế giới bên ngoài.

Hãy tưởng tượng bạn đang lái một chiếc xe hơi. Bạn chỉ cần biết cách sử dụng vô lăng, chân ga, chân phanh và cần số. Bạn không cần quan tâm đến cơ chế hoạt động phức tạp bên trong động cơ, hệ thống truyền động hay hệ thống điện tử. Đó chính là trừu tượng – bạn tương tác với một giao diện đơn giản hóa mà không cần biết chi tiết bên trong.

Trong Java, tính trừu tượng giúp:

  1. Giảm độ phức tạp: Bằng cách ẩn đi các chi tiết không cần thiết, code trở nên dễ hiểu và quản lý hơn.
  2. Tăng tính linh hoạt và bảo trì: Thay đổi trong phần triển khai nội bộ (phần bị che giấu) sẽ không ảnh hưởng đến các phần khác của chương trình đang sử dụng đối tượng đó, miễn là giao diện công khai (phần hiển thị) không đổi.
  3. Tập trung vào "cái gì" thay vì "như thế nào": Người dùng chỉ cần biết đối tượng làm được gì (what) thông qua các phương thức công khai, không cần biết nó làm như thế nào (how).

Cách đạt được Tính trừu tượng trong Java

Java cung cấp hai cơ chế chính để triển khai tính trừu tượng:

  1. Lớp trừu tượng (Abstract Class)
  2. Giao diện (Interface)

Chúng ta sẽ đi sâu vào từng loại.

1. Lớp trừu tượng (Abstract class)

Một lớp trừu tượng là một lớp được khai báo với từ khóa abstract. Nó có các đặc điểm sau:

  • Không thể khởi tạo trực tiếp: Bạn không thể tạo đối tượng (instance) trực tiếp từ một lớp trừu tượng bằng toán tử new. Mục đích của nó là để làm lớp cơ sở (base class) cho các lớp khác kế thừa.
  • Có thể chứa phương thức trừu tượng (abstract methods): Đây là những phương thức được khai báo với từ khóa abstract và không có phần thân (implementation). Các lớp con cụ thể (concrete subclasses - không phải abstract) kế thừa từ lớp trừu tượng này bắt buộc phải cung cấp triển khai (override) cho tất cả các phương thức trừu tượng đó.
  • Có thể chứa phương thức cụ thể (concrete methods): Lớp trừu tượng cũng có thể chứa các phương thức có đầy đủ phần thân. Các lớp con có thể kế thừa và sử dụng trực tiếp các phương thức này hoặc override nếu cần.
  • Có thể chứa biến instance, biến static, phương thức static, final methods và constructor: Giống như một lớp thông thường, lớp trừu tượng có thể có các thành phần này. Constructor của lớp trừu tượng sẽ được gọi khi một đối tượng của lớp con được tạo ra (thông qua super()).

Khi nào sử dụng Lớp trừu tượng?

  • Khi bạn muốn chia sẻ code chung (biến instance, phương thức cụ thể) giữa các lớp con có liên quan chặt chẽ (quan hệ "is-a").
  • Khi bạn muốn định nghĩa một "khung sườn" chung mà các lớp con phải tuân theo (bằng cách định nghĩa các phương thức trừu tượng), nhưng vẫn cho phép các lớp con có những hành vi hoặc trạng thái riêng.
  • Khi bạn muốn các lớp con phải có một tập hợp các thuộc tính hoặc phương thức chung (không phải public static final).

Ví dụ về Lớp trừu tượng:

// Định nghĩa lớp trừu tượng Shape
abstract class Shape {
    String color; // Biến instance có thể được chia sẻ

    // Constructor
    public Shape(String color) {
        System.out.println("Constructor của Shape được gọi");
        this.color = color;
    }

    // Phương thức cụ thể
    public String getColor() {
        return color;
    }

    // Phương thức trừu tượng - không có thân hàm
    // Các lớp con BẮT BUỘC phải triển khai phương thức này
    public abstract double calculateArea();

    // Một phương thức cụ thể khác
    public void displayInfo() {
        System.out.println("Đây là một hình có màu: " + color);
    }
}

// Lớp Circle kế thừa từ Shape
class Circle extends Shape {
    double radius;

    public Circle(String color, double radius) {
        super(color); // Gọi constructor của lớp cha (Shape)
        System.out.println("Constructor của Circle được gọi");
        this.radius = radius;
    }

    // Bắt buộc phải triển khai phương thức trừu tượng calculateArea()
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    // Có thể override phương thức cụ thể nếu muốn
    @Override
    public void displayInfo() {
        super.displayInfo(); // Gọi phương thức của lớp cha
        System.out.println("Loại hình: Tròn, Bán kính: " + radius);
    }
}

// Lớp Rectangle kế thừa từ Shape
class Rectangle extends Shape {
    double width;
    double height;

    public Rectangle(String color, double width, double height) {
        super(color);
        System.out.println("Constructor của Rectangle được gọi");
        this.width = width;
        this.height = height;
    }

    // Bắt buộc phải triển khai phương thức trừu tượng calculateArea()
    @Override
    public double calculateArea() {
        return width * height;
    }
}

public class AbstractionDemo {
    public static void main(String[] args) {
        // Shape myShape = new Shape("Red"); // Lỗi! Không thể khởi tạo lớp trừu tượng

        Shape circle = new Circle("Đỏ", 5.0);
        Shape rectangle = new Rectangle("Xanh", 4.0, 6.0);

        System.out.println("\nThông tin hình tròn:");
        circle.displayInfo();
        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("\nThông tin hình chữ nhật:");
        rectangle.displayInfo();
        System.out.println("Diện tích hình chữ nhật: " + rectangle.calculateArea());
        System.out.println("Màu sắc: " + rectangle.getColor());
    }
}

Trong ví dụ này, Shape là lớp trừu tượng định nghĩa cấu trúc chung (có màu sắc, phải tính được diện tích). CircleRectangle là các lớp cụ thể cung cấp cách triển khai riêng cho việc tính diện tích.

2. Giao diện (Interface)

Một giao diện trong Java là một bản thiết kế (blueprint) hoàn toàn trừu tượng của một lớp. Nó được khai báo bằng từ khóa interface. Đặc điểm chính:

  • Chứa các phương thức trừu tượng (mặc định): Trước Java 8, interface chỉ chứa các phương thức trừu tượng (không có thân hàm). Các phương thức này mặc định là public abstract.
  • Chứa hằng số (constants): Mọi biến được khai báo trong interface mặc định là public static final.
  • Không thể khởi tạo trực tiếp: Giống như abstract class, bạn không thể tạo đối tượng từ interface.
  • Lớp triển khai (implements) interface: Một lớp sử dụng từ khóa implements để "thực thi" một hoặc nhiều interface. Lớp này bắt buộc phải cung cấp triển khai cho tất cả các phương thức trừu tượng được định nghĩa trong interface đó (trừ khi lớp đó cũng là abstract class).
  • Hỗ trợ đa kế thừa (kiểu): Một lớp chỉ có thể kế thừa (extends) từ một lớp cha duy nhất, nhưng có thể triển khai (implements) nhiều interface. Điều này cho phép một lớp có nhiều "kiểu" hoặc "hành vi" khác nhau.
  • Từ Java 8: Interface có thể chứa default methods (phương thức có phần thân, có thể được override) và static methods (phương thức có phần thân, thuộc về interface, không thể override).
  • Từ Java 9: Interface có thể chứa private methodsprivate static methods để hỗ trợ việc tái sử dụng code bên trong các default và static methods của chính interface đó.

Khi nào sử dụng Interface?

  • Khi bạn muốn đạt được 100% trừu tượng (trước Java 8).
  • Khi bạn muốn định nghĩa một hợp đồng (contract) mà các lớp không liên quan đến nhau có thể tuân theo. Các lớp này chỉ cần "cam kết" thực hiện các hành vi được định nghĩa trong interface.
  • Khi bạn muốn tận dụng đa kế thừa kiểu trong Java.
  • Khi bạn muốn xác định các hành vi, khả năng (ví dụ: Runnable, Comparable, Serializable).

Ví dụ về Interface:

// Định nghĩa interface Drawable
interface Drawable {
    // Biến trong interface mặc định là public static final
    String DEFAULT_COLOR = "Black";

    // Phương thức trừu tượng (mặc định là public abstract)
    void draw();

    // Default method (từ Java 8)
    default void printInfo() {
        System.out.println("Đây là một đối tượng có thể vẽ được.");
        privateMethodHelper(); // Gọi phương thức private (từ Java 9)
    }

    // Static method (từ Java 8)
    static int getNumberOfCorners(String shapeType) {
        switch (shapeType.toLowerCase()) {
            case "circle": return 0;
            case "square": return 4;
            default: return -1; // Không xác định
        }
    }

    // Private method (từ Java 9) - chỉ dùng nội bộ trong interface
    private void privateMethodHelper() {
        System.out.println("Thông tin bổ sung từ private method.");
    }
}

// Định nghĩa interface Clickable
interface Clickable {
    void onClick();
}

// Lớp Button triển khai cả Drawable và Clickable
class Button implements Drawable, Clickable {
    String label;

    public Button(String label) {
        this.label = label;
    }

    // Bắt buộc triển khai phương thức draw() từ Drawable
    @Override
    public void draw() {
        System.out.println("Vẽ nút bấm: [" + label + "]");
    }

    // Bắt buộc triển khai phương thức onClick() từ Clickable
    @Override
    public void onClick() {
        System.out.println("Nút '" + label + "' đã được nhấn!");
    }

    // Có thể override default method nếu muốn
    @Override
    public void printInfo() {
        System.out.println("Đây là một Button có thể vẽ và nhấn.");
    }
}

// Lớp Image chỉ triển khai Drawable
class Image implements Drawable {
    String sourceFile;

    public Image(String sourceFile) {
        this.sourceFile = sourceFile;
    }

    // Bắt buộc triển khai phương thức draw() từ Drawable
    @Override
    public void draw() {
        System.out.println("Vẽ hình ảnh từ file: " + sourceFile);
    }
}


public class InterfaceDemo {
    public static void main(String[] args) {
        // Gọi static method của interface
        System.out.println("Số góc của hình vuông: " + Drawable.getNumberOfCorners("Square"));

        Drawable buttonDrawable = new Button("OK");
        Clickable buttonClickable = new Button("Cancel"); // Cùng đối tượng nhưng tham chiếu kiểu khác
        Button myButton = new Button("Submit");

        Drawable image = new Image("logo.png");

        System.out.println("\n--- Button ---");
        buttonDrawable.draw(); // Gọi draw() qua tham chiếu Drawable
        buttonClickable.onClick(); // Gọi onClick() qua tham chiếu Clickable
        myButton.printInfo();  // Gọi printInfo() (đã override)
        myButton.onClick(); // Gọi onClick() qua tham chiếu Button

        System.out.println("\n--- Image ---");
        image.draw();
        image.printInfo(); // Gọi default method printInfo() từ interface

        // Truy cập hằng số từ interface
        System.out.println("\nMàu mặc định: " + Drawable.DEFAULT_COLOR);
    }
}

Trong ví dụ này, DrawableClickable định nghĩa các "khả năng". Lớp Button có cả hai khả năng này, trong khi Image chỉ có khả năng Drawable.

3. So sánh Lớp trừu tượng và Giao diện

Đặc điểmLớp Trừu Tượng (Abstract Class)Giao diện (Interface)
Từ khóaabstract classinterface
Phương thứcCó thể chứa phương thức trừu tượng và phương thức cụ thểChỉ chứa phương thức trừu tượng (trước J8). Từ J8 có thêm defaultstatic. Từ J9 có thêm private.
BiếnCó thể chứa biến instance, static, final, non-finalChỉ chứa hằng số (public static final - mặc định)
ConstructorCó constructor (được gọi bởi lớp con)Không có constructor
Khởi tạoKhông thể khởi tạo trực tiếpKhông thể khởi tạo trực tiếp
Kế thừaLớp con dùng extends (chỉ kế thừa 1 lớp)Lớp triển khai dùng implements (có thể triển khai nhiều interface)
Mục đích chínhChia sẻ code, định nghĩa khung sườn chung cho các lớp liên quan (is-a)Định nghĩa hợp đồng, hành vi cho các lớp (có thể không liên quan), hỗ trợ đa kế thừa kiểu (can-do)
Tốc độThường nhanh hơn interface một chútChậm hơn abstract class một chút (do cần thêm bước tìm kiếm phương thức)
Mức độ trừu tượngCó thể từ 0% đến 100%Thường là 100% (trừ khi dùng default/static methods)

Nên chọn cái nào?

  • Chọn Abstract Class khi:
    • Bạn muốn chia sẻ code (phương thức cụ thể, biến instance không phải hằng số) giữa các lớp con liên quan chặt chẽ.
    • Bạn mong đợi các lớp kế thừa có nhiều phương thức hoặc thuộc tính chung.
    • Bạn cần khai báo các thành viên không phải public static final.
  • Chọn Interface khi:
    • Bạn muốn định nghĩa một "hợp đồng" về hành vi mà các lớp (có thể không liên quan) phải tuân thủ.
    • Bạn muốn tận dụng lợi thế của đa kế thừa kiểu.
    • Bạn muốn đạt được sự trừu tượng hoàn toàn (hoặc gần như hoàn toàn).
    • Bạn muốn tách biệt định nghĩa hành vi khỏi việc triển khai cụ thể.

Lợi ích của tính trừu tượng

  • Đơn giản hóa: Che giấu sự phức tạp bên trong, giúp người dùng tập trung vào việc sử dụng đối tượng.
  • Tăng tính module: Các thành phần được đóng gói và che giấu chi tiết, dễ dàng thay thế hoặc nâng cấp mà không ảnh hưởng lớn đến hệ thống.
  • Tăng khả năng bảo trì: Việc sửa lỗi hoặc thay đổi logic bên trong (phần bị ẩn) ít ảnh hưởng đến các phần khác sử dụng nó.
  • Tăng tính linh hoạt: Cho phép thay đổi cách triển khai bên trong mà không cần thay đổi cách người dùng tương tác với đối tượng.
  • Giảm sự phụ thuộc (Loose Coupling): Các lớp phụ thuộc vào interface hoặc lớp trừu tượng thay vì lớp cụ thể, giúp hệ thống linh hoạt hơn.

0 Comments

Để lại một bình luận