Kế thừa nguyên mẫu thực tế khác với kế thừa cổ điển như thế nào?


27

Kế thừa, Đa hình và Đóng gói là ba tính năng quan trọng nhất, khác biệt của OOP và từ đó, tính kế thừa có số liệu thống kê sử dụng cao trong những ngày này. Tôi đang học JavaScript và ở đây, tất cả họ đều nói rằng nó có tính kế thừa nguyên mẫu và mọi người ở khắp mọi nơi nói rằng đó là một cái gì đó khác xa so với thừa kế cổ điển.

Tuy nhiên, tôi không thể hiểu sự khác biệt của chúng từ điểm sử dụng thực tế là gì? Nói cách khác, khi bạn định nghĩa một lớp cơ sở (nguyên mẫu) và sau đó rút ra một số lớp con từ nó, cả hai bạn đều có quyền truy cập vào các chức năng của lớp cơ sở của mình và bạn có thể tăng các hàm trên các lớp dẫn xuất. Nếu chúng ta coi những gì tôi nói là kết quả dự định của thừa kế, thì tại sao chúng ta phải quan tâm nếu chúng ta sử dụng phiên bản nguyên mẫu hoặc phiên bản cổ điển?

Để làm rõ hơn bản thân mình, tôi thấy không có sự khác biệt trong tính hữu dụng và mô hình sử dụng của nguyên mẫu và kế thừa cổ điển. Điều này dẫn đến việc tôi không có hứng thú để tìm hiểu lý do tại sao chúng khác nhau, vì cả hai đều dẫn đến cùng một thứ, OOAD. Thực tế (không phải về mặt lý thuyết) kế thừa nguyên mẫu khác với kế thừa cổ điển như thế nào?

Câu trả lời:


6

Bài đăng trên blog gần đây về JS OO

Tôi tin rằng những gì so sánh của bạn là mô phỏng OO cổ điển trong JavaScriptOO cổ điển và tất nhiên bạn không thể thấy bất kỳ sự khác biệt nào.

Tuyên bố từ chối trách nhiệm: thay thế tất cả các tham chiếu đến "nguyên mẫu OO" bằng "nguyên mẫu OO trong JavaScript". Tôi không biết chi tiết cụ thể về Bản thân hoặc bất kỳ triển khai nào khác.

Tuy nhiên OO nguyên mẫu là khác nhau. Với các nguyên mẫu, bạn chỉ có các đối tượng và bạn chỉ có thể đưa các đối tượng vào chuỗi nguyên mẫu của các đối tượng khác. Bất cứ khi nào bạn truy cập một thuộc tính trên một đối tượng, bạn tìm kiếm đối tượng đó và bất kỳ đối tượng nào trong chuỗi nguyên mẫu.

Đối với OO nguyên mẫu không có khái niệm đóng gói. Đóng gói là một tính năng của phạm vi, đóng và các hàm hạng nhất nhưng không liên quan gì đến OO nguyên mẫu. Cũng không có khái niệm về thừa kế, cái mà mọi người gọi là "thừa kế" thực ra chỉ là đa hình.

Tuy nhiên, nó có đa hình.

Ví dụ

var Dog = {
  walk: function() { console.log("walks"); }
}

var d = Object.create(Dog);
d.walk();

Rõ ràng dcó quyền truy cập vào phương pháp Dog.walkvà điều này thể hiện tính đa hình.

Vì vậy, thực sự có một sự khác biệt lớn . Bạn chỉ có đa hình.

Tuy nhiên, như đã đề cập, nếu bạn muốn làm như vậy (tôi không biết tại sao bạn lại như vậy), bạn có thể mô phỏng OO cổ điển trong JavaScript và có quyền truy cập vào đóng gói và kế thừa (bị hạn chế).


1
@Rayons, Google cho thừa kế nguyên mẫu và bạn sẽ thấy rằng thừa kế nguyên mẫu xuất hiện. Ngay cả từ Yahoo!
Saeed Neamati

@Saeed thừa kế là một thuật ngữ mơ hồ và thường được sử dụng sai. Ý nghĩa của chúng với "thừa kế" Tôi gắn nhãn "đa hình". Các định nghĩa quá mơ hồ.
Raynos

Không có @Rayons, ý tôi là từ nguyên mẫu là thuật ngữ chính xác, không phải nguyên mẫu . Tôi đã không nói về thừa kế :)
Saeed Neamati

1
@Saeed đó là một trong những lỗi chính tả mà tâm trí tôi trống rỗng. Tôi không chú ý đến điều đó
Raynos

1
Hmm .. thuật ngữ. ick. Tôi sẽ gọi cách các đối tượng javascript có liên quan đến nguyên mẫu "ủy quyền" của chúng. Đa hình đối với tôi dường như là một mối quan tâm cấp thấp hơn, dựa trên thực tế là một tên cụ thể có thể trỏ đến các mã khác nhau cho các đối tượng khác nhau.
Sean McMillan

15

Kế thừa cổ điển kế thừa hành vi, không có bất kỳ trạng thái nào, từ lớp cha. Nó kế thừa hành vi tại thời điểm đối tượng được khởi tạo.

Kế thừa nguyên mẫu kế thừa hành vi và trạng thái từ đối tượng cha. Nó kế thừa hành vi và trạng thái tại thời điểm đối tượng được gọi. Khi đối tượng cha mẹ thay đổi vào thời gian chạy, trạng thái và hành vi của các đối tượng con bị ảnh hưởng.

"Ưu điểm" của thừa kế nguyên mẫu là bạn có thể "vá" trạng thái và hành vi sau khi tất cả các đối tượng của bạn được khởi tạo. Ví dụ, trong khung công tác Ext JS, thường tải "ghi đè" để vá các thành phần cốt lõi của khung sau khi khung được khởi tạo.


4
Trong thực tế, nếu bạn đang thừa hưởng trạng thái, bạn đang thiết lập cho mình một thế giới bị tổn thương. Trạng thái được kế thừa sẽ trở thành chia sẻ nếu nó sống trên một đối tượng khác.
Sean McMillan

1
Nó xảy ra với tôi rằng trong javascript thực sự không có khái niệm hành vi tách biệt với trạng thái. Ngoài chức năng của hàm tạo, tất cả các hành vi có thể được gán / sửa đổi hậu tạo đối tượng giống như bất kỳ trạng thái nào khác.
Joeri Sebrechts

Vì vậy, Python không có sự kế thừa cổ điển? Nếu tôi có class C(object): def m(self, x): return x*2và sau instance = C()đó khi tôi chạy instance.m(3)tôi nhận được 6. Nhưng nếu tôi sau đó thay đổi Cnhư thế C.m = lambda s, x: x*xvà tôi chạy instance.m(3)bây giờ tôi có được 9. Điều tương tự cũng xảy ra nếu tôi thực hiện class D(C)và thay đổi một phương thức Csau đó bất kỳ trường hợp nào cũng Dnhận được phương thức đã thay đổi. Tôi có hiểu lầm không hay điều này có nghĩa là Python không có sự kế thừa Cổ điển theo định nghĩa của bạn?
mVChr

@mVChr: ​​Bạn vẫn đang kế thừa hành vi và không nêu trong trường hợp đó, điều này chỉ ra sự kế thừa cổ điển, nhưng nó chỉ ra một vấn đề với định nghĩa của tôi về "kế thừa hành vi ngay khi đối tượng được khởi tạo". Tôi không chắc chắn làm thế nào để sửa định nghĩa.
Joeri Sebrechts

9

Thứ nhất: Hầu hết thời gian, bạn sẽ sử dụng các đối tượng, không xác định chúng và sử dụng các đối tượng là giống nhau theo cả hai mô hình.

Thứ hai: Hầu hết các môi trường nguyên mẫu sử dụng cùng một kiểu phân chia như các môi trường dựa trên lớp - dữ liệu có thể thay đổi trên cá thể, với các phương thức được kế thừa. Vì vậy, có rất ít sự khác biệt một lần nữa. (Xem câu trả lời của tôi cho câu hỏi tràn ngăn xếp nàyChương trình tự tổ chức không có lớp học . Hãy xem Citeseer cho phiên bản PDF .)

Thứ ba: Bản chất năng động của Javascript có ảnh hưởng lớn hơn nhiều so với loại kế thừa. Thực tế là tôi có thể thêm một phương thức mới cho tất cả các thể hiện của một loại bằng cách gán nó cho đối tượng cơ sở là gọn gàng, nhưng tôi có thể làm điều tương tự trong Ruby, bằng cách mở lại lớp.

Thứ tư: Sự khác biệt thực tế là nhỏ, trong khi vấn đề thực tế là quên sử dụng newthì lớn hơn nhiều - nghĩa là, bạn có nhiều khả năng bị ảnh hưởng do thiếu a newhơn là bạn bị ảnh hưởng bởi sự khác biệt giữa mã nguyên mẫu và mã cổ điển .

Tất cả những gì đã nói, sự khác biệt thực tế giữa kế thừa nguyên mẫu và kế thừa cổ điển là các phương thức (các lớp) giữ của bạn giống như các dữ liệu của bạn (các ví dụ). Điều này có nghĩa là bạn có thể xây dựng các lớp của mình từng phần, sử dụng tất cả các công cụ thao tác đối tượng giống nhau mà bạn sẽ sử dụng trong mọi trường hợp. (Trên thực tế, đây là cách tất cả các thư viện mô phỏng lớp thực hiện nó. Đối với cách tiếp cận không hoàn toàn giống như tất cả những người khác, hãy xem Traits.js ). Điều này chủ yếu là thú vị nếu bạn đang lập trình siêu dữ liệu.


yay, trả lời tốt nhất IMO!
Aivar

0

Kế thừa nguyên mẫu trong JavaScript khác với các lớp theo những cách quan trọng sau:

Trình xây dựng là các hàm đơn giản mà bạn có thể gọi mà không cần new:

function Circle (r, x, y) { 
  //stuff here
}
Var c = new Circle();
Circle.call(c, x, y, z); //This works and you can do it over and over again.

Không có biến hoặc phương thức riêng tư, tốt nhất bạn có thể làm điều này:

function Circle (r, x, y) {
  var color = 'red';
  function drawCircle () {
    //some code here with x, y and r
  }
  drawCircle();
  this.setX = function (x_) {
    x = x_;
    drawCircle();
  }

}
Circle.prototype.getX = function () {
   //Can't access x!
}

Trong ví dụ trước, bạn không thể mở rộng lớp một cách có ý nghĩa nếu bạn sử dụng các phương thức và biến riêng tư giả và ngoài ra, bất kỳ phương thức công khai nào bạn đã khai báo sẽ được tạo lại mỗi khi tạo một thể hiện mới.


Bạn có thể mở rộng "lớp" một cách có ý nghĩa. Bạn chỉ không thể truy cập các biến địa phương color, r, x, ydrawCircleđược liên kết với phạm vi từ vựng củaCircle
Raynos

Điều đó đúng, bạn có thể, nhưng bạn không thể làm điều đó mà không tạo ra một loạt các phương thức 'công khai' được thêm vào bối cảnh được tạo ra mỗi khi bạn tạo một thể hiện.
Bjorn

Đây là một mẫu rất lạ mà bạn đang sử dụng ở đó. Circle.call()làm nhà xây dựng? Có vẻ như bạn đang cố gắng mô tả "các đối tượng chức năng" (một cách gọi sai ...)
Sean McMillan

Đó không phải là điều tôi từng làm, tôi chỉ nói rằng có thể gọi hàm tạo nhiều lần bằng JavaScript nhưng không phải bằng ngôn ngữ với các lớp truyền thống.
Bjorn

1
Nhưng làm thế nào là những khác biệt "quan trọng"? Tôi không thấy việc xây dựng các đối tượng bằng cách gọi một hàm, không sử dụng 'mới', là 'quan trọng', chỉ là một sự khác biệt nhỏ về cú pháp. Tương tự như vậy, không có các biến riêng tư về cơ bản không khác nhau, chỉ là một sự khác biệt nhỏ. Thêm vào đó, bạn có thể có (một cái gì đó tương tự) các biến riêng tư, theo cách bạn mô tả.
Roel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.