Inna twórczość > C, C++
AbstractFactory - zbyt mało abstrakcji
Wonski:
Cześć,
mam problem w zrozumieniu wzorca projektowego: Fabryka Abstrakcyjna.
Może zanim przejdę do problemu, przedstawię diagram UML i implementacje tego diagramu za pomocą kodu:
Diagram (troche spory rozmiar)
Spoiler
Implementacja:
Spoiler
Pierwsza rodzina produktów wraz z interfejsem
Spoiler
--- Kod: ---// Product Family A
class AbstractProductA
{
public:
virtual void process() = 0;
};
class Product_A1 : public AbstractProductA
{
public:
virtual void process()
{
cout << "Product_A1 from Family_A" << endl;
}
};
class Product_A2 : public AbstractProductA
{
public:
virtual void process()
{
cout << "Product_A2 from Family_A" << endl;
}
};
--- Koniec kodu ---
druga rodzina produktów wraz z interfejsem
Spoiler
--- Kod: ---// Product Family B
class AbstractProductB
{
public:
virtual void process() = 0;
};
class Product_B1 : public AbstractProductB
{
public:
virtual void process()
{
cout << "Product_B1 from Family_B" << endl;
}
};
class Product_B2 : public AbstractProductB
{
public:
virtual void process()
{
cout << "Product_B2 from Family_B" << endl;
}
};
--- Koniec kodu ---
fabryki konkretne wraz z interfejsem
Spoiler
--- Kod: ---class AbstractFactory
{
public:
virtual AbstractProductA * CreateProductA() = 0;
virtual AbstractProductB * CreateProductB() = 0;
};
class ConcreteFactory1 : public AbstractFactory
{
public:
AbstractProductA * CreateProductA()
{
return new Product_A1;
};
AbstractProductB * CreateProductB()
{
return new Product_B1;
};
};
class ConcreteFactory2 : public AbstractFactory
{
public:
AbstractProductA * CreateProductA()
{
return new Product_A2;
}
AbstractProductB * CreateProductB()
{
return new Product_B2;
}
};
--- Koniec kodu ---
klient
Spoiler
--- Kod: ---//client
class FinalProduct
{
AbstractProductA * _product_A;
AbstractProductB * _product_B;
public:
void process()
{
this->_product_A->process();
this->_product_B->process();
}
void build(AbstractFactory & _helper)
{
this->_product_A = _helper.CreateProductA();
this->_product_B = _helper.CreateProductB();
}
};
--- Koniec kodu ---
A oto kod za pomoca ktorego testowalem ta implementacje:
Spoiler
--- Kod: ---int main()
{
FinalProduct c1;
ConcreteFactory1 _obj;
c1.build(_obj);
c1.process();
ConcreteFactory2 _obj2;
c1.build(_obj2);
c1.process();
system("PAUSE");
return 0;
}
--- Koniec kodu ---
Jak widzimy nic szczególnego. Ot prosta implementacja fabryki abstrakcyjnej.
Jednak mnie zastanawia to czy dobrze ukryłem implementacje fabryk konkretnych. Z diagramu wynika (ten sam co z książki od GOF), ze klient nie powinien widzieć implementacji fabryk konkretnych.
Jeżeli zasłaniam implementacje przed użytkownikiem końcowym, tzn umieszczam w klasie reprezentującej klienta specjalnego switcha opartego na typie wyliczeniowym (albo jakichkolwiek stałych), to mam niezgodność z diagramem bo klasa klient zna implementacje fabryk konkretnych, ale plus jest taki, ze nie zna ich użytkownik końcowy.
Jeżeli zasłaniam implementacje przed klasa reprezentującą klienta, tzn korzystam z interfejsu fabryki abstrakcyjnej, to niby jest zgodność z diagramem, ale odpowiedzialność za znajomość implementacji fabryk konkretnych spada na użytkownika końcowego, który musi przecież jakoś zainicjować interfejs klasy reprezentującej klienta ( co widać w kodzie testującym).
W tych dwóch przypadkach jedyne co jest niewidoczne to produkty konkretne.Czy nie powinno być jednak tak, ze niewidoczne zostają również implementacje fabryk konkretnych?
Przecież ten kod lamie zasadę mówiącą by uzależniać kod od abstrakcji a nie od klas konkretnych.
Adanos:
Nie, czemu? Fabryka ma dostarczyć interfejs dla rodziny konkretnych obiektów. Do fabryki masz przekazywać konkretne obiekty, które mają interfejs danej fabryki. Bo skąd w innym wypadku fabryka miałaby wiedzieć, jakie produkty ma tworzyć?
Wonski:
Takie samo miałem odczucie gdy implementowałem Budowniczego (W sumie wzorce podobne, ale jednak to inny proces konstrukcji).
Tyle abstrakcji i na koniec i tak wszystkie klasy były widoczne dla użytkownika końcowego.
A czy nie można rozwiązać tego w bardziej dyskretny sposób... tzn ukryć przed użytkownikiem końcowym konieczność tworzenia instancji fabryk konkretnych?
Mam na myśli tworzenie instancji w switchu wewnątrz metody build klasy FinalProduct (taka jakby fabryka statyczna).
Na użytkownika końcowego spada tylko obowiązek znajomości typu enum poprzez którego dokonuje się konkretyzacja. Dodatkowo użytkownik nie musi dbać o prawidłowe zwalnianie pamięci zajętej przez fabryki konkretne (tak wiem, można przejść na inteligentne wskaźniki).
Jednak przez takie działanie tracimy na abstrakcji.
Dodając nowy produkt musimy nie tylko konfigurować nowa fabrykę konkretna ale i również klienta.
Co o tym sadzisz?
inż. Avallach:
Nie rób switcha po enumie. Wybieraj typ do stworzenia na podstawie danych które będą wykorzystane w procesie konstrukcji. Enum jest tutaj bardzo sztucznym rozwiązaniem które zdecydowanie nie powinno trafić do kodu produkcyjnego. Skoro masz typ który wylicza wszystkie możliwe implementacje i musisz z góry podawać o którą chodzi, to nie ma w tym właściwie żadnej prawdziwej abstrakcji. Równie dobrze mógłbyś od razu zawołać docelowy konstruktor.
Wonski:
--- Cytuj ---Wybieraj typ do stworzenia na podstawie danych które będą wykorzystane w procesie konstrukcji
--- Koniec cytatu ---
Masz na myśli budowniczego?
Av, miałem raczej na myśli coś w tym stylu:
--- Kod: ---enum EnumConcreteFactory
{
CONCRETE_FACTORY_1,
CONCRETE_FACTORY_2
};
--- Koniec kodu ---
--- Kod: ---//client
class FinalProduct
{
AbstractProductA * _product_A;
AbstractProductB * _product_B;
public:
void process()
{
this->_product_A->process();
this->_product_B->process();
}
void build(AbstractFactory & _helper)
{
this->_product_A = _helper.CreateProductA();
this->_product_B = _helper.CreateProductB();
}
void build(EnumConcreteFactory type_factory_helper)
{
switch (type_factory_helper)
{
case CONCRETE_FACTORY_1:
ConcreteFactory1 * concrete_factory_1_obj = new ConcreteFactory1;
this->_product_A = concrete_factory_1_obj->CreateProductA();
this->_product_B = concrete_factory_1_obj->CreateProductB();
break;
case CONCRETE_FACTORY_2:
ConcreteFactory2 * concrete_factory_2_obj = new ConcreteFactory2;
this->_product_A = concrete_factory_2_obj->CreateProductA();
this->_product_B = concrete_factory_2_obj->CreateProductB();
break;
default:
// wlasny wyjatek
throw UnexpectedException();
}
}
};
--- Koniec kodu ---
Dzięki temu rozwiązaniu, mogę zyskać trochę po stronie użytkownika końcowego.
Zamiast tego:
--- Kod: --- FinalProduct c1;
ConcreteFactory1 _obj;
c1.build(_obj);
c1.process();
--- Koniec kodu ---
mam tylko to (użytkownik w ogóle nie widzi implementacji fabryk konkretnych):
--- Kod: --- FinalProduct c1;
c1.build(CONCRETE_FACTORY_1);
c1.process();
--- Koniec kodu ---
No tylko kosztem dodatkowej pracy przy dodawaniu nowych produktów.
Czy to dobre rozwiązanie?
Nawigacja
[#] Następna strona
Idź do wersji pełnej