Java Bài 30: Đa Hình (Polymorphism) – YellowCode.Books

Rate this item :

Rating: 5.0/5. From 56 votes.

Please wait…

Chào mừng những bạn đã đến với bài học kinh nghiệm Java số 30. Bài học về tính đa hình ( polymorphism ). Đây là bài học kinh nghiệm trong chuỗi bài viết về lập trình ngôn từ Java của Yellow Code Books .
Bài ngày hôm nay tất cả chúng ta sẽ nói sâu về tính Đa hình trong Java. Nghe qua đặc tính này thì có vẻ khó. Một phần vì ứng dụng của chúng không nhiều. Với cái tên nghe chẳng có cố định và thắt chặt gì cả, như là biến hình gì gì đó. Cộng với khá ít tài liệu viết rõ về công suất này của OOP .
Vậy thì tất cả chúng ta cùng đi sâu vào bài học kinh nghiệm để xem Đa hình là gì và nó có thực sự khó không nhé .

Tính Đa Hình ( Polymorphism ) Là Gì ?

Lần này thì nghĩa tiếng Anh và tiếng Việt trong lập trình Java lại khớp với nhau. Không nhiều các từ lan man, chỉ có Đa hình, hoặc Polymorphism mà thôi.

Vậy tại sao lại Đa hình ? Như bạn biết, vốn dĩ OOP là một phương pháp tư duy lập trình hướng trong thực tiễn, nên hiển nhiên những khái niệm của nó cũng phải sát với những đặc thù trong trong thực tiễn. Trong đó có Đa hình. Trong trong thực tiễn, sự Đa hình được xem như một đối tượng người dùng đặc biệt quan trọng, có lúc đối tượng người dùng này mang một hình dạng ( trở thành một đối tượng người dùng ) nào đó, và cũng có lúc đối tượng người dùng này lại mang một hình dạng khác nữa, tùy vào từng thực trạng. Sự “ nhập vai ” vào những hình dạng ( đối tượng người tiêu dùng ) khác nhau này giúp cho đối tượng người tiêu dùng Đa hình bắt đầu hoàn toàn có thể triển khai những hành vi khác nhau của từng đối tượng người dùng đơn cử. Chẳng hạn nếu ở công ty bạn, có nhân viên cấp dưới nhận hai nghĩa vụ và trách nhiệm khác nhau, họ vừa là nhân viên cấp dưới toàn thời hạn ở những ngày trong tuần, nhưng làm bán thời hạn ở những ngày cuối tuần. Vậy thì, để tính lương cho nhân viên cấp dưới này, tùy vào từng thời gian mà mạng lưới hệ thống sẽ xem nhân viên cấp dưới đó là toàn thời hạn hay bán thời hạn, và phương pháp tính lương của mỗi loại nhân viên cấp dưới sẽ triển khai đo lường và thống kê một cách hiệu suất cao nhất dựa vào từng vai trò khác nhau này. Bạn cũng hiểu sơ sơ về Đa hình rồi đúng không nào .
Có một điều chắc như đinh rằng. Nếu như không xem hành vi tính lương của nhân viên cấp dưới như ví dụ trên kia là Đa hình, thì tất cả chúng ta vẫn cứ thiết kế xây dựng được một mạng lưới hệ thống tính lương hoàn hảo, nhưng sẽ phức tạp hơn là nếu bạn biết kỹ năng và kiến thức về Đa hình là gì .
Và còn một ý nữa. Rằng tính Đa hình của bài thời điểm ngày hôm nay cũng là một trong những đặc tính nổi trội mà OOP mang lại đấy nhé. Bạn cố gắng nỗ lực chớp lấy và tận dụng. Ôn lại một tí những đặc tính cốt lõi của OOP gồm có :

– Tính Gói ghém dữ liệu (Encapsulation). Tính chất này được thể hiện qua các kiến thức về khả năng truy cập, getter/setter.
– Tính Kế thừa (Inheritance). Tính chất này được thể hiện qua các kiến thức về kế thừa, overriding, overloading.
– Tính Đa hình (Polymorphism). Bài hôm nay chúng ta sẽ học.
– Tính Trừu tượng (Abstraction). Bài sau chúng ta sẽ học.

Sử Dụng Tính Đa Hình Như Thế Nào ?

Đến đây chắc như đinh bạn đã hiểu sơ bộ khái niệm Đa hình. Vậy thì trong OOP tất cả chúng ta tổ chức triển khai và sử dụng đặc tính Đa hình này như thế nào ?
Thứ nhất, Đa hình sẽ gắn liền với kế thừa. Và, Đa hình cũng sẽ gắn liền với ghi đè phương pháp ( overriding ) nữa. Bởi vì như trên đây có nói đó, Đa hình là nói đến một đối tượng người tiêu dùng nào đó có năng lực nhập vai thành những đối tượng người dùng khác. Vậy thì để mà một đối tượng người dùng hoàn toàn có thể là một đối tượng người dùng nào đó, ắt hẳn nó phải là đối tượng người tiêu dùng cha. Và để đối tượng người tiêu dùng cha hoàn toàn có thể là một trong những đối tượng người tiêu dùng con ở từng thực trạng, thì nó phải định nghĩa ra những phương pháp để con của nó hoàn toàn có thể ghi đè. Điều này giúp mạng lưới hệ thống xác lập được đối tượng người dùng nào và phương pháp nào thực sự đang hoạt động giải trí khi ứng dụng đang chạy. Nên nhiều tài liệu gọi Đa hình này là Đa hình tại runtime là vậy .

Chúng ta sẽ đến ví dụ sau cho dễ hiểu hơn. Ví dụ khá đơn giản. Lớp HinhHoc là lớp cha, hai lớp con HinhTronHinhChuNhat đều override phương thức tinhDienTich() từ cha.

Đa hình - Sơ đồ lớp ví dụĐa hình - Sơ đồ lớp ví dụ

Code của chúng cũng khá đơn thuần, tất cả chúng ta vô hiệu hết toàn bộ những râu ria khác, chỉ tập trung chuyên sâu vào những phương pháp override mà thôi .

HinhHoc

public class HinhHoc {
	
	public void tinhDienTich() {
		System.out.println("Chưa biết hình nào");
	}
}

HinhTron

public class HinhTron extends HinhHoc {
	
	@Override
	public void tinhDienTich() {
		System.out.println("Đây là Diện tích hình Tròn");
	}
 
}

HinhChuNhat

public class HinhChuNhat extends HinhHoc {
 
	@Override
	public void tinhDienTich() {
		System.out.println("Đây là Diện tích hình Chữ nhật");
	}

}

Nào, sự diệu kỳ của tính Đa hình là đây, bạn hãy chú ý quan tâm vào đoạn code khai báo và sử dụng những phương pháp được overriding trên kia như sau .

MainClass

public class MainClass {
 
    public static void main(String[] args) {
        HinhHoc hinhHoc = new HinhHoc();
        hinhHoc.tinhDienTich(); // Đoạn code này bình thường, sẽ in ra "Chưa biết hình nào"
        
        // Có lúc hinhHoc đóng vai trò là HinhTron trong một ngữ cảnh nào đó
        hinhHoc = new HinhTron();
        hinhHoc.tinhDienTich(); // Đoạn code này sẽ in ra "Đây là Diện tích Hình tròn"
        
        // Có lúc hinhHoc đóng vai trò là HinhChuNhat trong một ngữ cảnh nào đó
        hinhHoc = new HinhChuNhat();
        hinhHoc.tinhDienTich(); // Đoạn code này sẽ in ra "Đây là Diện tích Chữ nhật"
    }
 
}

Bạn đã thấy đó, đối tượng HinhHoc bản thân nó có một phương thức tinhDienTich(). Nhưng khác với cách sử dụng các đối tượng từ các bài học từ trước đến giờ, rằng mỗi khi cần đến các lớp con thực hiện việc tính diện tích, chúng ta sẽ khai báo lớp con và gọi phương thức được override ở lớp con. Thì bài hôm nay chúng ta cho phép lớp HinhHoc có khả năng đóng vai trò là lớp con, bằng cách khởi tạo lại đối tượng là lớp con của nó, HinhHoc hinhHoc = new HinhTron(), rồi chính nó sẽ đóng vai là lớp con đó. Tính Đa hình là đây.

Thực Hành Xây Dựng Ứng Dụng Tính Lương Cho Nhân Viên

Nếu như ở bài học trước chúng ta đã xây dựng “hoàn chỉnh” một hệ thống tính lương “phức tạp” cho một công ty “bự”. Nhưng code khi đó lại không mang rõ tính ứng dụng thực tế, bởi vì chúng ta đã code “cứng” ở chỗ biết trước anh nhân viên nào là lính, anh nào là sếp, anh nào làm toàn thời gian, anh nào làm bán thời gian, để mà khai báo các đối tượng NhanVienFullTime hay NhanVienPartTime tương ứng.

Vậy sang bài hôm nay, chúng ta sẽ hoàn thiện ứng dụng tính lương nhân viên của bài trước. Làm cho hệ thống trở nên thực tế hơn. Cụ thể, bài này chúng ta sẽ cho người dùng nhập bằng tay thông tin nhân viên. Và vì vậy sẽ có một mảng các nhân viên trong ứng dụng. Lớp NhanVien sẽ là lớp có sử dụng đặc tính Đa hình để có thể đóng vai trò là NhanVienFullTime hoặc NhanVienPartTime ở từng hoàn cảnh cụ thể.

Mô Tả Lại Yêu Cầu Chương Trình

Yêu cầu của chương trình tính lương không hề biến hóa so với bài trước. Mình chỉ diễn đạt lại thôi .

– Công ty có hai loại nhân viên: nhân viên toàn thời gian và nhân viên thời vụ.
– Nhân viên toàn thời gian là lính sẽ hưởng lương 10 củ một tháng. Nhân viên toàn thời gian là sếp sẽ hưởng lương 20 củ một tháng.
– Nhân viên toàn thời gian nếu làm thêm ngày nào thì sẽ được cộng thêm 800k mỗi ngày, bất kể chức vụ.
– Nhân viên thời vụ cứ làm mỗi giờ được 100k, không phân biệt chức vụ gì cả. Làm nhiều thì hưởng nhiều.

Ứng dụng sẽ được cho phép người dùng nhập vào số lượng nhân viên cấp dưới. Sau đó với từng nhân viên cấp dưới, người dùng phải nhập vào tên nhân viên cấp dưới, loại nhân viên cấp dưới toàn thời hạn hay bán thời hạn, nhân viên cấp dưới toàn thời hạn thì là nhân viên cấp dưới lính hay nhân viên cấp dưới sếp, có làm thêm ngày nào không, nhân viên cấp dưới thời vụ thì làm được mấy giờ. Cuối cùng dựa vào những thông tin đó, sẽ xuất ra màn hình hiển thị lương tương ứng cho tổng thể nhân viên cấp dưới .

Sơ Đồ Lớp

Chúng ta vẫn dựa vào sơ đồ lớp của bài trước. Nhưng chỉnh sửa một chút sao cho lớp NhanVien sẽ “vào vai” tốt các lớp con của nó. Bằng cách xây dựng thêm phương thức tinhLuong() ở lớp này, rồi ở các lớp con sẽ phải override lại.

Đa hình - Sơ đồ lớp bài thực hànhĐa hình - Sơ đồ lớp bài thực hành

Xây Dựng Các Lớp

Lớp Configs không hề thay đổi.

package util;

public class Configs {

	// Loại nhân viên
	public static final int NHAN_VIEN_SEP = 1;
	public static final int NHAN_VIEN_LINH = 2;
	
	// Lương nhân viên
	public static final long LUONG_NHAN_VIEN_FULL_TIME_SEP = 20000000; // Lương tháng của sếp
	public static final long LUONG_NHAN_VIEN_FULL_TIME_LINH = 10000000; // Lương tháng của lính
	public static final long LUONG_LAM_THEM_MOI_NGAY = 800000; // Làm thêm mỗi ngày của nhân viên toàn thời gian được 800 k
	public static final long LUONG_NHAN_VIEN_PART_TIME_MOI_GIO = 100000; // Lương nhân viên thời vụ mỗi giờ 100 k
}

Lớp NhanVien chỉ có thêm phương thức tinhLuong() để thực hiện Đa hình trên phương thức này.

package model;

public class NhanVien {

	protected String ten;
	protected long luong;
	
	public NhanVien() {	
	}
	
	public NhanVien(String ten) {
		this.ten = ten;
	}
	
	protected String loaiNhanVien() {
		// Lớp con phải override để lo vụ loại nhân viên này
		return "";
	}
	
	public void tinhLuong() {
		// Lớp con phải override để lo vụ tính lương này
	}
	
	public void xuatThongTin() {
		System.out.println("===== Nhân viên: " + ten + " =====");
		System.out.println("- Loại nhân viên: " + loaiNhanVien());
		System.out.println("- Lương: " + luong + " VND");
	}
}

Lớp NhanVienFullTimeNhanVienPartTime cũng không thay đổi gì. Chỉ có giảm bớt overloading ở constructor để khâu nhập liệu được dễ dàng hơn thôi.

package model;

import util.Configs;

/**
 * NhanVienFullTime chính là nhân viên toàn thời gian
 */
public class NhanVienFullTime extends NhanVien {
	
	private int ngayLamThem; // Ngày làm thêm của nhân viên
	private int loaiChucVu; // Chức vụ là lính hay sếp
	
	public NhanVienFullTime(String ten, int ngayLamThem, int loaiChucVu) {
		super(ten);
		this.ngayLamThem = ngayLamThem;
		this.loaiChucVu = loaiChucVu;
	}
	
	@Override
	public String loaiNhanVien() {
		if (loaiChucVu == Configs.NHAN_VIEN_LINH) {
			return "Lính toàn thời gian" + (ngayLamThem > 0 ? " (có làm thêm ngày)":"");
		} else {
			return "Sếp toàn thời gian" + (ngayLamThem > 0 ? " (có làm thêm ngày)":"");
		}
	}
	
	@Override
	public void tinhLuong() {
		if (loaiChucVu == Configs.NHAN_VIEN_LINH) {
			luong = Configs.LUONG_NHAN_VIEN_FULL_TIME_LINH + ngayLamThem * Configs.LUONG_LAM_THEM_MOI_NGAY;
		} else if (loaiChucVu == Configs.NHAN_VIEN_SEP) {
			luong = Configs.LUONG_NHAN_VIEN_FULL_TIME_SEP + ngayLamThem * Configs.LUONG_LAM_THEM_MOI_NGAY;
		}
	}
}
package model;

import util.Configs;

/**
 * NhanVienPartTime chính là nhân viên thời vụ
 */
public class NhanVienPartTime extends NhanVien {
	
	private int gioLamViec; // Tổng số giờ làm việc của nhân viên
	
	public NhanVienPartTime(String ten, int gioLamViec) {
		this.ten = ten;
		this.gioLamViec = gioLamViec;
	}
	
	@Override
	public String loaiNhanVien() {
		return "Nhân viên thời vụ";
	}
	
	@Override
	public void tinhLuong() {
		luong = Configs.LUONG_NHAN_VIEN_PART_TIME_MOI_GIO * gioLamViec;
	}
}

Và đây. Mọi thay đổi sẽ nằm ở phương thức main(). Nếu bạn đừng để ý đến các đoạn code râu ria nhập liệu từ console. Thì phương thức main() có các ý sau chúng ta nên lưu tâm.

– Lần đầu tiên, chúng ta sử dụng đến mảng các đối tượng. Và bạn thấy rằng, mảng các đối tượng cũng chẳng khác mảng của các kiểu nguyên thủy mà bạn đã học là mấy.
– Với mỗi phần tử trong mảng các NhanVien. Chúng ta khởi tạo NhanVien này là NhanVienFullTime hay NhanVienPartTime là do điều kiện mà người dùng nhập vào. Tính Đa hình phát huy tác dụng ở đây.

package main;

import java.util.Scanner;

import model.NhanVien;
import model.NhanVienFullTime;
import model.NhanVienPartTime;

public class MainClass {

	public static void main(String[] args) {
		// Kêu người dùng nhập vào số lượng nhân viên trong công ty
		Scanner scanner = new Scanner(System.in);
		System.out.print("Hãy nhập số lượng nhân viên: ");
		int tongNhanVien = Integer.parseInt(scanner.nextLine());
		
		// Khai báo mảng các nhân viên
		NhanVien[] mangNhanVien = new NhanVien[tongNhanVien];
		for (int i = 0; i < tongNhanVien; i++) {
			// Khai báo từng loại nhân viên, và kêu người dùng nhập thông tin nhân viên
			System.out.print("Tên nhân viên " + (i + 1) + ": ");
			String ten = scanner.nextLine();
			System.out.print("Là nhân viên (1-Toàn thời gian; 2-Bán thời gian): ");
			int laNhanVien = Integer.parseInt(scanner.nextLine());
			if (laNhanVien == 1) {
				// Nhân viên toàn thời gian
				System.out.print("Chức vụ nhân viên (1-Sếp; 2-Lính): ");
				int chucVu = Integer.parseInt(scanner.nextLine());
				System.out.print("Ngày làm thêm (nếu có): ");
				int ngayLamThem = Integer.parseInt(scanner.nextLine());
				mangNhanVien[i] = new NhanVienFullTime(ten, ngayLamThem, chucVu);
			} else {
				System.out.print("Giờ làm: ");
				int gioLamViec = Integer.parseInt(scanner.nextLine());
				mangNhanVien[i] = new NhanVienPartTime(ten, gioLamViec);
			}
		}
		
		System.out.println("\nKết quả tính lương\n");
		
		// Tính lương và xuất thông tin nhân viên
		for (NhanVien nhanVien : mangNhanVien) {
			nhanVien.tinhLuong();
			nhanVien.xuatThongTin();
		}
	}

}

Cuối cùng là tác dụng thực thi chương trình. Ngoài việc nhập liệu động ra thì tác dụng in ra là như bài hôm trước. Bạn thử tích hợp việc nhập liệu động của bài thời điểm ngày hôm nay với việc không sử dụng tính Đa hình, mà dùng như bài học kinh nghiệm hôm trước xem. Với cách thử nghiệm này, bạn sẽ hiểu rõ hơn về thế mạnh của Đa hình đấy .

Đa hình - Kết quả console bài thực hànhĐa hình - Kết quả console bài thực hành

Đa hình là vậy, bạn có thấy khó không nào. Sẽ còn kiến thức có liên quan đến Đa hình nữa, như ép kiểu trong OOP chẳng hạn, mà chúng ta sẽ nói ở bài học sau.

Cảm ơn bạn đã đọc những bài viết của Yellow Code Books. Bạn hãy nhìn nhận 5 sao nếu thấy thích bài viết, hãy comment bên dưới nếu có vướng mắc, hãy để lại địa chỉ email của bạn để nhận được thông tin mới nhất khi có bài viết mới, và nhớ san sẻ những bài viết của Yellow Code Books đến nhiều người khác nữa nhé .

Bài Kế Tiếp

Chúng ta đã biết đến ép kiểu so với kiểu tài liệu nguyên thủy rồi đúng không nào. Vậy thì ép kiểu với những đối tượng người dùng OOP có gì khác không. Bài sau tất cả chúng ta cùng khám phá nhé .