Modest Programmer logo
1 grudnia 2018
Tagi: JavaScript
Nadszedł czas na omówienie ostatniego z zapowiadanych filarów programowania zorientowanego obiektowo w JavaScript. W poprzednich artykułach starałem Ci się opisać jak wygląda enkapsulacja, abstrakcja oraz dziedziczenie, dzisiaj opiszę polimorfizm. Zacznijmy może od teorii, polimorfizm czyli wielopostaciowość, innymi słowy zapisanie jednej funkcji pod różnymi postaciami. A co to oznacza w praktyce? O tym za chwilę.

Przypomnijmy sobie nasz przykładowy kod, który został przedstawiony w artykule poświęconemu dziedziczeniu:
function extend(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}

function Shape() {
}

Shape.prototype.setWidth = function(width) {
    this.width = width;
}

function Square() {
}

function Rectangle() {
}

extend(Square, Shape);
extend(Rectangle, Shape);

let square = new Square();
let rectangle = new Rectangle();

square.setWidth(10);
rectangle.setWidth(20);
Załóżmy, że chcemy aby obiekty Square oraz Rectangle miały metody wyświetlające swoje nazwy, tzn. metody które po prostu wyświetlą w konsoli dla Square - kwadrat, a dla Rectangle - prostokąt. Następnie stworzymy tablicę różnych obiektów i w zależności od typu obiektu zostanie wyświetlona odpowiednia metoda.

Nie wiedząc jak używać polimorfizmu możesz to zrobić mniej więcej w taki sposób:
function extend(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}

function Shape() {
}

Shape.prototype.setWidth = function(width) {
    this.width = width;
}

function Square() {
    this.displaySquareName = function() {
        console.log('kwadrat');
    }
}

function Rectangle() {
    this.displayRectangleName = function() {
        console.log('prostokąt');
    }
}

extend(Square, Shape);
extend(Rectangle, Shape);

let shapes = [
    new Square(),
    new Rectangle()
];

for (let shape of shapes) {
    shape.setWidth(10);  

    if (shape.constructor.name === 'Square')    
        shape.displaySquareName();

    if (shape.constructor.name === 'Rectangle')
        shape.displayRectangleName();
}
//kwadrat
//prostokąt
Najpierw dodaliśmy do każdego obiektu odpowiednią metodę. Dla obiektu Square jest to metoda displaySquareName, a dla Rectangle displayRectangleName. Następnie tworzymy tablicę z obiektami, które dziedziczą po obiekcie Shape i wyświetlamy w zależności od typu obiektu odpowiednią metodę. Oczywiście to rozwiązanie nie jest najlepsze, mówiąc prawdę jest złe :) Najgorsze w tym kodzie jest sprawdzanie typu obiektu w pętli for, a co by było gdyby mieliśmy jeszcze więcej różnych typów obiektów? Na pewno wówczas takich instrukcji warunkowych musiało by być odpowiednio więcej, przez co nasz kod byłby jeszcze gorszy. Dużo lepszym rozwiązaniem w tym wypadku będzie zastosowanie właśnie tytułowego polimorfizmu.

Aby lepiej zrozumieć polimorfizm musisz najpierw poznać pojęcie nadpisywania metod. Spójrz na poniższy przykład:
function extend(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}

function Shape() {
}

Shape.prototype.setWidth = function(width) {
    this.width = width;
}

function Square() {
}

extend(Square, Shape);

Square.prototype.setWidth = function(width) {
    this.width = width * 10;
}

let square = new Square();
square.setWidth(10);
console.log(square.width); //100
Jak widzisz po rozszerzeniu obiektu Square nadpisana zostaje funkcja nadrzędna setWidth. Dzięki temu jak możesz zobaczyć w ostatniej linii wartość width wynosi 100, ponieważ została wywołana nasza nowo zdefiniowana funkcja, która mnoży szerokość przez 10.

Dzięki tej technice możemy dużo łatwiej napisać nasz poprzedni kod, którego zadaniem było wyświetlenie nazw obiektów w tablicy i nasz finalny kod może wyglądać w taki sposób:
function extend(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}

function Shape() {
    this.displayName = function() {
        console.log('kształ');
    }
}

Shape.prototype.setWidth = function(width) {
    this.width = width;
}

function Square() {
}

function Rectangle() {
}

extend(Square, Shape);
extend(Rectangle, Shape);

Square.prototype.displayName = function() {
    console.log('kwadrat');
}

Rectangle.prototype.displayName = function() {
    console.log('prostokąt');
}

let shapes = [
    new Square(),
    new Rectangle()
];

for (let shape of shapes) {
    shape.setWidth(10);  
    shape.displayName();
}
//kwadrat
//prostokąt
Jak widzisz dzięki polimorfizmowi kod jest dużo łatwiejszy i bardziej czytelny. Oba obiekty Square oraz Rectangle dziedziczą po Shape, a następnie nadpisuję metodę displayName. Dzięki takiemu zabiegowi, próbując użyć tej metody w pętli, iterując po obiektach dziedziczących po Shape nie musimy sprawdzać typu obiektu. Mamy pewność, że każdy obiekt ma zaimplementowaną metodę displayName odpowiednią dla swojego typu.

To wszystko co chciałem Ci przekazać w tym artykule. Zapraszam Cię do kolejnych artykułów z serii podstaw programowania w języku JavaScript.

Poprzedni artykuł - EcmaScript 6 dziedziczenie w JavaScript.
Następny artykuł - Kurs podstawy programowania w języku JavaScript.
Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
Programista. Specjalizuje się w C#, głównie WPF, Windows Forms oraz ASP.NET MVC.
Autor bloga ModestProgrammer.pl
Dodaj komentarz
© Copyright 2018 modestprogrammer.pl. Wszelkie prawa zastrzeżone Design by Kazimierz Szpin