Wzorzec projektowy Prototyp - okładka

Wzorzec projektowy Prototyp

Posted on Categories Czysty kod

Ten wpis jest jednym z serii wpisów o wzorcach projektowych. Zachęcam do zapoznania się z pozostałymi wpisami dotyczących wzorców projektowych:

Tym razem wezmę na tapet wzorzec projektowy Prototyp.

Wzorzec projektowy Prototyp

Prototyp (Prototype) jest jednym z kreacyjnych wzorców projektowych. Wzorzec zakłada istnienie obiektu – prototypu, który może ulec powieleniu. Powielenie prototypu powoduje powstanie nowego obiektu, będącego klonem oryginału. W literaturze, implementację wzorca Prototyp przestawia się poprzez implementację publicznej metody clone(), której celem jest powołanie do życia klona obiektu. Wzorzec Prototyp przedstawia poniższy diagram:

Wzorzec prototyp - diagram UML

Metoda abstrakcyjna clone() została zdefiniowana w bazowej klasie abstrakcyjnej. W oparciu o tę klasę, tworzone będą klasy pochodne. W każdej klasie pochodnej, według diagramu, musi zostać zaimplementowana metoda clone(). Obiekty utworzone w oparciu o klasę pochodną, będą miały możliwość ich sklonowania. W rezultacie powstanie nowy, identyczny obiekt.

Wzorzec Prototyp znajduje swoje zastosowanie w kilku sytuacjach. Pierwszą z nich jest sytuacja, gdy nowe obiekty mają wiele właściwości, a nowy obiekt różni się nieznacznie od oryginału. Przykładem może być sklep internetowy z możliwością zduplikowania produktu z poziomu panelu administratora. Załóżmy, że w sklepie dostępne są takie same koszulki, w wielu różnych kolorach, a każdy wariant kolorystyczny koszulki ma być osobnym produktem. W takiej sytuacji, sklonowanie pierwszego utworzonego produktu, a następnie zmiana właściwości color będzie wygodniejsza niż ręczne definiowanie wszystkich właściwości dla każdego produktu.

Prototyp okaże się równie przydatny, gdy w systemie potrzeba stworzyć dużą ilość identycznych obiektów. Mogłaby to być na przykład symulacja rozmnażania się bakterii.

Innym przypadkiem zastosowaniem wzorca Prototyp będzie sytuacja, gdy stworzenie nowej instancji obiektu jest kosztowne. Przykładem może być sytuacja, gdy powołanie do życia nowej instancji obiektu wymaga zaawansowanej walidacji, zasobów, czy połączenia z zewnętrzną usługą.

W środowisku JavaScript, przypadkiem użycia wzorca Prototyp jest sytuacja, gdy jest potrzeba skopiowania obiektu zawierającego typy referencyjne. W przypadku obiektów z właściwościami, które są typami prymitywnymi, rozwiązaniem jest wykorzystanie spread operatora:


const cloned: Record<string, unknown> = { ...sampleObject };

Nie zadziała to jednak dobrze w przypadku, gdy sampleObject zawiera typy referencyjne, na przykład obiekty czy funkcje. O tym, czym są typy referencyjne, oraz o poprawnej strategii kopiowania obiektów z takimi typami więcej możesz dowiedzieć się z artykułu Kopiowanie obiektów w JavaScript. Jednym ze sposobów na rozwiązanie problemu sampleObject jest zaimplementowanie wzorca Prototyp.

Przykład zastosowania wzorca Prototyp

Implementację wzorca Prototyp przedstawię na przykładzie fragmentu systemu e-commerce. Implementacja będzie obejmowała mechanizm klonowania produktów opisany nieco wcześniej w tym artykule. Tak jak we wcześniejszym przykładzie, celem wykorzystania wzorca jest zwiększenie wygody tworzenia nowych produktów. Aby lepiej zobrazować przykład, przygotowałem diagram klas UML:

Diagram - praktyczne wykorzystanie wzorca Prototyp

Implementacja w TypeScript dla klas Tee oraz Trousers wygląda następująco:


abstract class Prototype {
    abstract clone(): Prototype;
}

class Tee extends Prototype {
    public color: string;

    public name: string;

    public price: number;

    public size: string;

    public constructor( params: { color: string; name: string; price: number; size: string; } ) {
        super();

        this.color = params.color;
        this.name = params.name;
        this.price = params.price;
        this.size = params.size;
    }

  public clone(): Tee {
    const { color, name, price, size } = this;

    return new Tee( { color, name, price, size } );
  }
}

class Trousers extends Prototype {
    public color: string;

    public name: string;

    public price: string;

    public width: number;

    public length: number;

    public constructor( params: { color: string; name: string; price: string; width: number; length: number; } ) {
        super();

        this.color = params.color;
        this.name = params.name;
        this.price = params.price;
        this.width = params.width;
        this.length = params.length;
    }

  public clone(): Trousers {
    const { color, name, price, width, length } = this;

    return new Trousers( { color, name, price, width, length } );
  }
}

Dla prostoty przykładu, pominąłem nieistotne szczegóły produktów czy aspekty związane z poprawną hermetyzacją właściwości klas. W przypadku TypeScript, abstrakcyjną klasę można zastąpić interfejsem IPrototype ze zdefiniowaną metodą clone(). Bazując na powyższym kodzie, łatwo można przygotować listę produktów:


const trousersList: Trousers[] = [];

const sampleTrousers: Trousers = new Trousers({
    color: 'black',
    name: 'Style Chino Classic',
    price: '199',
    width: 28,
    length: 32
} );

trousersList.push( sampleTrousers );

[ 29, 30, 31, 32, 34, 34, 35, 36 ].map( width => {
    const copiedTrousers: Trousers = sampleTrousers.clone();

    copiedTrousers.width = width;

    trousersList.push( copiedTrousers );
} );

W realnej aplikacji, aplikacja najpewniej oferowałaby przycisk w stylu „Zduplikuj produkt”, a po kliknięciu wywoływała metodę clone() na oryginalnym produkcie i tworzyła nowy produkt oraz pozwalała na modyfikację jego cech. W powyższym przykładzie, stworzyłem listę, która zawiera produkt Style Chino Classic w kilku dostępnych rozmiarach.

Podsumowanie

Wzorzec Prototyp może kojarzyć się ze wzorcem Fabryki, ponieważ jego zastosowanie jest podobne. Diabeł jednak tkwi w szczegółach i w pewnych sytuacjach lepsza okaże się Fabryka, w innych Prototyp. Przypadki wykorzystania Prototypu już znasz. O przypadkach zastosowania Fabryki więcej dowiesz się z tego artykułu – Wzorzec projektowy Factory (Fabryka).

Jeśli miałeś/aś okazję wykorzystać Prototyp w praktyce, daj znać o tym w komentarzu. Będzie to świetna lekcja i wartość dla mnie i innych czytelników! Nie zapomnij też zajrzeć do źródeł i materiałów dodatkowych.

Źródła i materiały dodatkowe

Dominik Szczepaniak

Zawodowo Software Engineer w CKSource. Prywatnie bloger, fan włoskiej kuchnii, miłośnik jazdy na rowerze i treningu siłowego. Oprócz programowania, interesuję się tematami gospodarczymi, ekonomicznymi i inwestycjami na rynkach kapitałowych.

Inne wpisy, które mogą Cię zainteresować

Zapisz się na mailing

Zapisując się na mój mailing będziesz otrzymywać wartościowe treści powiadomienia o najnowszych wpisach na skrzynkę email.

Subscribe
Powiadom o
guest

0 komentarzy
Inline Feedbacks
View all comments