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.
1. Thừa kế
Giả sử rằng chúng ta có một lớpInsect
(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()
và 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ế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ể:
- 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àoattack()
việc triển khai phương thức của lớp cha. Nếuattack()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. -
Viết lại phương thức
attack()
như sau:public void attack() { move(); System.out.println("Attack"); }
-
Đ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.
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àyattack()
đượ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 Insect
khô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 Insect
có 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:
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:- 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.
- nếu mối quan hệ là "HAS" thì thành phần được ưu tiên hơn.
- Bloch, Joshua. java hiệu quả. Pearson Giáo dục Ấn Độ, 2008
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one- Should...
GO TO FULL VERSION