Wzorzec Fabryka abstrakcyjna i Metoda wytwórcza C++/Qt

Zastosowanie

Fabryka abstrakcyjna (ang. abstract factory) to kreacyjny wzorzec metody wytwórczej dostarcza abstrakcji do tworzenia obiektów nieokreślonych, ale powiązanych typów. Umożliwia także dziedziczącym klasom decydowanie jakiego typu ma to być obiekt. Jest on często używany wraz ze wzorce metody wytwórczej lub inaczej nazywanej metody fabrykującej (ang. factory method), która umożliwia zwracanie obiektów, lub przypisywanie im właściwości w zależności od parametru wysłanego.

Fabryka abstrakcyjna

Wskazówki praktyczne

Wzorzec jest stosowany wówczas gdy chcemy ujednolicić interfejs i uniezależnić interfejs od implementacji i składania obiektów. W przypadku metody wytwórczej stosujemy ją wówczas gdy nie wiemy jakie obiekty będą tworzone podczas używania programu (np. będzie delegatem lub tworzona przez wybór użytkownika). Dobrym przykładem jest generowania poprzez klasy konkretne plików html, pdf lub zwykłego tekstu, za pomocą wspólnego interfejsu (fabryki abstrakcyjnej).

Zalety i wady

Zalety

  • Odseparowanie klas konkretnych poprzez posługiwanie się interfejsami abstrakcyjnymi.
  • Łatwość rzutowania pomiędzy obiektami pochodnymi.
  • Spójność klas, w sytuacji gdy pożądane jest, aby klasy produkty były z określonej rodziny, fabryka bardzo dobrze to zapewnia.
  • Łatwość utrzymania kodu po dodaniu klasy pochodnej, oraz łatwe dodawanie implementacji logiki nowych klas pochodnych.

Wady:

  • Trudność w rozszerzaniu o nowe funkcjonalności klas pochodnych, spowodowana koniecznością rozszerzania interfejsów fabryki.
#include <QDebug>
#include <QString>

using namespace std;

// Abstract base class
class Oferta {
public:
    virtual const QString getOfertaPodstawowa() = 0;
    virtual const QString getDodatki() = 0;

    void print() {
        qDebug() << getOfertaPodstawowa() <<  getDodatki();
    }
    virtual ~Oferta(){qDebug() <<"Oferta usunięta";}

};

// Klasa konkretna
class OszczednaOferta : public Oferta {
public:
   const QString getOfertaPodstawowa() {
        return "Abonament 20zł";
    }

   const QString getDodatki() {
        return "Pakiet 60 minut i 30 sms-ów";
    }
   ~OszczednaOferta(){ qDebug() <<"Oszczędna oferta usunięta"; }
};

// Klasa konkretna
class SredniaOferta : public Oferta {
public:
    const QString getOfertaPodstawowa()
    {
        return "Abonament 50zł";
    }

   const QString getDodatki()
   {
        return "Pakiet 1000 minut i 1000 sms-ów";
    }
   ~SredniaOferta(){ qDebug() <<"Średnia oferta usunięta";}
};

// Klasa konkretna
class DrogaOferta : public Oferta {
public:
   const QString getOfertaPodstawowa() {
        return "Abonament 40zł";
    }

   const QString getDodatki() {
        return "Pakiet 300 minut i 200 sms-ów";
    }
   ~DrogaOferta(){ qDebug() <<"Droga oferta usunięta";}
};

// Abstract Factory returning a Oferta
class OfertaFactory {
public:
    Oferta* getOferta(const QString type)
    {
        if ( type == "Oszczędna" ) return new OszczednaOferta();
        else if ( type == "Średnia" ) return new SredniaOferta();
        else if ( type == "Droga" ) return new DrogaOferta();
        return NULL;
    }
    ~OfertaFactory(){

}

};

int main()
{
    OfertaFactory* myFactory = new OfertaFactory();

    Oferta* myOferta1 = myFactory->getOferta("Oszczędna");
    myOferta1->print();

    Oferta* myOferta2 = myFactory->getOferta("Średnia");
    myOferta2->print();

    Oferta* myOferta3 = myFactory->getOferta("Droga");
    myOferta3->print();

    delete myOferta1;
    delete myOferta2;
    delete myOferta3;

    return 0;
}

Wyjście programu:

"Abonament 20zł" "Pakiet 60 minut i 30 sms-ów"
"Abonament 50zł" "Pakiet 1000 minut i 1000 sms-ów"
"Abonament 40zł" "Pakiet 300 minut i 200 sms-ów"
Oszczędna oferta usunięta
Oferta usunięta
Średnia oferta usunięta
Oferta usunięta
Droga oferta usunięta
Oferta usunięta

Przykład 2

#include <iostream>
#include <QtCore>


using namespace std;

class Shape {
  public:
    Shape() {
      id_ = total_++;
    }
    virtual void draw() = 0;
  protected:
    int id_;
    static int total_;
};
int Shape::total_ = 0;

class Circle : public Shape {
  public:
    void draw() {
      cout << "circle " << id_ << ": draw" << endl;
    }
};
class Square : public Shape {
  public:
    void draw() {
      cout << "square " << id_ << ": draw" << endl;
    }
};
class Ellipse : public Shape {
  public:
    void draw() {
      cout << "ellipse " << id_ << ": draw" << endl;
    }
};
class Rectangle : public Shape {
  public:
    void draw() {
      cout << "rectangle " << id_ << ": draw" << endl;
    }
};

class Factory {
  public:
    virtual Shape* createCurvedInstance() = 0;
    virtual Shape* createStraightInstance() = 0;
};

class SimpleShapeFactory : public Factory {
  public:
    Shape* createCurvedInstance() {
      return new Circle;
    }
    Shape* createStraightInstance() {
      return new Square;
    }
};
class RobustShapeFactory : public Factory {
  public:
    Shape* createCurvedInstance()   {
      return new Ellipse;
    }
    Shape* createStraightInstance() {
      return new Rectangle;
    }
};

int main() {
#ifdef Q_OS_LINUX
  Factory* factory = new SimpleShapeFactory;
#elif Q_OS_ANDROID
  Factory* factory = new RobustShapeFactory;
#endif
  Shape* shapes[3];

  shapes[0] = factory->createCurvedInstance();   // shapes[0] = new Ellipse;
  shapes[1] = factory->createStraightInstance(); // shapes[1] = new Rectangle;
  shapes[2] = factory->createCurvedInstance();   // shapes[2] = new Ellipse;

  for (int i=0; i < 3; i++) {
    shapes[i]->draw();
  }
  for (int i=0; i < 3; i++)
    delete shapes[i];
}

Wzorzec projektowy Singleton C++ / Qt

Zastosowanie

Kreacyjny wzorzec projektowy, charakteryzujący się jedną instancją danego obiektu oraz globalnym dostępem do funkcji getInstance(), dzięki czemu możemy uzyskać do niego dostęp praktycznie z każdego miejsca. Inną cechą charakterystyczną jest Lazy loading, czyli alokacja pamięci dla singletona występuje dopiero przy jego pierwszym wywołaniu (odwołaniu się do niego za pomocą funkcji getInstance()).

Singleton

Własności singletona

ZALETY

  • jest tworzony przy pierwszym wywołaniu,
  • jest niszczony po zakończeniu działania programu,
  • po utworzeniu istnieje tylko i wyłącznie jego jedna instancja,
  • programista nie uczestniczy w procesie wywołania konstruktora, który zwykle jest prywatny lub zabezpieczony (protected), po to aby nie tworzyć więcej instancji obiektu singletona.

WADY

  • Rozhermetyzowanie klas poprzez globalny dostęp,
  • Trudniejsza analiza aplikacji podczas testów,
  • Może powodować problemy podczas wykorzystania go w wielowątkowych procesach.
#include <QDebug>
#include <QString>

class Ustawienia
{
public:
    static Ustawienia & getSingleton()
    {
        static Ustawienia singleton; // lazy Loading
        return singleton;
    }

    void setStyle(const QString style)
    {
        this->style = style;
    }
    QString getStyle(){return style;}

private:
    QString style;
    Ustawienia(): style("Unknown"){}
    ~Ustawienia(){}

};

class Klient
{
public:
    Klient( )
    {
    Ustawienia::getSingleton().setStyle("Blue");
    }

    void whiteStyle()
    {
        Ustawienia::getSingleton().setStyle("White");
    }
};

void funkcjaGlobalStyle()
{
    qDebug() << Ustawienia::getSingleton().getStyle();
}

int main()
{
    funkcjaGlobalStyle();

    Klient klient;
    funkcjaGlobalStyle();

    klient.whiteStyle();
    funkcjaGlobalStyle();

    Ustawienia::getSingleton().setStyle("Red");
    funkcjaGlobalStyle();

    return 0;
}

Rezultat

"Unknown"
"Blue"
"White"
"Red"

Wzorzec Budowniczy C++/Qt

Budowniczy

Wzorzec Budowniczy to wzorzec kreacyjny zapewniający kontrolę nad procesem tworzenia klas. Jest on przydatny, gdy chcemy zastosować wiele różnych konstruktorów, zapewniając większą czytelność kodu.

builder_uml_01

Elementy charakterystyczne

W tym wzorcu możemy wyróżnić następujące elementy:
Budowniczy – interfejs abstrakcyjny do tworzenia części składowych obiektu złożonego, w naszym przykładzie jest to klasa Pizza
(produktu)
Konkretny budowniczy – jego celem jest konstruowanie i zestawianie części produktu poprzez implementowanie interfejsu Budowniczego. Definiuje i kontroluje on tworzoną przez siebie
reprezentację. Konkretnych budowniczych może być wiele, w naszym przykładzie jest to HawajskaPizza, OstraPizza i MexicoPizza.
Dyrektor – konstruuje obiekty, używając interfejsu Budowniczego do wywoływania metod konkretnego budowniczego oraz nadzoruje proces budowy aby proces konstrukcyjny przebiegał w odpowiedniej sekwencji. W naszym przypadku jest to kelner.
Produkt – złożony obiekt który tworzony jest z wielu części składowych. Jest on konstruowany przez Dyrektora przy użyciu konkretnego budowniczego. W naszym przypadku jest to Pizza którą zwaraca getProduct();

Zalety

  • Różnorodność implementacji wewnętrznych struktur klas.
  • Łatwość konwersji tych samych danych do kilku różnych postaci. Np. jeden format tekstu na różne typy wyjściowe PDF, HTML, Plain text.
  • Duża skalowalność dzięki elastyczności w modyfikacjach (dodawanie nowych reprezentacji klas jest uproszczone). Gdy Chcemy sobie zagwarantować możliwość łatwego dodawania obsługi nowego formatu danych wyjściowych, zapobiegając jednocześnie jakimkolwiek modyfikacjom klasy zarządzającej obiektami budowniczymi.
  • Większa możliwość kontrolowania tego, w jaki sposób tworzony jest obiekt (proces konstrukcyjny jest niezależny od elementów, z których składa się tworzony obiekt.

Wady

  • Duża liczba obiektów reprezentujących konkretne produkty.
  • Redundancja – nieumiejętne używanie wzorca może spowodować nieczytelność kodu (jeden produkt może tworzony przez zbyt wielu budowniczych).

Zastosowanie

Wzorzec budowniczego stosowany jest do oddzielenia sposobu tworzenia obiektów od tego jak te obiekty mają wyglądać. Używamy go wtedy gdy obiekty różnią się finalnie, ale zawierają takie same elementy i chcemy je poskładać z takich samych części np. gdy składamy komputer to robimy to z różnych elementów, ale finalnie osiągamy ten sam produkt – komputer. Tak samo jest ze składaniem samochodów ( rożne silniki, amortyzatory, światła), jadłospisów (różne elementy tj. ziemiaki z mięsem lub ryż z mięsem, różne rodzaje surówek).

#include <QDebug>
#include <QString>
#include <QList>

class Pizza
{
public:
    enum SKLADNIK{
        PIECZARKI = 1,
        SER = 2,
        SZYNKA = 4,
        PEPERONI = 8,
        KUKURYDZA = 16,
        TUNCZYK = 32,
        KURCZAK = 64,
        BROKULY = 128,
        JAJKO = 256,
        CEBULA = 512,
        ANANAS = 1024,
        OREGANO = 2048,
        FASOLA = 4096,
        SALAMI = 8192
    };

    enum SOS{
        CZOSNKOWY = 1,
        POMIDOROWY = 2,
        OLIWA = 4
    };

    const QString getSkladniki(const int skladnik)
    {
                switch(skladnik)
                {
                case PIECZARKI:
                    return "Pieczarki";
                case SER:
                    return "Ser";
                case SZYNKA:
                    return "Szynka";
                case PEPERONI:
                    return "Peperoni";
                case KUKURYDZA:
                    return "Kukurydza";
                case TUNCZYK:
                    return "Tuńczyk";
                case KURCZAK:
                    return "Kurczak";
                case BROKULY:
                    return "Brokuły";
                case JAJKO:
                    return "Jajko";
                case CEBULA:
                    return "Cebula";
                case ANANAS:
                    return "Ananas";
                case SALAMI:
                    return "Salami";
                case FASOLA:
                    return "Fasola";
                case OREGANO:
                    return "Oregano";
                default:
                    return "Niezdefiniowany";
                }
    }

    const QString getSosy(const int sos)
    {
                switch(sos)
                {
                case CZOSNKOWY:
                    return "Czosnkowy";
                case POMIDOROWY:
                    return "Pomidorowy";
                case OLIWA:
                    return "Oliwa";
                default:
                    return "Niezdefiniowany";
                }
    }

private:
    QString nazwa;
    bool ciasto;
    int sos;
    int skladniki;

public:
    Pizza() { }
    ~Pizza() { }

    void setCiasto(bool grube = false)
    {
        ciasto = grube;
    };
    void setSos(const int sklad) { this->sos = sos; };
    void setDodatki(const int sklad) { skladniki = sklad;  };
    void setDodatkiNowe(const int sklad) { skladniki += sklad;  };
    void setNazwa(const QString &value) { nazwa = value; };

    void showPizza()
    {
        QString nazwy;
        QString sosy;
        for(int i = 1; i <= SALAMI;i*=2)
            if(i & skladniki)
                nazwy += (nazwy.isEmpty() ? "" : ", ") + getSkladniki(i);

        for(int i = 1; i <= OLIWA;i*=2)
            if(i & skladniki)
                sosy += (sosy.isEmpty() ? "" : ", ") + getSosy(i);

        qDebug() << "Pizza" << nazwa
                 << " z grubym ciastem: " << (ciasto ? "Tak" : "Nie")
                 << "\nSos: " << sosy
                 << " Dodatki " << nazwy << "\n";
    }
};
// Builder
class PizzaBuilder
{
protected:
    Pizza * pizza;
public:
    PizzaBuilder() {}
    virtual ~PizzaBuilder() {}
    Pizza *getPizza() { return pizza; }

    void createNewPizzaProduct() { pizza = new Pizza; }

    virtual void buildNazwa()=0;
    virtual void buildCiasto()=0;
    virtual void buildSos()=0;
    virtual void buildDodatki()=0;

};

class HawajskaPizza : public PizzaBuilder
{
public:
    HawajskaPizza() : PizzaBuilder() {}
    ~HawajskaPizza(){}

    void buildNazwa() { pizza->setNazwa("Hawajska"); };
    void buildCiasto() { pizza->setCiasto(true); }
    void buildSos() { pizza->setSos(Pizza::CZOSNKOWY); }
    void buildDodatki() { pizza->setDodatki(Pizza::SER | Pizza::SZYNKA | Pizza::ANANAS  | Pizza::OREGANO); }
};
class OstraPizza : public PizzaBuilder
{
public:
    OstraPizza() : PizzaBuilder() {}
    ~OstraPizza() {}

    void buildNazwa() { pizza->setNazwa("Ostra"); };
    void buildCiasto() { pizza->setCiasto(false); }
    void buildSos() { pizza->setSos(Pizza::CZOSNKOWY | Pizza::POMIDOROWY); }
    void buildDodatki() { pizza->setDodatki(Pizza::SER | Pizza::CEBULA | Pizza::KUKURYDZA | Pizza::OREGANO); }
};

class MexicoPizza : public PizzaBuilder
{
public:
    MexicoPizza() : PizzaBuilder() {}
    ~MexicoPizza() {}

    void buildNazwa() { pizza->setNazwa("Meksykańska"); };
    void buildCiasto() { pizza->setCiasto(true); }
    void buildSos() { pizza->setSos(Pizza::OLIWA); }
    void buildDodatki() { pizza->setDodatki(Pizza::SER | Pizza::PEPERONI | Pizza::SALAMI | Pizza::FASOLA); }
};

// Director
class Kelner
{
private:
    PizzaBuilder* pizzaBuilder;
public:
    Kelner() : pizzaBuilder(NULL) {}
    ~Kelner() { }

    void setPizzaBuilder(PizzaBuilder* b) { pizzaBuilder = b; }
    Pizza* getPizza() { return pizzaBuilder->getPizza(); }
    void constructPizza()
    {
        pizzaBuilder->createNewPizzaProduct();
        pizzaBuilder->buildNazwa();
        pizzaBuilder->buildCiasto();
        pizzaBuilder->buildSos();
        pizzaBuilder->buildDodatki();
    }
};


int main()
{
    Kelner kelner;

    HawajskaPizza hawajskaPizza;
    kelner.setPizzaBuilder (&hawajskaPizza);
    kelner.constructPizza();
    Pizza* pizza = kelner.getPizza();
    pizza->showPizza();

    OstraPizza ostraPizza;
    kelner.setPizzaBuilder(&ostraPizza);
    kelner.constructPizza();
    pizza = kelner.getPizza();
    pizza->showPizza();

    MexicoPizza mexicoPizza;
    kelner.setPizzaBuilder(&mexicoPizza);
    kelner.constructPizza();
    kelner.getPizza()->setDodatkiNowe(Pizza::KURCZAK);
    pizza = kelner.getPizza();
    pizza->showPizza();

    return EXIT_SUCCESS;
}

Recenzja książki „Wzorce projektowe – elementy oprogramowania obiektowego wielokrotnego użytku”

Wzorce-Projektowe

O książce „Wzorce projektowe – elementy oprogramowania obiektowego wielokrotnego użytku” można powiedzieć, że powinna być wpisana w kanon każdego dobrego programisty. Pozycja ta jest cytowana przez wiele innych źródeł, a sami autorzy są nazywani „bandą czterech”. Jako pierwsi zapisali oni wszystkie dobre metody programowania w jednej książce, co przyniosło im znaczny rozgłos.

Książka zawiera szczegółowe omówienie wzorców projektowych podzielonych na trzy grupy:  wzorce konstrukcyjne, strukturalne i operacyjne.

Ocena

Książkę oceniam 8/10.

Uzasadnienie

Dlaczego wystawiam książce „tylko” 8/10? Jest napisana ciężkim, technicznym językiem. Pomimo, że niektóre opisywane metody różnią się znacznie stopniem trudności, to cała książka utrzymana jest w podobnym stylu. Poza tym wykorzystywany przez autorów kod jest trudny do zrozumienia dla początkujących programistów, a niekiedy odbiega od dobrej notacji (np. metody rozpoczynają się wielkimi literami). Pomimo tego pozycja stanowi fachową literaturę.

Rekomendacja

Książkę poleciłbym co najmniej średnio zaawansowanym programistom.

Recenzja książki „C++ i Qt – wprowadzenie do wzorców projektowych”

Qt wprowadzenie do wzorców projektowych

Książkę można byłoby zawrzeć w jednym zdaniu – jest to zdecydowanie najlepsza pozycja o Qt, którą czytałem. Choć zdjęcie wygląda, jakbym ją wypożyczył z biblioteki, gdzie była czytana co najmniej przez kilkadziesiąt osób, to jednak muszę przyznać, że jest to książka którą czytałem sam wielokrotnie, a jej wygląd wynika z wysokiej jakości treści i przykładów dobrych praktyk, do których wracałem.

„C++ i Qt wprowadzenie do wzorców projektowych” zawiera kompleksowe omówienie zasad tworzenia wysokiej jakości oprogramowania. Wyjaśnione są w niej najważniejsze wzorce projektowe, widgety, sposoby ich konstruowania, pokazany na przykładach język modelowania UML. Autorzy rekomendują też w większości narzędzia typu open source i podają wiele przykładów praktycznych.

ZALETY

Po przeczytaniu książki można wielokrotnie do niej wracać, ponieważ jest w niej tak dużo rozwiązań i solidnych przykładów, że mogłaby się  ona z powodzeniem nazywać biblią programisty.

REKOMENDACJA

Lekturę polecam osobom, które miały już styczność z programowaniem. Raczej co najmniej śrenio zaawansowanym programistom. Książkę mogę zarekomendować wszystkim, chcącym stać się lepszymi programistami C++/Qt.

OCENA

Bez zastanowienia: 10/10.

&nb