Co to jest polimorfizm w JavaScript?

91

Przeczytałem kilka artykułów na temat polimorfizmu, które mogłem znaleźć w Internecie . Ale myślę, że nie mogłem do końca pojąć jego znaczenia i wagi. Większość artykułów nie mówi, dlaczego jest to ważne i jak mogę osiągnąć polimorficzne zachowanie w OOP (oczywiście w JavaScript).

Nie mogę podać żadnego przykładu kodu, ponieważ nie mam pomysłu, jak go zaimplementować, więc moje pytania są poniżej:

  1. Co to jest?
  2. Dlaczego tego potrzebujemy?
  3. Jak to działa?
  4. Jak mogę osiągnąć to polimorficzne zachowanie w javascript?

Mam ten przykład. Ale jest łatwo zrozumiałe, jaki będzie wynik tego kodu. Nie daje jasnego pojęcia o samym polimorfizmie.

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);
AL-zami
źródło
To pytanie jest prawdopodobnie zbyt szerokie, aby dobrze współpracowało ze StackOverflow. Najlepsze, co moglibyśmy zrobić, to połączyć cię z innym wyjaśnieniem polimorfizmu. StackOverflow najlepiej odpowiada na konkretne pytania lub wyjaśnienia dotyczące konkretnego problemu, takie jak „Źródło powiedział, że polimorfizmem był XYZ, ale co oznacza Y?”
Witruwiusz
2
nie potrzebujesz tego. w ogóle. nie potrzebujesz nawet klas w JS, aw rzeczywistości istnieje wiele innych, prawdopodobnie lepszych, paradygmatów do tworzenia aplikacji. zastosuj / zadzwoń / połącz eliminuje potrzebę jednorodności, a dzięki miękkiemu obiektowi możesz modyfikować wszystko, aby pasowało do twoich potrzeb, bez uprzedniego dekorowania tego lub dziedziczenia specjalnych przypadków.
dandavis
1
Polimorfizm nie jest związany tylko z OO i ma wiele znaczeń. Możesz przeczytać tę inną odpowiedź w ramach pytań Czy polimorfizm jest możliwy bez dziedziczenia .
Edwin Dalorzo
Dziedziczenie jest zwykle wykonywane nieprawidłowo w JavaScript. Stworzenie instancji Parent, która będzie używana jako prototyp Childa, pokazuje brak zrozumienia roli, jaką funkcja konstruktora i prototyp odgrywają w definiowaniu i tworzeniu obiektu. Więcej informacji można znaleźć w tej odpowiedzi: stackoverflow.com/a/16063711/1641941
HMR,

Odpowiedzi:

99

Polimorfizm jest jednym z założeń programowania obiektowego (OOP). Jest to praktyka projektowania obiektów w celu współdzielenia zachowań i nadpisywania wspólnych zachowań konkretnymi. Polimorfizm wykorzystuje dziedziczenie, aby tak się stało.

W OOP wszystko jest zamodelowane jako obiekt. Tę abstrakcję można sprowadzić do śrub i nakrętek do samochodu lub tak szerokiego, jak po prostu typ samochodu z rocznikiem, marką i modelem.

Aby mieć polimorficzny scenariusz samochodu, istniałby podstawowy typ samochodu, a następnie byłyby podklasy, które odziedziczyłyby po samochodzie i zapewniałyby własne zachowania oprócz podstawowych zachowań, jakie miałby samochód. Na przykład podklasą może być TowTruck, która nadal będzie miała roczną markę i model, ale może również mieć dodatkowe zachowania i właściwości, które mogą być tak podstawowe jak flaga dla IsTowing, tak skomplikowane, jak specyfika windy.

Wracając do przykładu ludzi i pracowników, wszyscy pracownicy są ludźmi, ale nie wszyscy są pracownikami. To znaczy, że ludzie będą superklasą, a pracownicy podklasy. Ludzie mogą mieć wiek i wagę, ale nie mają pensji. Pracownicy to ludzie, więc z natury będą mieli wiek i wagę, ale także dlatego, że są pracownikami, będą mieli pensję.

Aby to ułatwić, najpierw napiszemy superklasę (Person)

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

Damy Person możliwość dzielenia się swoimi informacjami

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

Następnie chcielibyśmy mieć podklasę Osoba, Pracownik

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

Zastąpimy zachowanie getInfo, definiując takie, które jest bardziej odpowiednie dla pracownika

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

Można ich używać podobnie do oryginalnego kodu

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

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

Jednak nie wiele zyskuje się na dziedziczeniu, ponieważ konstruktor Pracownika jest tak podobny do osoby, a jedyna funkcja w prototypie jest nadpisywana. Siła w projektowaniu polimorficznym polega na dzieleniu zachowań.

Travis J.
źródło
2
@ user3138436 - To prawda. Prototypowy łańcuch zostanie sprawdzony pod kątem pierwszego wystąpienia, getInfoktóre będzie dotyczyło pracownika, ponieważ znajduje się wyżej w łańcuchu niż osoby. To właśnie miałem na myśli, kiedy powiedziałem „zastąpiony”.
Travis J
17
Nie używasz ponownie konstruktora (Person.call (this, arg)) i ustawiasz prototyp pracownika na instancję Person. Prototyp to miejsce, w którym przechodzą współdzielone elementy członkowskie, a funkcja konstruktora to miejsce, w którym tworzone są określone elementy instancji. Twoje próbki używają kodu kopiuj wklej, ponownie używają funkcji konstruktora i dziedziczą nieprawidłową część prototypu (osoba ma specyficzne elementy instancji, które nie mają żadnego interesu w Employee.prototype, zwłaszcza jeśli masz zmienne elementy członkowskie). Więcej informacji o dziedziczeniu przy użyciu funkcji konstruktora i prototypu tutaj: stackoverflow.com/a/16063711/1641941
HMR,
3
Aby Pracownik mógł ponownie użyć i rozszerzyć getinfo o osobie, może to po prostu zrobić return Person.prototype.getInfo.call(this) + + "and earns " + this.salary + " dollar.";Zamiast kopiować wklej kod ponownie użyć.
HMR,
3
tutaj, gdzie zastosowano polimorfizm?
Albert Jegani
2
@rpeg punkt polimorfizmu można lepiej dostrzec w przykładzie PO. funkcja showInfo (); akceptuje ogólny obiekt. polimorfizm to teraz zdolność do różnej reakcji w zależności od typu obiektu. Myślę, że ta odpowiedź nie wyjaśnia tego wystarczająco jasno, ponieważ wywołuje getInfo () dla każdego konkretnego obiektu.
Stefan
26

Jak wyjaśniono w tej innej odpowiedzi , polimorfizm ma różne interpretacje.

Najlepszym wyjaśnieniem na ten temat, jakie kiedykolwiek czytałem, jest artykuł Luca Cardelli , uznanego teoretyka typów. Artykuł nosi tytuł O zrozumieniu typów, abstrakcji danych i polimorfizmie .

Co to jest?

Cardelli definiuje w tym artykule kilka typów polimorfizmu:

  • uniwersalny
    • parametryczne
    • włączenie
  • Ad-hoc
    • przeładowanie
    • przymus

Być może w JavaScript nieco trudniej jest dostrzec skutki polimorfizmu, ponieważ bardziej klasyczne typy polimorfizmu są bardziej widoczne w statycznych systemach typów, podczas gdy JavaScript ma dynamiczny system typów.

Na przykład nie ma przeciążania metod lub funkcji ani automatycznego koercji typu w czasie kompilacji w JavaScript. W dynamicznym języku większość z tych rzeczy traktujemy jako coś oczywistego. Nie potrzebujemy też czegoś takiego jak polimorfizm parametryczny w JavaScript ze względu na dynamiczną naturę języka.

Mimo to, JavaScript ma formę dziedziczenia typów, które emulują te same idee polimorfizmu podtypów (sklasyfikowanych powyżej jako polimorfizm inkluzji przez Cardellego) w podobny sposób, jak to zwykle robimy w innych językach programowania obiektowego, takich jak Java lub C # (jak wyjaśniono w inna odpowiedź, którą udostępniłem powyżej).

Inną formą polimorfizmu, bardzo typową dla języków dynamicznych, jest typowanie kacze .

Błędem jest sądzić, że polimorfizm jest związany tylko z programowaniem obiektowym. Inne modele programowania (funkcjonalne, proceduralne, logiczne itp.) Oferują różne formy polimorfizmu w swoich systemach typów, prawdopodobnie w sposób nieco nieznany tym, które są używane tylko do OOP.

Dlaczego tego potrzebujemy?

Polimorfizm wspiera wiele dobrych cech w oprogramowaniu, między innymi sprzyja modułowości i możliwości ponownego użycia oraz sprawia, że ​​system typów jest bardziej elastyczny i plastyczny. Bez tego naprawdę trudno byłoby wnioskować o typach. Polimorfizm zapewnia, że ​​jeden typ można zastąpić innym kompatybilnym, pod warunkiem, że spełnia wymagania publicznego interfejsu, co sprzyja również ukrywaniu informacji i modułowości.

Jak to działa?

Odpowiedź na to pytanie nie jest łatwa, różne języki mają różne sposoby jej implementacji. W przypadku JavaScript, jak wspomniano powyżej, zobaczysz, że materializuje się on w postaci hierarchii typów wykorzystujących dziedziczenie prototypowe, a także możesz go wykorzystać za pomocą pisania kaczego.

Temat jest trochę obszerny i otworzyłeś dwa pytania w jednym poście. Być może najlepiej będzie, jeśli zaczniesz od przeczytania artykułu Cardellego, a następnie spróbujesz zrozumieć polimorfizm niezależnie od jakiegokolwiek języka lub paradygmatu programowania, a następnie zaczniesz tworzyć skojarzenia między koncepcjami teoretycznymi a tym, co ma do zaoferowania dowolny język, taki jak JavaScript, aby zrealizować te pomysły.

Edwin Dalorzo
źródło
14

Jaki jest cel polimorfizmu?

Polimorfizm sprawia, że ​​system typu statycznego jest bardziej elastyczny bez utraty (znacznego) bezpieczeństwa typu statycznego poprzez poluzowanie warunków równoważności typów. Dowód pozostaje, że program będzie działał tylko wtedy, gdy nie będzie zawierał żadnych błędów typu.

Funkcja polimorficzna lub typ danych jest bardziej ogólny niż monomorficzny, ponieważ może być używany w szerszym zakresie scenariuszy. W tym sensie polimorfizm reprezentuje ideę uogólnienia w ściśle typizowanych językach.

Jak to się ma do Javascript?

Javascript ma słaby, dynamiczny system typów. Taki system typów jest równoważny ze ścisłym systemem typów zawierającym tylko jeden typ. Możemy myśleć o takim typie jako o ogromnym typie unii (pseudo składni):

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

Każda wartość zostanie skojarzona z jedną z alternatyw tego typu w czasie wykonywania. A ponieważ JavaScript jest napisany słabo, każda wartość może zmienić swój typ dowolną liczbę razy.

Jeśli przyjmiemy perspektywę teoretyczną typu i weźmiemy pod uwagę, że istnieje tylko jeden typ, możemy z całą pewnością stwierdzić, że system typów JavaScript nie ma pojęcia polimorfizmu. Zamiast tego mamy pisanie typu kaczego i niejawne wymuszanie typu.

Ale to nie powinno powstrzymywać nas od myślenia o typach w naszych programach. Ze względu na brak typów w Javascript musimy je wywnioskować podczas procesu kodowania. Nasz umysł musi zastępować brakujący kompilator, tj. Gdy tylko spojrzymy na program, musimy rozpoznać nie tylko algorytmy, ale także podstawowe (być może polimorficzne) typy. Te typy pomogą nam tworzyć bardziej niezawodne i solidniejsze programy.

Aby zrobić to właściwie, przedstawię przegląd najczęstszych przejawów polimorfizmu.

Polimorfizm parametryczny (inaczej rodzajowe)

Polimorfizm parametryczny mówi, że różne typy są wymienne, ponieważ typy w ogóle nie mają znaczenia. Funkcja, która definiuje jeden lub więcej parametrów typu parametrycznego polimorficznego, nie może nic wiedzieć o odpowiednich argumentach, ale traktować je tak samo, ponieważ można je zaadaptować do dowolnego typu. Jest to dość restrykcyjne, ponieważ taka funkcja może działać tylko z tymi właściwościami jej argumentów, które nie są częścią ich danych:

// 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"]

Polimorfizm ad-hoc (znany również jako przeciążenie)

Polimorfizm ad hoc mówi, że różne typy są równoważne tylko w określonym celu. Aby być równoważnym w tym sensie, typ musi implementować zestaw funkcji specyficznych dla tego celu. Funkcja, która definiuje jeden lub więcej parametrów typu polimorficznego ad hoc, musi wtedy wiedzieć, które zestawy funkcji są skojarzone z każdym z jej argumentów.

Polimorfizm ad-hoc sprawia, że ​​funkcja jest zgodna z większą domeną typów. Poniższy przykład ilustruje cel „mapowania” i sposób, w jaki typy mogą implementować to ograniczenie. Zamiast zestawu funkcji, ograniczenie „mapowalne” obejmuje tylko jedną mapfunkcję:

// 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 {}
);

Polimorfizm podtypu

Ponieważ inne odpowiedzi obejmują już polimorfizm podtypów, pomijam je.

Strukturalny polimorfizm (inaczej podtyp strutrualny)

Polimorfizm strukturalny mówi, że różne typy są równoważne, jeśli zawierają tę samą strukturę w taki sposób, że jeden typ ma wszystkie właściwości drugiego, ale może zawierać dodatkowe właściwości. Biorąc to pod uwagę, polimorfizm strukturalny jest typowaniem typu kaczego w czasie kompilacji i z pewnością zapewnia dodatkowe bezpieczeństwo typów. Ale twierdząc, że dwie wartości są tego samego typu tylko dlatego, że mają pewne wspólne właściwości, całkowicie ignoruje semantyczny poziom wartości:

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

Niestety speeduważany jest za podtyp weighti gdy tylko porównamy valuewłaściwości, praktycznie porównujemy jabłka z pomarańczami.


źródło
1
Chociaż jest to pod wieloma względami dokładniejsze (iz pewnością dokładniejsze) niż przyjęta odpowiedź, nie ma takiej samej dostępności: ta odpowiedź zakłada, że ​​pytający jest już zbyt sprytny, aby zawracać sobie głowę pytaniem :)
Jared Smith
@JaredSmith Próbowałem sprowadzić temat do kilku łatwych do uchwycenia akapitów. Ale im głębiej wiercę, tym bardziej się to komplikuje. Nigdy nie znalazłem dobrego źródła polimorfizmu w językach bez typu, dlatego myślę, że ta odpowiedź jest jednak cenna.
edycja znacznie poprawia dostępność. Jeśli chodzi o użyteczność polimorfizmu w językach dynamicznych, jest ich wiele, ale staram się wymyślić dobry przykład w JS. Lepszym przykładem mogą być magiczne metody Pythona, które pozwalają typom zdefiniowanym przez użytkownika na pracę z funkcjami polimorficznymi, takimi jak len. A może conjz ubrań.
Jared Smith
8

co to jest?

Poly = wiele, morfizm = zmiana formy lub zachowania.

dlaczego tego potrzebujemy?

W programowaniu jest używany, gdy chcemy, aby interfejs funkcji (powiedzmy funkcji X) był wystarczająco elastyczny, aby akceptować różne typy lub liczbę parametrów. Ponadto, w oparciu o zmianę typów parametrów lub liczb, możemy chcieć, aby funkcja X zachowywała się inaczej (morfizm).

Jak to działa?

Piszemy wiele implementacji funkcji X, w których każda implementacja akceptuje różne typy parametrów lub liczbę parametrów. Na podstawie typu lub liczby parametrów kompilator (w czasie wykonywania) decyduje, która implementacja X powinna zostać wykonana, gdy X jest wywoływany z jakiegoś kodu.

jak mogę osiągnąć to polimorficzne zachowanie w javascript?

JS nie jest językiem typizowanym, więc tak naprawdę nie miał na celu używania pojęć OOP, takich jak polimorfizm. Jednak nowsza wersja JS zawiera teraz klasy i istnieje możliwość, że polimosfizm może zacząć mieć sens również w JS. Inne odpowiedzi zawierają kilka interesujących obejść.

hadaytullah
źródło
4

Polimorfizm oznacza możliwość wywołania tej samej metody na różnych obiektach, a każdy obiekt reaguje w inny sposób, nazywany jest POLIMORFIZMEM .

    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);

SooRaj Patil
źródło
2

JavaScript jest językiem interpretowanym, a nie kompilowanym.

Polimorfizm czasu kompilacji (lub polimorfizm statyczny) Polimorfizm czasu kompilacji to nic innego jak przeciążanie metod w java, c ++

Tak więc przeciążanie metod nie jest możliwe w javascript.

Ale polimorfizm dynamiczny (w czasie wykonywania) jest polimorfizmem istniejącym w czasie wykonywania, więc nadpisywanie metody jest możliwe w javascript

innym przykładem jest PHP.

Apoorv
źródło