Prototyp – to wzorzec projektowy którego celem jest kopiowanie wzorcowego już istniejącego obiektu.
Zastosowanie
Wzorzec jest stosowany w przypadku gdy koszt utworzenia nowego obiektu jest wysoki. Obrazową sytuacją jest np. sczytanie ustawień z pliku lub sieci internetowej, które odbywa się w konstruktorze.
Innym zastosowaniem wzorca jest zapamiętanie stanu obiektu.
Praktyczne wskazówki
Dobrą praktyką jest utworzenie klasy bazowej, po której klasy pohodne będą musiały nadpisać metodę wirtualną clone().
{
public:
virtual Prototype * clone() const = 0;
virtual ~Prototype() { }
};
Warto również zwrócić uwagę na destruktor wirtualny w przypadku gdy klasy pochodne posiadają wskaźniki, ponieważ domyślne zrealizowanie płytkiego kopiowania, czyli samych wskaźników bez przydzielenia w kopii miejsca, może spowodować nieprzyjemne konsekwencje. Dlatego też oprócz implementacji konstruktora kopiującego, w którym przydzielimy miejsce wskaźnikom należy pamiętać o odpowiednim zwolnieniu miejsca, aby uniknąć wycieków pamięci.
Przykład konstruktora kopiującego.
: dlugosc( kopia.dlugosc )
, kolor( kopia.kolor )
{
this->paczka = new int(kopia.getPaczka());
}
W roli przypomnienia różnica pomiędzy klonowaniem płytkim a głębokim.
- Klonowanie płytkie (Shallow copy) – tworzona jest kopia aktualnego obiektu i wszystkich składników obiektu (pole klasy wraz z typami wbudowanymi) w tym obiekcie. Należy pamiętać że podczas takiego klonowania gdy mamy wskaźniki do obiektów to tylko same adresy są klonowane, ale domyślnie nie jest przydzielana pamięć na to co znajduje się pod danym adresem. To samo tyczy się referencji do innych obiektów, tablic i innego typu kontenerów.
- Klonowanie głębokie (Deep copy) – alternatywą jest kopiowanie głębokie, gdzie to my określamy jakie obiekty, tablice i wskaźniki zostaną sklonowane. Aby tak było należy utworzyć konstruktor kopiujący lub przeciążyć operator przypisania =.
#include <QString>
#include <QDebug>
class Prototype
{
public:
virtual Prototype * clone() const = 0;
virtual ~Prototype() { }
};
class Dlugopis : public Prototype
{
private:
const int dlugosc;
const QString kolor;
int *paczka;
public:
Dlugopis(const int dlugosc, const QString kolor, int paczka = 0 )
: dlugosc( dlugosc ), kolor( kolor ), paczka(new int(paczka))
{
}
Dlugopis( const Dlugopis & kopia )
: dlugosc( kopia.dlugosc )
, kolor( kopia.kolor )
{
this->paczka = new int(kopia.getPaczka());
}
virtual ~Dlugopis()
{
delete paczka;
}
virtual Dlugopis * clone() const
{
return new Dlugopis( *this );
}
void setIlosc(int value){
if(paczka)
delete paczka;
paczka = new int(value);
}
int getPaczka() const{
return *paczka;
}
void print()
{
qDebug() << "Paczka: " << *paczka << " Kolor: "<< kolor << " Długość: " << dlugosc;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Dlugopis *zamowienie1 = new Dlugopis(5,"Czarny",5);
Dlugopis *zamowienie2 = zamowienie1->clone();
zamowienie1->print();
delete zamowienie1;
zamowienie2->print();
delete zamowienie2;
return a.exec();
}
OUTPUT:
Paczka: 5 Kolor: "Czarny" Długość: 5
Paczka: 5 Kolor: "Czarny" Długość: 5
Podsumowując powyższy wzorzec projektowy stosuje się gdy:
- chcesz mieć możliwość otrzymania prawidłowej kopii obiektu z poziomu interfejsu – metoda clone();
- chcesz tworzyć nowe kopie obiektów z wcześniej przygotowanych obiektów (zwanych prototypami);
- chcesz mieć możliwość zapamiętywania i przywrócenia obiektu do stanu w którym się znajdował w chwili jego zapamiętania
Należy zwrócić uwagę że zamiast metody clone() moglibyśmy utworzyć przeciążenie operatora =, jednak clone() chroni nas przed zjawiskiem slicingu podczas dziedziczenia, czyli odcinania części zmiennych podczas przypisania przez operator =.
Derived b;
a = b; // działa, ale jest to tzw. slicing - nic dobrego !!!