Abstraktní továrna
Abstraktní továrna (anglicky Abstract factory) je softwarový návrhový vzor, který poskytuje možnost "obalit" skupinu individuálních konkrétních továren, které mají společné téma. Obvyklé použití vzoru spočívá ve vytvoření konkrétní implementace abstraktní továrny, za jejíhož použití vyrábí potřebné objekty. Klient se nestará o to, které konkrétní objekty vyrábí která konkrétní továrna, protože používá jednotné rozhraní abstraktní továrny. Vzor odděluje detaily implementace skupiny objektů od jejich obecného používání.
Příkladem abstraktní továrny může být DocumentCreator
, který poskytuje interface pro vytváření množství produktů(např. createLetter()
a createResume()
). Systém pak bude obsahovat několik odvozených konkrétních verzí DocumentCreatoru
jako například FancyDocumentCreator
nebo ModernDocumentCreator
. Každá z těchto tříd bude mít jinou implementaci metod createLetter()
a createResume()
, které budou vytvářet odpovídající objekty (např. FancyLetter
nebo ModernResume
). Každý z takto vytvořených objektů bude instancí třídy, která bude dědit od společného předka (např. Letter
nebo Resume
). Klient vždy dostane správnou instanci DocumentCreatoru
s odpovídajícími továrními metodami. Všechny objekty vytvářené pomocí DocumentCreatoru
budou pocházet z jednoho tématu (Fancy nebo Modern). Jediné, co potřebuje klient znát, je rozhraní společného předka produkovaných objektů (Letter
nebo Resume
), nepotřebuje vědět přesnou verzi (Fancy nebo Modern).
Továrna je místem v kódu, kde dochází k vytváření nových objektů. Cílem továrny je odstínit klienta od vytváření těchto objektů. Továrny umožňují používání nově vytvořených tříd, stačí pouze dodržet podmínku dědění od rodičovských tříd produktů továrny.
Implementace vzoru umožňuje vyměňovat konkrétní implementace bez změny kódu. Změna použité továrny a tím i změna toho, jaké se budou vytvářet objekty, je proveditelná dokonce za běhu.
Definice
Základem návrhového vzoru abstraktní továrna je poskytnutí interface pro vytváření rodin souvisejících objektů, aniž by bylo třeba specifikovat jejich konkrétní třídu.
Použití
Jednotlivé továrny určují přímo typy objektů, které vytvářejí. Ve většině jazyků se tak obvykle děje použitím klíčového slova new. Nicméně továrna vrací abstraktní ukazatel na vytvořený konkrétní objekt.
Díky tomu není klientský kód zatěžován vytvářením objektů, ale o vytvoření požádá objekt továrny, která vrací objekt požadovaného abstraktního typu pomocí ukazatele na tento objekt.
Protože továrna vrací pouze abstraktní ukazatel, klientský kód nezná aktuální konkrétní typ objektu, který byl právě vytvořen. Nicméně abstraktní továrna musí znát konkrétní typ objektů, které mají být vytvářeny. To se může dozvědět například z konfiguračního souboru. Důsledkem toho je:
- Klientský kód nemá žádné povědomí o konkrétních typech objektů a proto nemusí vkládat header soubory (C) nebo deklarace tříd vztahujících se ke konkrétním typům. Klientský kód se musí vypořádat pouze s deklarací abstraktního předka vytvářených objektů, protože pouze přes interface abstraktního předka klient přistupuje k vytvořeným objektům.
- Přidání nové rodiny konkrétních typů je provedeno úpravou klientského kódu tak, aby používal jinou továrnu. Tato modifikace je obvykle přidání jednoho či dvou řádků kódu do jednoho souboru. (Různé továrny vytvářejí objekty různých konkrétních typů, ale vrací ukazatel na stejný abstraktní typ, to chrání klientský kód před nutností zásahu při přidání nového typu.) Tento přístup je mnohem jednodušší než modifikovat klientský kód tak, aby mohl používat nový typ všude, kde je tento typ vytvářen. Muselo by být také zajištěno správné vložení header souborů nebo deklarací tříd. Obvykle se konkrétní továrny vytvářejí jako singletony, díky čemuž je pak jejich případná výměna velmi jednoduchá.
Diagram tříd
Metoda createButton
v interfacu GuiFactory
vrací objekty typu Button
. Jaká implementace Button
u bude vrácena, záleží na tom jaké implementace GuiFactory
obslouží volání metody.
Pro jednoduchost diagramu je jsou zobrazeny pouze třídy pro vytváření jednoho typu objektů (tlačítek). Jednoduše lze však doplnit abstraktního předka například pro kurzor a jeho konkrétní implementace WinCursor a OSXCursor.
Příklady
Výstupem příkladů by mělo být buďto "I'm a WinButton" nebo "I'm an OSXButton" podle toho, jaký druh továrny byl použit. Povšimněte si, že aplikace nemá tušení, jaký druh GUIFactory dostane, ani jaký druh tlačítka tato továrna vytvoří.
Java
/* GUIFactory example -- */
interface GUIFactory {
public Button createButton();
}
class WinFactory implements GUIFactory {
public Button createButton() {
return new WinButton();
}
}
class OSXFactory implements GUIFactory {
public Button createButton() {
return new OSXButton();
}
}
interface Button {
public void paint();
}
class WinButton implements Button {
public void paint() {
System.out.println("I'm a WinButton");
}
}
class OSXButton implements Button {
public void paint() {
System.out.println("I'm an OSXButton");
}
}
class Application {
public Application(GUIFactory factory) {
Button button = factory.createButton();
button.paint();
}
}
public class ApplicationRunner {
public static void main(String[] args) {
new Application(createOsSpecificFactory());
}
public static GUIFactory createOsSpecificFactory() {
int sys = readFromConfigFile("OS_TYPE");
if (sys == 0) {
return new WinFactory();
} else {
return new OSXFactory();
}
}
}
C#
/* GUIFactory example -- */
using System;
using System.Configuration;
namespace AbstractFactory
{
public interface IButton
{
void Paint();
}
public interface IGUIFactory
{
IButton CreateButton();
}
public class OSXButton : IButton
{
public void Paint()
{
System.Console.WriteLine("I'm an OSXButton");
}
}
public class WinButton : IButton
{
public void Paint()
{
System.Console.WriteLine("I'm a WinButton");
}
}
public class OSXFactory : IGUIFactory
{
IButton IGUIFactory.CreateButton()
{
return new OSXButton();
}
}
public class WinFactory : IGUIFactory
{
IButton IGUIFactory.CreateButton()
{
return new WinButton();
}
}
public class Application
{
public Application(IGUIFactory factory)
{
IButton button = factory.CreateButton();
button.Paint();
}
}
public class ApplicationRunner
{
static IGUIFactory CreateOsSpecificFactory()
{
// Contents of App.Config associated with this C# project
//<?xml version="1.0" encoding="utf-8" ?>
//<configuration>
// <appSettings>
// <!-- Uncomment either Win or OSX OS_TYPE to test -->
// <add key="OS_TYPE" value="Win" />
// <!-- <add key="OS_TYPE" value="OSX" /> -->
// </appSettings>
//</configuration>
string sysType = ConfigurationSettings.AppSettings["OS_TYPE"];
if (sysType == "Win")
{
return new WinFactory();
}
else
{
return new OSXFactory();
}
}
static void Main(string[] args)
{
new Application(CreateOsSpecificFactory());
Console.ReadLine();
}
}
}
C++
/* GUIFactory example -- */
#include<iostream>
using namespace std;
class Button {
public:
virtual void paint() = 0;
virtual ~Button(){
}
};
class WinButton: public Button {
public:
void paint() {
cout << "I'm a WinButton";
}
};
class OSXButton: public Button {
public:
void paint() {
cout << "I'm an OSXButton";
}
};
class GUIFactory {
public:
virtual Button * createButton() = 0;
virtual ~GUIFactory(){
}
};
class WinFactory: public GUIFactory {
public:
Button * createButton() {
return new WinButton();
}
~WinFactory(){
}
};
class OSXFactory: public GUIFactory {
public:
Button * createButton() {
return new OSXButton();
}
~OSXFactory(){
}
};
class Application {
public:
Application(GUIFactory * factory) {
Button * button = factory->createButton();
button->paint();
delete button;
delete factory;
}
};
GUIFactory * createOsSpecificFactory() {
int sys;
cout << endl << "Enter OS Type(0 - Win, 1 - OSX): ";
cin >> sys;
if (sys == 0) {
return new WinFactory();
} else {
return new OSXFactory();
}
}
int main(int argc, char **argv) {
Application * newApplication = new Application(createOsSpecificFactory());
delete newApplication;
return 0;
}
Objective-C
/* GUIFactory example -- */
#import <Foundation/Foundation.h>
@protocol Button
- (void)paint;
@end
@interface WinButton : NSObject <Button>
@end
@interface OSXButton : NSObject <Button>
@end
@protocol GUIFactory
- (id)createButton;
@end
@interface WinFactory : NSObject <GUIFactory>
@end
@interface OSXFactory : NSObject <GUIFactory>
@end
@interface Application : NSObject
- (id)initWithGUIFactory:(id)factory;
+ (id)createOsSpecificFactory:(int)type;
@end
@implementation WinButton
- (void)paint {
NSLog(@"I am a WinButton.");
}
@end
@implementation OSXButton
- (void)paint {
NSLog(@"I am a OSXButton.");
}
@end
@implementation WinFactory
- (id)createButton {
return [[[WinButton alloc] init] autorelease];
}
@end
@implementation OSXFactory
- (id)createButton {
return [[[OSXButton alloc] init] autorelease];
}
@end
@implementation Application
- (id)initWithGUIFactory:(id)factory {
if (self = [super init]) {
id button = [factory createButton];
[button paint];
}
return self;
}
+ (id)createOsSpecificFactory:(int)type {
if (type == 0) {
return [[[WinFactory alloc] init] autorelease];
} else {
return [[[OSXFactory alloc] init] autorelease];
}
}
@end
int main(int argc, char* argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[Application alloc] initWithGUIFactory:[Application createOsSpecificFactory:0]];// 0 is WinButton
[pool release];
return 0;
}