+2

Tìm hiểu tính đa hình trong Java

Tổng quan về tính đa hình

Tính đa hình trong Java là một khái niệm quan trọng trong lập trình hướng đối tượng, cho phép các đối tượng có thể thể hiện hành vi khác nhau dựa trên cùng một giao diện hoặc lớp cơ sở. Có hai loại đa hình trong Java: đa hình tại biên biên dịch và đa hình tại runtime.

  1. Đa hình tại biên dịch (Compile-time Polymorphism): Được thực hiện thông qua việc sử dụng nạp chồng phương thức (method overloading) và nạp chồng toán tử (operator overloading).

    • Nạp chồng phương thức (Method Overloading): Đây là quá trình định nghĩa nhiều phương thức có cùng tên trong một lớp nhưng khác nhau về số lượng tham số, kiểu dữ liệu của tham số hoặc cả hai.

      class Calculator {
          int add(int a, int b) {
              return a + b;
          }
      
          double add(double a, double b) {
              return a + b;
          }
      }
      
    • Nạp chồng toán tử (Operator Overloading): Java không hỗ trợ nạp chồng toán tử như một số ngôn ngữ khác như C++, vì vậy bạn không thể định nghĩa các toán tử (+, -, *, /) cho các lớp do người dùng tạo.

  2. Đa hình tại runtime (Runtime Polymorphism): Được thực hiện thông qua kỹ thuật kế thừa và ghi đè phương thức (method overriding).

    • Kế thừa (Inheritance): Đối tượng con có thể thừa hưởng các thuộc tính và phương thức từ đối tượng cha. Khi một phương thức trong lớp con ghi đè (override) một phương thức trong lớp cha, chúng ta có thể gọi phương thức của lớp con nhưng hành vi được xác định tại runtime sẽ phụ thuộc vào đối tượng được tạo.

      class Animal {
          void makeSound() {
              System.out.println("Animal makes a sound");
          }
      }
      
      class Dog extends Animal {
          void makeSound() {
              System.out.println("Dog barks");
          }
      }
      
      class Cat extends Animal {
          void makeSound() {
              System.out.println("Cat meows");
          }
      }
      
    • Ghi đè phương thức (Method Overriding): Đối tượng con cung cấp triển khai mới cho một phương thức đã được định nghĩa trong lớp cha.

    Khi sử dụng đa hình, bạn có thể thao tác với đối tượng dựa trên giao diện hoặc lớp cơ sở mà không cần biết chi tiết về loại đối tượng cụ thể được sử dụng. Điều này giúp code trở nên linh hoạt và dễ bảo trì hơn, vì bạn có thể thêm các đối tượng mới mà không cần thay đổi nhiều phần code hiện tại.

Một số ví dụ

Ví dụ 1:

Hãy xem xét một ví dụ về đa hình tại runtime thông qua kế thừa và ghi đè phương thức. Hãy tưởng tượng chúng ta có một lớp Shape (Hình dạng) và các lớp con của nó như Circle (Hình tròn) và Square (Hình vuông). Mỗi lớp con sẽ ghi đè phương thức calculateArea() để tính diện tích của hình đó.

// Lớp cơ sở Shape
class Shape {
    public void calculateArea() {
        System.out.println("Tính diện tích của hình...");
    }
}

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

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

    // Ghi đè phương thức calculateArea() để tính diện tích của hình tròn
    @Override
    public void calculateArea() {
        double area = Math.PI * radius * radius;
        System.out.println("Diện tích hình tròn là: " + area);
    }
}

// Lớp con Square kế thừa từ Shape
class Square extends Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }

    // Ghi đè phương thức calculateArea() để tính diện tích của hình vuông
    @Override
    public void calculateArea() {
        double area = side * side;
        System.out.println("Diện tích hình vuông là: " + area);
    }
}

Bây giờ, chúng ta có thể tạo các đối tượng CircleSquare, sau đó gọi phương thức calculateArea(). Mặc dù chúng ta gọi cùng một phương thức từ lớp Shape, nhưng hành vi cụ thể được thực thi sẽ phụ thuộc vào loại đối tượng mà chúng ta đang sử dụng.

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5); // Tạo đối tượng hình tròn
        Shape square = new Square(4); // Tạo đối tượng hình vuông

        circle.calculateArea(); // Gọi phương thức tính diện tích của hình tròn
        square.calculateArea(); // Gọi phương thức tính diện tích của hình vuông
    }
}

Khi chạy chương trình này, phương thức calculateArea() sẽ được gọi tương ứng với loại hình đối tượng được tạo ra. Điều này chứng minh tính đa hình tại runtime trong Java, cho phép chúng ta sử dụng cùng một phương thức từ lớp cơ sở Shape, nhưng hành vi cụ thể được xác định tại thời điểm runtime dựa trên loại của đối tượng.

Ví dụ 2:

Tất nhiên, hãy xem xét một ví dụ trong một dự án Java thực tế hơn, ví dụ về hệ thống quản lý nhân viên trong một công ty. Trong hệ thống này, có nhiều loại nhân viên như Nhân viên bán hàng, Nhân viên quản lý và Nhân viên sản xuất. Mỗi loại nhân viên sẽ có các thuộc tính và hành vi riêng của mình.

// Lớp cơ sở Employee (Nhân viên)
public abstract class Employee {
    private String name;
    private int employeeId;

    public Employee(String name, int employeeId) {
        this.name = name;
        this.employeeId = employeeId;
    }

    public abstract double calculateSalary(); // Phương thức tính lương trừu tượng

    public void displayInfo() {
        System.out.println("Employee ID: " + employeeId);
        System.out.println("Name: " + name);
    }
}

// Lớp SalesEmployee (Nhân viên bán hàng)
public class SalesEmployee extends Employee {
    private double sales;

    public SalesEmployee(String name, int employeeId, double sales) {
        super(name, employeeId);
        this.sales = sales;
    }

    @Override
    public double calculateSalary() {
        // Lương cơ bản cộng thêm 10% doanh số bán hàng
        return 2000 + (0.1 * sales);
    }
}

// Lớp ManagerEmployee (Nhân viên quản lý)
public class ManagerEmployee extends Employee {
    private int teamSize;

    public ManagerEmployee(String name, int employeeId, int teamSize) {
        super(name, employeeId);
        this.teamSize = teamSize;
    }

    @Override
    public double calculateSalary() {
        // Lương cơ bản cộng thêm 5% số lượng nhân viên quản lý
        return 3000 + (0.05 * teamSize * 1000); // Giả sử mỗi nhân viên quản lý được tính là 1000 đơn vị
    }
}

// Lớp ProductionEmployee (Nhân viên sản xuất)
public class ProductionEmployee extends Employee {
    private int hoursWorked;

    public ProductionEmployee(String name, int employeeId, int hoursWorked) {
        super(name, employeeId);
        this.hoursWorked = hoursWorked;
    }

    @Override
    public double calculateSalary() {
        // Lương cơ bản cộng thêm số giờ làm việc nhân với mức lương theo giờ
        return 1500 + (hoursWorked * 20); // Giả sử mỗi giờ làm việc được tính là 20 đơn vị
    }
}

Trong ví dụ này, các lớp SalesEmployee, ManagerEmployeeProductionEmployee đều kế thừa từ lớp cơ sở Employee. Mỗi lớp con triển khai phương thức calculateSalary() để tính lương dựa trên các thuộc tính đặc thù của từng loại nhân viên.

Khi chạy chương trình, bạn có thể tạo các đối tượng của các loại nhân viên và gọi phương thức calculateSalary() để tính toán lương dựa trên loại nhân viên tương ứng. Điều này thể hiện tính đa hình tại runtime, khi các đối tượng được tạo và phương thức được gọi dựa trên loại cụ thể của từng đối tượng nhân viên.

Ví dụ 3:

Hãy tưởng tượng chúng ta xây dựng một ứng dụng quản lý thư viện với nhiều loại tài liệu như Sách, Tạp chí và Báo. Mỗi loại tài liệu sẽ có các thuộc tính và hành vi riêng, và chúng ta sẽ sử dụng tính đa hình để quản lý chúng.

// Lớp cơ sở Document (Tài liệu)
public abstract class Document {
    private String title;

    public Document(String title) {
        this.title = title;
    }

    public abstract void displayInfo(); // Phương thức hiển thị thông tin tài liệu trừu tượng
}

// Lớp Book (Sách)
public class Book extends Document {
    private String author;
    private int numberOfPages;

    public Book(String title, String author, int numberOfPages) {
        super(title);
        this.author = author;
        this.numberOfPages = numberOfPages;
    }

    @Override
    public void displayInfo() {
        System.out.println("Book Title: " + title);
        System.out.println("Author: " + author);
        System.out.println("Number of Pages: " + numberOfPages);
    }
}

// Lớp Journal (Tạp chí)
public class Journal extends Document {
    private int issueNumber;
    private String publisher;

    public Journal(String title, int issueNumber, String publisher) {
        super(title);
        this.issueNumber = issueNumber;
        this.publisher = publisher;
    }

    @Override
    public void displayInfo() {
        System.out.println("Journal Title: " + title);
        System.out.println("Issue Number: " + issueNumber);
        System.out.println("Publisher: " + publisher);
    }
}

// Lớp Newspaper (Báo)
public class Newspaper extends Document {
    private String date;
    private String editor;

    public Newspaper(String title, String date, String editor) {
        super(title);
        this.date = date;
        this.editor = editor;
    }

    @Override
    public void displayInfo() {
        System.out.println("Newspaper Title: " + title);
        System.out.println("Date: " + date);
        System.out.println("Editor: " + editor);
    }
}

Trong ví dụ này, mỗi loại tài liệu (Book, Journal, Newspaper) kế thừa từ lớp cơ sở Document và triển khai phương thức displayInfo() để hiển thị thông tin cụ thể về từng loại tài liệu.

Khi chạy ứng dụng, bạn có thể tạo các đối tượng Book, Journal và Newspaper, sau đó gọi phương thức displayInfo() trên mỗi đối tượng. Mặc dù chúng ta gọi cùng một phương thức từ lớp cơ sở Document, nhưng hành vi cụ thể của việc hiển thị thông tin sẽ được xác định tại runtime dựa trên loại cụ thể của từng đối tượng tài liệu.

Kết luận

Tính đa hình (Polymorphism) là một khái niệm quan trọng trong lập trình hướng đối tượng, đặc biệt trong ngôn ngữ Java, mang lại tính linh hoạt và tái sử dụng code. Đa hình cho phép các đối tượng được xử lý dưới dạng các đối tượng của lớp cơ sở chung mà không cần biết về loại đối tượng cụ thể.

Trong Java, tính đa hình thể hiện ở hai dạng chính: đa hình tại biên dịch (compile-time polymorphism) và đa hình tại runtime (runtime polymorphism).

Đa hình tại biên dịch được thực hiện thông qua nạp chồng phương thức và toán tử. Nạp chồng phương thức (method overloading) cho phép định nghĩa nhiều phương thức có cùng tên trong một lớp nhưng khác nhau về số lượng tham số hoặc kiểu dữ liệu của tham số. Toán tử overloading, tuy không được hỗ trợ mạnh mẽ trong Java nhưng cũng cho phép định nghĩa các toán tử với các hành vi khác nhau tùy thuộc vào kiểu dữ liệu.

Đa hình tại runtime thường được thực hiện thông qua kế thừa và ghi đè phương thức (method overriding). Khi một phương thức trong lớp con ghi đè một phương thức trong lớp cha, việc gọi phương thức này từ đối tượng con sẽ thực thi hành vi đã được định nghĩa trong lớp con tại thời điểm runtime.

Tính đa hình giúp tạo ra code linh hoạt và dễ bảo trì. Nó cho phép mở rộng chương trình bằng cách thêm các lớp mới mà không làm thay đổi nhiều phần code hiện tại. Việc sử dụng đa hình giúp giảm sự phụ thuộc vào chi tiết của từng đối tượng cụ thể, tăng tính linh hoạt và tái sử dụng code.

Ngoài ra, tính đa hình còn là một trong những nguyên lý quan trọng của nguyên lý SOLID trong lập trình hướng đối tượng, đặc biệt là nguyên tắc "L" - Liskov Substitution Principle, khuyến khích việc sử dụng đa hình để thực hiện việc thay thế các đối tượng của lớp cơ sở bằng các đối tượng của lớp con mà không làm thay đổi tính đúng đắn của chương trình.

Tóm lại, tính đa hình trong Java là một khái niệm mạnh mẽ, cho phép xử lý các đối tượng dưới dạng các đối tượng của lớp cơ sở chung, giúp tăng tính linh hoạt, tái sử dụng code và làm cho chương trình trở nên dễ bảo trì và mở rộng hơn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí