JavaRush /Blog Java /Random-VI /Kế thừa và thành phần trong Java
dio
Mức độ
Москва

Kế thừa và thành phần trong Java

Xuất bản trong nhóm
Bài viết này minh họa các khái niệm về kế thừa và thành phần trong Java. Ví dụ đầu tiên thể hiện tính kế thừa và sau đó cho thấy cách cải thiện thiết kế kế thừa bằng cách sử dụng thành phần. Chúng tôi sẽ tóm tắt cách lựa chọn giữa chúng ở phần cuối. Kế thừa và Thành phần trong Java - 1

1. Thừa kế

Giả sử rằng chúng ta có một lớp Insect(côn trùng tiếng Anh) Lớp này chứa hai phương thức: 1. move()(từ tiếng Anh di chuyển) và 2. attack()(từ tiếng Anh tấn công)
class Insect {
	private int size;
	private String color;

	public Insect(int size, String color) {
		this.size = size;
		this.color = color;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

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

	public void move() {
		System.out.println("Move");
	}

	public void attack() {
		move(); //assuming an insect needs to move before attacking
		System.out.println("Attack");
	}
}
Bây giờ bạn muốn định nghĩa một lớp Bee(con ong tiếng Anh), là một trong các loại Insect, nhưng có cách triển khai khác nhau attack()move(). Điều này có thể được thực hiện bằng cách sử dụng tính kế thừa:
class Bee extends Insect {
	public Bee(int size, String color) {
		super(size, color);
	}

	public void move() {
		System.out.println("Fly");
	}

	public void attack() {
		move();
		super.attack();
	}
}
public class InheritanceVSComposition {
	public static void main(String[] args) {
		Insect i = new Bee(1, "red");
		i.attack();
	}
}
Sơ đồ phân cấp lớp khá đơn giản: Kế thừa và Thành phần trong Java - 2Kết quả thực hiện:
Fly
Fly
Attack
"Fly" được gõ hai lần nên phương thức này move()được gọi hai lần. Nhưng nó chỉ nên được gọi một lần. Vấn đề là do super.attack(). Phương thức này attack ()gọi một phương thức move()lớp Insect. Khi một lớp con gọi super.attack (), nó cũng gọi phương thức được ghi đè move(). Để khắc phục sự cố chúng ta có thể:
  1. Loại bỏ attack()phương thức lớp con. Điều này sẽ làm cho lớp con phụ thuộc vào attack()việc triển khai phương thức của lớp cha. Nếu attack()attack()siêu lớp bắt đầu sử dụng một phương thức di chuyển khác thì lớp con cũng sẽ cần phải thay đổi. Đây là sự đóng gói xấu.
  2. Viết lại phương thức attack()như sau:

    public void attack() {
    	move();
    	System.out.println("Attack");
    }
  3. Điều này đảm bảo kết quả chính xác vì lớp con không còn phụ thuộc vào lớp cha nữa. Tuy nhiên, mã này là bản sao của siêu lớp. (phương thức này attack()thực hiện những việc phức tạp hơn là chỉ xuất ra một chuỗi). Đây không phải là thiết kế phần mềm tốt và không nên có mã trùng lặp.

Thiết kế kế thừa này không tốt vì lớp con phụ thuộc vào chi tiết triển khai của siêu lớp của nó. Nếu lớp cha thay đổi, lớp con sẽ không hoạt động chính xác.

2. Thành phần

Bạn có thể sử dụng thành phần thay vì kế thừa. Hãy xem xét một giải pháp sử dụng nó. Chức năng này attack()được trừu tượng hóa như một giao diện.
interface Attack {
	public void move();
	public void attack();
}
Các kiểu tấn công khác nhau có thể được xác định bằng cách triển khai giao diện Tấn công.
class AttackImpl implements Attack {
	private String move;
	private String attack;

	public AttackImpl(String move, String attack) {
		this.move = move;
		this.attack = attack;
	}

	@Override
	public void move() {
		System.out.println(move);
	}

	@Override
	public void attack() {
		move();
		System.out.println(attack);
	}
}
Vì chức năng tấn công là bên ngoài nên lớp Insectkhông còn chứa nó nữa.
class Insect {
	private int size;
	private String color;

	public Insect(int size, String color) {
		this.size = size;
		this.color = color;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}
}
Class Bee(từ English Bee), loại này Insectcó thể tấn công như thế nào.
// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
	private Attack attack;

	public Bee(int size, String color, Attack attack) {
		super(size, color);
		this.attack = attack;
	}

	public void move() {
		attack.move();
	}

	public void attack() {
		attack.attack();
	}
}
Sơ đồ lớp: Kế thừa và Thành phần trong Java - 3
public class InheritanceVSComposition2 {
	public static void main(String[] args) {
		Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
		a.attack();

		// if you need another implementation of move()
		// there is no need to change Insect, we can quickly use new method to attack

		Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
		b.attack();
	}
}
Kết quả thực hiện:
fly
move
fly
sting

3. Khi nào nên sử dụng những phương pháp này?

2 điểm sau đây có thể giúp bạn quyết định giữa kế thừa và thành phần:
  1. Nếu bạn đang xử lý mối quan hệ giữa các lớp có dạng "IS" và một lớp muốn cung cấp tất cả giao diện của nó cho lớp khác thì nên ưu tiên tính kế thừa.
  2. nếu mối quan hệ là "HAS" thì thành phần được ưu tiên hơn.
Vì vậy, tính kế thừa và thành phần có những ứng dụng riêng và cần hiểu rõ giá trị của chúng. Liên kết:
  1. Bloch, Joshua. java hiệu quả. Pearson Giáo dục Ấn Độ, 2008
  2. https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
  3. https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one- Should...
Liên kết tới bài viết gốc đã được dịch
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION