Wzorce projektowe
Transkrypt
Wzorce projektowe
Wzorce projektowe Informatyka, II stopień, studia stacjonarne wykład 3 Fabryka Abstrakcyjna (ang. Abstract Factory) Definicja: • Jeden z kreacyjnych wzorców projektowych (obiektowy), którego celem jest dostarczenie interfejsu do tworzenia różnych obiektów jednego typu (tej samej rodziny) bez specyfikowania ich konkretnych klas. • Wzorzec umożliwia jednemu obiektowi tworzenie różnych, powiązanych ze sobą, reprezentacji podobiektów określając ich typy podczas działania programu. Fabryka Abstrakcyjna Cel użycia: • Stosuje się ten wzorzec, kiedy zachodzi potrzeba skoordynowania procesów tworzenia rodzin obiektów. • Pozwala na wyodrębnienie reguł dotyczących tworzenia obiektów z obiektów użytkownika, które będą wykorzystywać tworzone obiekty. • Ułatwia przystosowywanie kodu źródłowego do pracy w zróżnicowanych środowiskach. System tworzy nową unikatową konkretną fabrykę (która z kolei tworzy nowy unikatowy produkt) dla każdego środowiska. Ze względu na korzystanie w kodzie wyłącznie z interfejsów, gwarantuje właściwą współpracę z konkretnymi środowiskami. Fabryka Abstrakcyjna • Fabryka abstrakcyjna różni się od Budowniczego, tym, że kładzie nacisk na tworzenie produktów z konkretnej rodziny, a Budowniczy kładzie nacisk na sposób tworzenia obiektów. Przykład użycia: • Aplikacja kliencka łącząca się ze zdalnym serwerem. Aplikacja ma być przenośna. Jednym z rozwiązań takiego problemu jest stworzenie fabryki, która będzie tworzyła odpowiednie obiekty w zależności od tego na jakiej platformie się znajduje. Fabryka Abstrakcyjna • Na diagramie klas: wzorzec zbudowany jest z kilku podstawowych klas. • Klasa Fabryka abstrakcyjna deklaruje abstrakcyjny interfejs umożliwiający tworzenie produktów. • Interfejs ten jest implementowany w Fabrykach konkretnych, które odpowiedzialne są za tworzenie konkretnych produktów. • Każda fabryka konkretnego produktu posiada także metodę wytwórczą tego produktu. Fabryka Abstrakcyjna class AbstractProductA { public: virtual ~AbstractProductA()=0; virtual void operation()=0; protected: AbstractProductA(); }; class AbstractProductB { public: virtual ~AbstractProductB()=0; virtual void operation()=0; protected: AbstractProductB(); }; class ProductA1:public AbstractProductA { public: ProductA1(); virtual void operation(); virtual ~ProductA1(); }; class ProductB1:public AbstractProductB { public: ProductB1(); virtual void operation(); virtual ~ProductB1(); }; class ProductA2:public AbstractProductA { public: ProductA2(); virtual void operation(); virtual ~ProductA2(); }; class ProductB2:public AbstractProductB { public: ProductB2(); virtual void operation(); virtual ~ProductB2(); }; class AbstractFactory { public: virtual ~AbstractFactory()=0; virtual AbstractProductA* CreateProductA()=0; virtual AbstractProductB* CreateProductB()=0; protected: AbstractFactory(); }; class ConcreteFactory1:public AbstractFactory { public: ConcreteFactory1(); ~ConcreteFactory1(); virtual AbstractProductA* CreateProductA(); virtual AbstractProductB* CreateProductB(); }; class ConcreteFactory2:public AbstractFactory { public: ConcreteFactory2(); ~ConcreteFactory2(); virtual AbstractProductA* CreateProductA(); virtual AbstractProductB* CreateProductB(); }; AbstractProductA::AbstractProductA() { cout <<"AbstractProductA..." << endl; } AbstractProductB::AbstractProductB() { cout <<"AbstractProductB..." << endl; } AbstractProductA::~AbstractProductA() { cout <<"~AbstractProductA..." << endl; } AbstractProductB::~AbstractProductB() { cout <<"~AbstractProductB..." << endl; } ProductA1::ProductA1() { cout <<"ProductA1..." << endl; } ProductB1::ProductB1() { cout <<"ProductB1..." << endl; } ProductA1::~ProductA1() { cout <<"~ProductA1..." << endl; } ProductB1::~ProductB1() { cout <<"~ProductB1..." << endl; } void ProductA1::operation() {} void ProductB1::operation() {} ProductA2::ProductA2() { cout <<"ProductA2..." << endl; } ProductB2::ProductB2() { cout <<"ProductB2..." << endl; } ProductA2::~ProductA2() { cout <<"~ProductA2..." << endl; } ProductB2::~ProductB2() { cout <<"~ProductB2..." << endl; } void ProductA2::operation() {} void ProductB2::operation() {} AbstractFactory::AbstractFactory() { cout <<"AbstractFactory..." << endl; } ConcreteFactory2::~ConcreteFactory2() { cout <<"~ConcreteFactory2..." << endl; } AbstractFactory::~AbstractFactory() { cout <<"~AbstractFactory..." << endl; } AbstractProductA* ConcreteFactory2::CreateProductA() { return new ProductA2(); } ConcreteFactory1::ConcreteFactory1() { cout <<"ConcreteFactory1..." << endl; } AbstractProductB* ConcreteFactory2::CreateProductB() { return new ProductB2(); } ConcreteFactory1::~ConcreteFactory1() { cout <<"~ConcreteFactory1..." << endl; } int main() { AbstractFactory* fa1 = new ConcreteFactory1(); AbstractProductA* a1 = fa1->CreateProductA(); AbstractProductB* b1 = fa1->CreateProductB(); AbstractProductA* ConcreteFactory1::CreateProductA() { return new ProductA1(); } AbstractFactory* fa2 = new ConcreteFactory2(); AbstractProductA* a2 = fa2->CreateProductA(); AbstractProductB* b2 = fa2->CreateProductB(); AbstractProductB* ConcreteFactory1::CreateProductB() { return new ProductB1(); } ConcreteFactory2::ConcreteFactory2() { cout <<"ConcreteFactory2..." << endl; } delete delete delete delete delete delete return } fa1; a1; b1; fa2; a2; b2; 0; Fabryka Abstrakcyjna Zalety: • możliwość ukrycia szczegółów implementacyjnych klas reprezentujących konkretny produkt - klient widzi tylko interfejs. Ukryciu ulegają także nazwy tych klas, co nie wymusza ich zapamiętywania i odizolowuje klienta od problemu określenia, do której klasy należy obiekt. • możliwość całkowitego ukrycia implementacji obiektów przed klientem. Klient widzi tylko interfejs i nie ma możliwości zajrzenia do kodu oraz to, że wymuszana jest spójność produktów. Fabryka Abstrakcyjna Wady: • trudność rozszerzania rodziny obiektów o nowe podobiekty. Wymusza to modyfikację klasy fabryki abstrakcyjnej oraz wszystkich obiektów, które są tworzone przez nią. • Gdy dany produkt nie realizuje powierzonych mu zadań zgodnie z oczekiwaniami, najczęściej należy dokonać zmiany interfejsu abstrakcyjnego produktu – może to być dosyć trudne, ponieważ wymaga zmodyfikowania definicji wszystkich konkretnych produktów. Prototyp (ang. Prototype) Definicja: • Jeden z kreacyjnych wzorców projektowych. Umożliwia tworzenie obiektów danej klasy (lub klas) z wykorzystaniem już istniejącego obiektu, zwanego prototypem. • Głównym celem tego wzorca jest uniezależnienie systemu od sposobu w jaki są tworzone, składane i reprezentowane w nim produkty. Prototyp • Używany głównie w celu konieczności utworzenia prawdziwej kopii (ang. truecopy) innej instancji w czasie wykonywania programu (przez dynamiczne ładowanie) – potrzeba użycia metody clone(). • Prawdziwa kopia to taka, w której skopiowany obiekt ma wszystkie swe pola identyczne z pierwowzorem. • Gdy używa się operatora new, wówczas pola obiektu mają wartości początkowe. Np. w systemie do przeprowadzania transakcji bankowych potrzebna jest taka kopia obiektu, która przechowa dane konta, wykona transakcje na tej kopii i zamieni tę kopię z oryginałem (potrzebna jest raczej metoda clone() niż operator new). Prototyp • Wzorzec Prototyp określa rodzaj obiektów do tworzenia za pomocą prototypowej instancji. Prototypy nowych produktów są często budowane przed pełną produkcją. • Prototyp - deklaruje interfejs klonowania się. • PrototypKonkretny - implementuje operację klonowania się. • Klient - tworzy nowy obiekt, prosząc prototyp o sklonowanie się. Współpraca: Klient prosi Prototyp o sklonowanie się. Prototyp class Prototype { protected: Prototype(); public: virtual Prototype* Clone() const=0; virtual ~Prototype(); }; class ConcretePrototype1:public Prototype { public: ConcretePrototype1(); ~ConcretePrototype1(); ConcretePrototype1(const ConcretePrototype1&); virtual Prototype* Clone() const; }; class ConcretePrototype2:public Prototype { public: ConcretePrototype2(); ~ConcretePrototype2(); ConcretePrototype2(const ConcretePrototype2&); virtual Prototype* Clone() const; }; Prototype::Prototype() { cout<<"Prototype"<<endl; } Prototype::~Prototype() { cout<<"~Prototype"<<endl; } ConcretePrototype1::ConcretePrototype1(const ConcretePrototype1& cp) { cout<<"ConcretePrototype1 copy"<<endl; } Prototype* ConcretePrototype1::Clone() const { return new ConcretePrototype1(*this); } ConcretePrototype2::ConcretePrototype2() { cout<<"ConcretePrototype2"<<endl; } ConcretePrototype2::~ConcretePrototype2() { cout<<"~ConcretePrototype2"<<endl; } ConcretePrototype2::ConcretePrototype2(const ConcretePrototype2& cp) { cout<<"ConcretePrototype2 copy"<<endl; } Prototype* ConcretePrototype2::Clone() const { return new ConcretePrototype2(*this); } int main() { Prototype* p1 = new ConcretePrototype1(); Prototype* p2 = p1->Clone(); ConcretePrototype1::ConcretePrototype1() { cout<<"ConcretePrototype1"<<endl; } Prototype* p3 = new ConcretePrototype2(); Prototype* p4 = p3->Clone(); delete p1; delete p2; ConcretePrototype1::~ConcretePrototype1() { cout<<"~ConcretePrototype1"<<endl; } delete p3; delete p4; return 0; } Prototyp Zalety: • Prototyp może skrócić czas tworzenia obiektów. • Można zainstalować nowy konkretny produkt w ramach wytwórni przez proste przekazanie tej wytwórni prototypu (już w czasie wykonania programu). Usuwanie produktów jest równie łatwe. Wady: • Konieczność interpretowania metody clone() – niekiedy może być dość trudne. • Należy rozważyć problem „głębokiej” lub „płytkiej” kopii, tzn. czy kopia powinna się sprowadzać do referencji, czy należy klonować cały wskazany obiekt • Metoda klonująca powinna niekiedy działać jak konstruktor – inicjalizować pewne pola wartościami domyślnymi, np. klon pola listy zwykle nie może się znaleźć na liście.