Đa hình trong Javascript là gì?


90

Tôi đã đọc một số bài báo khả thi mà tôi có thể tìm thấy trên internet về tính đa hình . Nhưng tôi nghĩ rằng tôi không thể hiểu được ý nghĩa của nó và tầm quan trọng của nó. Hầu hết các bài báo không nói lý do tại sao nó lại quan trọng và làm thế nào tôi có thể đạt được hành vi đa hình trong OOP (tất nhiên là trong JavaScript).

Tôi không thể cung cấp bất kỳ ví dụ mã nào vì tôi không có ý tưởng về cách triển khai nó, vì vậy câu hỏi của tôi như sau:

  1. Nó là gì?
  2. Tại sao chúng ta cần nó?
  3. Làm thế nào nó hoạt động?
  4. Làm cách nào để đạt được hành vi đa hình này trong javascript?

Tôi đã có ví dụ này. Nhưng có thể dễ dàng hiểu được kết quả của đoạn mã này. Nó không đưa ra bất kỳ ý tưởng rõ ràng nào về tính đa hình.

function Person(age, weight) {
    this.age = age;
    this.weight = weight;
    this.getInfo = function() {
        return "I am " + this.age + " years old " +
        "and weighs " + this.weight +" kilo.";
    }
}
function Employee(age, weight, salary) {
    this.salary = salary;
    this.age = age;
    this.weight = weight;
    this.getInfo = function() {
        return "I am " + this.age + " years old " +
        "and weighs " + this.weight +" kilo " +
        "and earns " + this.salary + " dollar.";
    }
}

Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
  // The argument, 'obj', can be of any kind
  // which method, getInfo(), to be executed depend on the object
  // that 'obj' refer to.

function showInfo(obj) {
    document.write(obj.getInfo() + "<br>");
}

var person = new Person(50,90);
var employee = new Employee(43,80,50000);
showInfo(person);
showInfo(employee);

Câu hỏi này có lẽ quá rộng để hoạt động tốt với StackOverflow. Điều tốt nhất chúng tôi có thể làm là liên kết bạn với một số giải thích đa hình khác. StackOverflow tốt nhất trong việc trả lời các câu hỏi cụ thể hoặc giải thích rõ ràng về một vấn đề cụ thể, chẳng hạn như "Nguồn cho biết tính đa hình là XYZ, nhưng Y có nghĩa là gì?"
Vitruvius

2
bạn không cần nó. ở tất cả. bạn thậm chí không cần các lớp trong JS và trên thực tế, có nhiều mô hình khác, được cho là tốt hơn, để xây dựng ứng dụng. apply / call / bind loại bỏ nhu cầu về tính đồng nhất và với soft-object, bạn có thể sửa đổi bất cứ thứ gì cho phù hợp với nhu cầu của mình mà không cần trang trí trước hoặc kế thừa các trường hợp đặc biệt.
dandavis

1
Tính đa hình không chỉ liên quan đến OO, và nó có nhiều ý nghĩa. Bạn có thể muốn đọc câu trả lời khác này dưới các câu hỏi Có thể có đa hình mà không cần thừa kế .
Edwin Dalorzo

Kế thừa thường được thực hiện không chính xác trong JavaScript. Để tạo một thể hiện của Parent được sử dụng làm nguyên mẫu của Child cho thấy sự thiếu hiểu biết về vai trò của hàm khởi tạo và nguyên mẫu trong việc xác định và tạo một đối tượng. Có thêm thông tin trong câu trả lời này: stackoverflow.com/a/16063711/1641941
HMR

Câu trả lời:


98

Đa hình là một trong những nguyên lý của Lập trình hướng đối tượng (OOP). Đó là thực hành thiết kế các đối tượng để chia sẻ hành vi và có thể ghi đè các hành vi được chia sẻ với những hành vi cụ thể. Đa hình tận dụng lợi thế của sự kế thừa để làm cho điều này xảy ra.

Trong OOP, mọi thứ được coi là được mô hình hóa như một đối tượng. Sự trừu tượng này có thể được đưa xuống tận các đai ốc và bu lông cho một chiếc ô tô, hoặc đơn giản là một loại ô tô có năm, sản xuất và kiểu xe.

Để có một kịch bản ô tô đa hình, sẽ có loại ô tô cơ sở và sau đó sẽ có các lớp con kế thừa từ ô tô và cung cấp các hành vi của riêng chúng trên các hành vi cơ bản mà một chiếc ô tô sẽ có. Ví dụ: một lớp con có thể là TowTruck vẫn sẽ có mẫu và chế tạo năm, nhưng cũng có thể có một số hành vi và thuộc tính bổ sung có thể cơ bản như một lá cờ cho IsTowing phức tạp như các chi tiết cụ thể của thang máy.

Lấy lại ví dụ về con người và nhân viên, tất cả nhân viên là người, nhưng tất cả mọi người không phải là nhân viên. Có nghĩa là mọi người sẽ là tầng lớp siêu cấp, và nhân viên là tầng lớp phụ. Mọi người có thể có tuổi và cân nặng, nhưng họ không có lương. Nhân viên là con người nên vốn dĩ họ sẽ có tuổi và cân, nhưng cũng vì đã là nhân viên nên họ sẽ có lương.

Vì vậy, để tạo điều kiện thuận lợi cho việc này, trước tiên chúng ta sẽ viết ra siêu lớp (Person)

function Person(age,weight){
 this.age = age;
 this.weight = weight;
}

Và chúng tôi sẽ cung cấp cho Người khả năng chia sẻ thông tin của họ

Person.prototype.getInfo = function(){
 return "I am " + this.age + " years old " +
    "and weighs " + this.weight +" kilo.";
};

Tiếp theo, chúng tôi muốn có một lớp con là Person, Employee

function Employee(age,weight,salary){
 this.age = age;
 this.weight = weight;
 this.salary = salary;
}
Employee.prototype = new Person();

Và chúng tôi sẽ ghi đè hành vi của getInfo bằng cách xác định một hành vi phù hợp hơn với Nhân viên

Employee.prototype.getInfo = function(){
 return "I am " + this.age + " years old " +
    "and weighs " + this.weight +" kilo " +
    "and earns " + this.salary + " dollar.";  
};

Chúng có thể được sử dụng tương tự như việc sử dụng mã ban đầu của bạn

var person = new Person(50,90);
var employee = new Employee(43,80,50000);

console.log(person.getInfo());
console.log(employee.getInfo());

Tuy nhiên, không có nhiều lợi ích khi sử dụng tính năng thừa kế ở đây vì hàm tạo của Employee rất giống với của người đó và hàm duy nhất trong nguyên mẫu đang bị ghi đè. Sức mạnh trong thiết kế đa hình là chia sẻ các hành vi.


2
@ user3138436 - Đúng vậy. Chuỗi nguyên mẫu sẽ được kiểm tra lần xuất hiện đầu tiên getInfosẽ là của Nhân viên vì nó cao hơn trong chuỗi so với của Người. Đó là ý của tôi khi tôi nói "override".
Travis J

16
Bạn không đang sử dụng hàm tạo (Person.call (this, arg)) và thiết lập nguyên mẫu của Employee thành một thể hiện của Person. Nguyên mẫu là nơi các thành viên được chia sẻ đi đến và hàm khởi tạo là nơi các thành viên cụ thể được tạo ra. Các mẫu của bạn sử dụng mã copy paste lại sử dụng hàm khởi tạo và kế thừa sai phần nguyên mẫu (Người có ví dụ các thành viên cụ thể không có công việc kinh doanh trên Employee.prototype, đặc biệt nếu bạn có các thành viên có thể thay đổi). Thông tin thêm về kế thừa bằng cách sử dụng các hàm khởi tạo và nguyên mẫu tại đây: stackoverflow.com/a/16063711/1641941
HMR

3
Đối với nhân viên để sử dụng lại và mở rộng getinfo của người, nó có thể đơn giản làm return Person.prototype.getInfo.call(this) + + "and earns " + this.salary + " dollar.";thay vì sao chép mã dán lại sử dụng.
HMR

3
đây là nơi áp dụng đa hình?
albert Jegani

2
@rpeg có thể thấy rõ hơn điểm đa hình trong ví dụ OPs. hàm showInfo (); chấp nhận một đối tượng chung. tính đa hình bây giờ là khả năng phản ứng khác nhau tùy thuộc vào loại đối tượng. tôi nghĩ câu trả lời này không làm cho điều này đủ rõ ràng, vì nó gọi getInfo () trên mọi đối tượng cụ thể.
Stefan

26

Như đã giải thích trong câu trả lời khác , tính đa hình có những cách hiểu khác nhau.

Lời giải thích tốt nhất về chủ đề này mà tôi từng đọc là một bài báo của Luca Cardelli , một nhà lý thuyết loại nổi tiếng. Bài báo này có tên là Tìm hiểu các loại, trừu tượng hóa dữ liệu và đa hình .

Nó là gì?

Cardelli định nghĩa một số kiểu đa hình trong bài viết này:

  • phổ cập
    • tham số
    • bao gồm
  • Đặc biệt
    • oveloading
    • sự ép buộc

Có lẽ trong JavaScript, hơi khó để nhìn thấy ảnh hưởng của tính đa hình vì các kiểu đa hình cổ điển hơn thể hiện rõ ràng hơn trong hệ thống kiểu tĩnh, trong khi JavaScript có hệ thống kiểu động.

Vì vậy, chẳng hạn, không có phương thức hoặc hàm quá tải hoặc các cưỡng chế kiểu tự động tại thời điểm biên dịch trong JavaScript. Trong một ngôn ngữ động, chúng tôi coi hầu hết những điều này là đương nhiên. Chúng ta cũng không cần một thứ gì đó giống như đa hình tham số trong JavaScript do tính chất động của ngôn ngữ.

Tuy nhiên, JavaScript có một dạng kế thừa kiểu mô phỏng cùng ý tưởng về đa hình kiểu con (được Cardelli phân loại là đa hình bao hàm ở trên) theo cách tương tự như những gì chúng ta thường làm trong các ngôn ngữ lập trình hướng đối tượng khác như Java hoặc C # (như đã giải thích trong một câu trả lời khác mà tôi đã chia sẻ ở trên).

Một dạng đa hình khác rất điển hình trong các ngôn ngữ động được gọi là kiểu gõ vịt .

Thật sai lầm khi tin rằng tính đa hình chỉ liên quan đến lập trình hướng đối tượng. Các mô hình lập trình khác (chức năng, thủ tục, logic, v.v.) cung cấp các dạng đa hình khác nhau trong các hệ thống kiểu của chúng, có thể theo một cách hơi xa lạ với những người chỉ sử dụng OOP.

Tại sao chúng ta cần nó?

Tính đa hình thúc đẩy nhiều thuộc tính tốt trong phần mềm, trong số những thứ khác, nó thúc đẩy tính mô-đun và khả năng tái sử dụng và làm cho hệ thống kiểu linh hoạt và dễ uốn hơn. Nếu không có nó, sẽ rất khó để suy luận về các loại. Tính đa hình đảm bảo rằng một kiểu có thể được thay thế bằng những kiểu tương thích khác với điều kiện là chúng đáp ứng giao diện công khai, do đó, điều này cũng thúc đẩy việc ẩn thông tin và tính mô đun.

Làm thế nào nó hoạt động?

Điều này không đơn giản để trả lời, các ngôn ngữ khác nhau có những cách khác nhau để thực hiện nó. Trong trường hợp JavaScript, như đã đề cập ở trên, bạn sẽ thấy nó hiện thực hóa dưới dạng phân cấp kiểu sử dụng kế thừa nguyên mẫu và bạn cũng có thể khai thác nó bằng cách gõ vịt.

Chủ đề này hơi rộng và bạn đã mở hai câu hỏi trong một bài đăng. Có lẽ tốt nhất là bạn nên bắt đầu bằng cách đọc bài báo của Cardelli và sau đó cố gắng hiểu tính đa hình bất kể ngôn ngữ hoặc mô hình lập trình nào, sau đó bạn sẽ bắt đầu tạo liên tưởng giữa các khái niệm lý thuyết và những gì mà bất kỳ ngôn ngữ cụ thể nào như JavaScript phải cung cấp để thực hiện những ý tưởng đó.


14

Mục đích của đa hình là gì?

Tính đa hình làm cho hệ thống kiểu tĩnh linh hoạt hơn mà không làm mất (đáng kể) sự an toàn kiểu tĩnh bằng cách nới lỏng các điều kiện cho sự tương đương kiểu. Bằng chứng vẫn là một chương trình sẽ chỉ chạy nếu nó không chứa bất kỳ lỗi loại nào.

Kiểu dữ liệu hoặc hàm đa hình thường tổng quát hơn kiểu dữ liệu đơn hình vì nó có thể được sử dụng trong nhiều tình huống hơn. Theo nghĩa này, tính đa hình thể hiện ý tưởng tổng quát hóa trong các ngôn ngữ được đánh máy nghiêm ngặt.

Điều này áp dụng cho Javascript như thế nào?

Javascript có một hệ thống kiểu động yếu. Một hệ thống loại như vậy tương đương với một hệ thống loại nghiêm ngặt chỉ chứa một loại. Chúng ta có thể coi kiểu như vậy là kiểu liên hợp lớn (cú pháp giả):

type T =
 | Undefined
 | Null
 | Number
 | String
 | Boolean
 | Symbol
 | Object
 | Array
 | Map
 | ...

Mọi giá trị sẽ được liên kết với một trong những lựa chọn thay thế loại này tại thời điểm chạy. Và vì Javascript được gõ yếu, mọi giá trị có thể thay đổi kiểu của nó bất kỳ số lần nào.

Nếu chúng ta xem xét quan điểm lý thuyết về kiểu và xem xét rằng chỉ có một kiểu, chúng ta có thể nói chắc chắn rằng hệ thống kiểu của Javascript không có khái niệm về tính đa hình. Thay vào đó chúng ta có kiểu gõ vịt và kiểu ép buộc ngầm.

Nhưng điều này không khiến chúng ta phải suy nghĩ về các loại trong chương trình của mình. Do thiếu các kiểu trong Javascript, chúng ta cần suy ra chúng trong quá trình viết mã. Tâm trí của chúng ta phải chờ đợi trình biên dịch bị thiếu, tức là ngay khi chúng ta nhìn vào một chương trình, chúng ta phải nhận ra không chỉ các thuật toán, mà còn cả các kiểu cơ bản (có thể là đa hình). Những loại này sẽ giúp chúng tôi xây dựng các chương trình đáng tin cậy hơn và mạnh mẽ hơn.

Để làm điều này đúng cách, tôi sẽ cung cấp cho bạn một cái nhìn tổng quan về các biểu hiện phổ biến nhất của đa hình.

Đa hình tham số (hay còn gọi là generic)

Tính đa hình tham số nói rằng các kiểu khác nhau có thể hoán đổi cho nhau vì các kiểu không quan trọng chút nào. Một hàm xác định một hoặc nhiều tham số của kiểu đa hình tham số không được biết gì về các đối số tương ứng nhưng đối xử với chúng như nhau, vì chúng có thể áp dụng cho bất kỳ kiểu nào. Điều này khá hạn chế, bởi vì một hàm như vậy chỉ có thể hoạt động với những thuộc tính của các đối số của nó mà không phải là một phần dữ liệu của chúng:

// parametric polymorphic functions

const id = x => x;

id(1); // 1
id("foo"); // "foo"

const k = x => y => x;
const k_ = x => y => y;

k(1) ("foo"); // 1
k_(1) ("foo"); // "foo"

const append = x => xs => xs.concat([x]);

append(3) ([1, 2]); // [1, 2, 3]
append("c") (["a", "b"]); // ["a", "b", "c"]

Đa hình Ad-hoc (còn gọi là quá tải)

Tính đa hình Ad-hoc nói rằng các kiểu khác nhau chỉ tương đương cho một mục đích cụ thể. Để tương đương theo nghĩa này, một kiểu phải triển khai một tập hợp các chức năng cụ thể cho mục đích đó. Khi đó, một hàm xác định một hoặc nhiều tham số của kiểu đa hình ad-hoc cần phải biết bộ hàm nào được liên kết với mỗi đối số của nó.

Tính đa hình đặc biệt làm cho một hàm tương thích với một miền lớn hơn của các kiểu. Ví dụ sau minh họa mục đích "map-over" và cách các kiểu có thể triển khai ràng buộc này. Thay vì một tập hợp hàm, ràng buộc "có thể lập bản đồ" chỉ bao gồm một maphàm duy nhất :

// Option type
class Option {
  cata(pattern, option) {
    return pattern[option.constructor.name](option.x);
  }
  
  map(f, opt) {
    return this.cata({Some: x => new Some(f(x)), None: () => this}, opt);
  }
};

class Some extends Option {
  constructor(x) {
    super(x);
    this.x = x;
  }
};

class None extends Option {
  constructor() {
    super();
  }
};


// ad-hoc polymorphic function
const map = f => t => t.map(f, t);

// helper/data

const sqr = x => x * x;

const xs = [1, 2, 3];
const x = new Some(5);
const y = new None();

// application

console.log(
  map(sqr) (xs) // [1, 4, 9]
);

console.log(
  map(sqr) (x) // Some {x: 25}
);

console.log(
  map(sqr) (y) // None {}
);

Đa hình kiểu phụ

Vì các câu trả lời khác đã bao gồm đa hình kiểu phụ nên tôi bỏ qua.

Tính đa hình cấu trúc (hay còn gọi là kiểu phụ nghiêm ngặt)

Đa hình cấu trúc nói rằng các kiểu khác nhau là tương đương, nếu chúng chứa cùng một cấu trúc theo cách đó, kiểu này có tất cả các thuộc tính của kiểu kia nhưng có thể bao gồm các thuộc tính bổ sung. Điều đó đang được nói, đa hình cấu trúc là gõ vịt tại thời điểm biên dịch và chắc chắn cung cấp một số an toàn kiểu bổ sung. Nhưng bằng cách tuyên bố rằng hai giá trị thuộc cùng một kiểu chỉ vì chúng chia sẻ một số thuộc tính, nó hoàn toàn bỏ qua cấp độ ngữ nghĩa của các giá trị:

const weight = {value: 90, foo: true};
const speed =  {value: 90, foo: false, bar: [1, 2, 3]};

Thật không may, speednó được coi là một loại phụ weightvà ngay sau khi chúng tôi so sánh các valueđặc tính, chúng tôi gần như so sánh táo với cam.


1
Mặc dù ở nhiều khía cạnh , câu trả lời này chính xác hơn (và chắc chắn kỹ lưỡng hơn) so với câu trả lời được chấp nhận, nhưng nó không có cùng khả năng tiếp cận: câu trả lời này giả định rằng người hỏi đã quá thông minh để bận tâm đến câu hỏi :)
Jared Smith

@JaredSmith Tôi đã cố gắng tóm tắt chủ đề thành một số đoạn văn dễ hiểu. Nhưng tôi càng khoan sâu thì nó càng phức tạp hơn. Tôi chưa bao giờ tìm thấy một nguồn tốt cho tính đa hình trong các ngôn ngữ không định kiểu, vì vậy tôi nghĩ câu trả lời này vẫn có giá trị.

bản chỉnh sửa cải thiện đáng kể khả năng tiếp cận. Về tính hữu ích của tính đa hình trong các ngôn ngữ động, có rất nhiều nhưng tôi đang đấu tranh để nghĩ ra một ví dụ tốt trong JS. Một ví dụ tốt hơn sẽ là các phương thức ma thuật của Python cho phép các kiểu do người dùng xác định hoạt động với các hàm đa hình như len. Hoặc có lẽ conjtừ áo choàng.
Jared Smith

8

nó là gì?

Poly = nhiều, biến hình = sự thay đổi hình thức hoặc hành vi.

tại sao chúng ta cần nó?

Trong lập trình, nó được sử dụng khi chúng ta muốn giao diện của một hàm (ví dụ như hàm X) đủ linh hoạt để chấp nhận các kiểu hoặc số lượng tham số khác nhau. Ngoài ra, dựa trên việc thay đổi các loại tham số hoặc số, chúng ta có thể muốn hàm X hoạt động khác (biến hình).

Làm thế nào nó hoạt động?

Chúng tôi viết nhiều triển khai của hàm X trong đó mỗi triển khai chấp nhận các kiểu tham số hoặc số lượng tham số khác nhau. Dựa trên kiểu hoặc số lượng tham số, trình biên dịch (trong thời gian chạy) quyết định việc triển khai nào của X sẽ được thực thi khi X được gọi từ một số mã.

làm cách nào để đạt được hành vi đa hình này trong javascript?

JS không phải là một ngôn ngữ định kiểu nên nó thực sự không có nghĩa là sử dụng các khái niệm OOP như đa hình. Tuy nhiên, phiên bản JS mới hơn hiện bao gồm các lớp và có khả năng tính đa nghĩa cũng có thể bắt đầu có ý nghĩa trong JS. Các câu trả lời khác cung cấp một số cách giải quyết thú vị.


3

Đa hình nghĩa là Khả năng gọi cùng một phương thức trên các đối tượng khác nhau và mỗi đối tượng phản hồi theo cách khác nhau được gọi là POLYMORPHISM .

    function Animal(sound){
    this.sound=sound;
    this.speak=function(){
    			return this.sound;
    	}
    }
//one method 
    function showInfo(obj){
    		console.log(obj.speak());
    }
//different objects
    var dog = new Animal("woof");
    var cat = new Animal("meow");
    var cow = new Animal("humbow");
//responds different ways
    showInfo(dog);
    showInfo(cat);
    showInfo(cow);


2

JavaScript là một ngôn ngữ thông dịch, không phải là một ngôn ngữ biên dịch.

Tính đa hình thời gian biên dịch (hay Đa hình tĩnh) Tính đa hình thời gian biên dịch không là gì khác ngoài việc nạp chồng phương thức trong java, c ++

Vì vậy, không thể nạp chồng phương thức trong javascript.

Nhưng tính đa hình Động (thời gian chạy) là tính đa hình tồn tại trong thời gian chạy nên có thể ghi đè phương thức trong javascript

một ví dụ khác là PHP.

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.