Giả sử bạn gặp trường hợp sau
#include <iostream>
class Animal {
public:
virtual void speak() = 0;
};
class Dog : public Animal {
void speak() { std::cout << "woff!" <<std::endl; }
};
class Cat : public Animal {
void speak() { std::cout << "meow!" <<std::endl; }
};
void makeSpeak(Animal &a) {
a.speak();
}
int main() {
Dog d;
Cat c;
makeSpeak(d);
makeSpeak(c);
}
Như bạn có thể thấy, makeSpeak là một quy trình chấp nhận một đối tượng Động vật chung. Trong trường hợp này, Animal khá giống với một giao diện Java, vì nó chỉ chứa một phương thức ảo thuần túy. makeSpeak không biết bản chất của Động vật mà nó được truyền qua. Nó chỉ gửi cho nó tín hiệu "speak" và để lại ràng buộc muộn để quan tâm đến phương thức nào sẽ gọi: Cat :: speak () hoặc Dog :: speak (). Điều này có nghĩa là, đối với makeSpeak, kiến thức về lớp con nào thực sự được truyền qua là không liên quan.
Nhưng còn Python thì sao? Hãy xem mã cho trường hợp tương tự trong Python. Xin lưu ý rằng tôi cố gắng giống với trường hợp C ++ nhất có thể trong giây lát:
class Animal(object):
def speak(self):
raise NotImplementedError()
class Dog(Animal):
def speak(self):
print "woff!"
class Cat(Animal):
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
Bây giờ, trong ví dụ này, bạn thấy chiến lược tương tự. Bạn sử dụng tính năng thừa kế để tận dụng khái niệm phân cấp của cả Chó và Mèo là Động vật. Nhưng trong Python, không cần cấu trúc phân cấp này. Điều này hoạt động tốt như nhau
class Dog:
def speak(self):
print "woff!"
class Cat:
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
Trong Python, bạn có thể gửi tín hiệu “nói” đến bất kỳ đối tượng nào bạn muốn. Nếu đối tượng có thể xử lý nó, nó sẽ được thực thi, nếu không nó sẽ tạo ra một ngoại lệ. Giả sử bạn thêm một lớp Máy bay vào cả hai mã và gửi một đối tượng Máy bay cho makeSpeak. Trong trường hợp C ++, nó sẽ không biên dịch, vì Máy bay không phải là một lớp dẫn xuất của Động vật. Trong trường hợp Python, nó sẽ đưa ra một ngoại lệ trong thời gian chạy, thậm chí có thể là một hành vi được mong đợi.
Mặt khác, giả sử bạn thêm một lớp MouthOfTruth với phương thức speak (). Trong trường hợp C ++, bạn sẽ phải cấu trúc lại hệ thống phân cấp của mình hoặc bạn sẽ phải xác định một phương thức makeSpeak khác để chấp nhận các đối tượng MouthOfTruth hoặc trong java, bạn có thể trích xuất hành vi vào một CanSpeakIface và triển khai giao diện cho từng đối tượng. Có nhiều giải pháp ...
Điều tôi muốn chỉ ra là tôi vẫn chưa tìm thấy một lý do nào để sử dụng kế thừa trong Python (ngoài các khung và cây ngoại lệ, nhưng tôi đoán rằng các chiến lược thay thế tồn tại). bạn không cần phải triển khai cấu trúc phân cấp có nguồn gốc từ cơ sở để thực hiện đa hình. Nếu bạn muốn sử dụng kế thừa để sử dụng lại việc triển khai, bạn có thể thực hiện điều tương tự thông qua ngăn chặn và ủy quyền, với lợi ích bổ sung là bạn có thể thay đổi nó trong thời gian chạy và bạn xác định rõ ràng giao diện của nội dung được chứa mà không gặp phải các tác dụng phụ ngoài ý muốn.
Vì vậy, cuối cùng, câu hỏi đặt ra: điểm kế thừa trong Python là gì?
Chỉnh sửa : cảm ơn vì những câu trả lời rất thú vị. Thật vậy, bạn có thể sử dụng nó để tái sử dụng mã, nhưng tôi luôn cẩn thận khi sử dụng lại việc triển khai. Nói chung, tôi có xu hướng làm các cây kế thừa rất nông hoặc không có cây nào cả, và nếu một chức năng phổ biến, tôi sẽ cấu trúc lại nó như một quy trình mô-đun chung và sau đó gọi nó từ từng đối tượng. Tôi thấy lợi thế của việc có một điểm thay đổi duy nhất (ví dụ: thay vì thêm vào Chó, Mèo, Moose, v.v., tôi chỉ thêm vào Động vật, đây là lợi thế cơ bản của việc kế thừa), nhưng bạn có thể đạt được điều tương tự với một chuỗi ủy quyền (ví dụ: a la JavaScript). Tôi không khẳng định nó tốt hơn, chỉ là một cách khác.
Tôi cũng tìm thấy một bài tương tự về vấn đề này.