Điều kinh khủng đối với bộ nhớ đối số của người Viking là hoàn toàn sai, nhưng đó là một cách thực hành khách quan của người Bỉ. Khi bạn kế thừa từ một lớp, bạn không chỉ kế thừa các trường và phương thức bạn quan tâm. Thay vào đó, bạn kế thừa mọi thứ . Mọi phương pháp mà nó tuyên bố, ngay cả khi nó không hữu ích cho bạn. Và quan trọng nhất, bạn cũng kế thừa tất cả các hợp đồng của nó và đảm bảo rằng lớp cung cấp.
Từ viết tắt RẮN cung cấp một số phương pháp phỏng đoán cho thiết kế hướng đối tượng tốt. Ở đây, Nguyên tắc phân chia I nterface (ISP) và L iskov thay thế Bảng giá (LSP) có một cái gì đó để nói.
ISP bảo chúng tôi giữ cho giao diện của chúng tôi nhỏ nhất có thể. Nhưng bằng cách kế thừa từ ArrayList
, bạn có được nhiều, nhiều phương pháp. Là nó có ý nghĩa để get()
, remove()
, set()
(thay thế), hoặc add()
(insert) một nút con tại một chỉ số cụ thể không? Có hợp lý với ensureCapacity()
danh sách cơ bản không? Nó có nghĩa gì với sort()
một nút? Là người dùng của lớp học của bạn thực sự cần phải có được một subList()
? Vì bạn không thể ẩn các phương thức mà bạn không muốn, giải pháp duy nhất là có ArrayList làm biến thành viên và chuyển tiếp tất cả các phương thức mà bạn thực sự muốn:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Nếu bạn thực sự muốn tất cả các phương pháp bạn thấy trong tài liệu, chúng tôi có thể chuyển sang LSP. LSP cho chúng ta biết rằng chúng ta phải có thể sử dụng lớp con bất cứ nơi nào mà lớp cha được mong đợi. Nếu một hàm lấy ArrayList
tham số làm và chúng ta vượt qua Node
thay vào đó, không có gì phải thay đổi.
Khả năng tương thích của các lớp con bắt đầu với những thứ đơn giản như chữ ký loại. Khi bạn ghi đè một phương thức, bạn không thể làm cho các kiểu tham số trở nên nghiêm ngặt hơn vì điều đó có thể loại trừ các sử dụng hợp pháp với lớp cha. Nhưng đó là thứ mà trình biên dịch kiểm tra cho chúng ta trong Java.
Nhưng LSP chạy sâu hơn nhiều: Chúng ta phải duy trì khả năng tương thích với mọi thứ được hứa hẹn bằng tài liệu của tất cả các lớp và giao diện cha. Trong câu trả lời của họ , Lynn đã tìm thấy một trường hợp như vậy trong đó List
giao diện (mà bạn được kế thừa thông qua ArrayList
) đảm bảo cách thức equals()
và hashCode()
các phương thức được cho là hoạt động. Đối với hashCode()
bạn thậm chí còn được đưa ra một thuật toán cụ thể phải được thực hiện chính xác. Giả sử bạn đã viết điều này Node
:
public class Node extends ArrayList<Node> {
public final int value;
public Node(int value, Node... children) {
this.value = Value;
for (Node child : children)
add(child);
}
...
}
Điều này đòi hỏi rằng value
không thể đóng góp vào hashCode()
và không thể ảnh hưởng equals()
. Các List
giao diện - mà bạn hứa danh dự bằng cách kế thừa từ nó - đòi hỏi new Node(0).equals(new Node(123))
đến mức khó tin.
Bởi vì kế thừa từ các lớp làm cho quá dễ dàng để vô tình phá vỡ một lời hứa mà lớp cha mẹ đã thực hiện và vì nó thường phơi bày nhiều phương thức hơn bạn dự định, nên thường đề nghị bạn thích sáng tác hơn kế thừa . Nếu bạn phải kế thừa một cái gì đó, nó được đề xuất chỉ kế thừa giao diện. Nếu bạn muốn sử dụng lại hành vi của một lớp cụ thể, bạn có thể giữ nó như một đối tượng riêng biệt trong một biến thể hiện, theo cách đó, tất cả các lời hứa và yêu cầu của nó không bắt buộc đối với bạn.
Đôi khi, ngôn ngữ tự nhiên của chúng tôi cho thấy mối quan hệ thừa kế: Xe hơi là phương tiện. Một chiếc xe máy là một phương tiện. Tôi có nên định nghĩa các lớp Car
và Motorcycle
kế thừa từ một Vehicle
lớp không? Thiết kế hướng đối tượng không phải là phản ánh chính xác thế giới thực trong mã của chúng tôi. Chúng ta không thể dễ dàng mã hóa các nguyên tắc phân loại phong phú của thế giới thực trong mã nguồn của mình.
Một ví dụ như vậy là vấn đề mô hình hóa nhân viên-sếp. Chúng tôi có nhiều Person
s, mỗi cái có một tên và địa chỉ. An Employee
là một Person
và có một Boss
. A Boss
cũng là a Person
. Vậy tôi có nên tạo một Person
lớp được kế thừa bởi Boss
và Employee
không? Bây giờ tôi có một vấn đề: ông chủ cũng là một nhân viên và có một cấp trên khác. Vì vậy, nó có vẻ như Boss
nên mở rộng Employee
. Nhưng đó CompanyOwner
là một Boss
nhưng không phải là một Employee
? Bất kỳ loại biểu đồ thừa kế bằng cách nào đó sẽ phá vỡ ở đây.
OOP không phải là về phân cấp, kế thừa và sử dụng lại các lớp hiện có, nó là về khái quát hóa hành vi . OOP nói về vấn đề của tôi. Tôi có rất nhiều đối tượng và muốn họ làm một việc cụ thể - và tôi không quan tâm làm thế nào. Đây là giao diện dành cho. Nếu bạn triển khai Iterable
giao diện cho bạn Node
vì bạn muốn làm cho nó có thể lặp lại, điều đó hoàn toàn tốt. Nếu bạn triển khai Collection
giao diện vì bạn muốn thêm / xóa các nút con, v.v. thì không sao. Nhưng kế thừa từ một lớp khác bởi vì nó tình cờ mang đến cho bạn tất cả những gì không, hoặc ít nhất là không trừ khi bạn đã suy nghĩ cẩn thận như đã nêu ở trên.