Фабрички метод је објектно-оријентисанипројектни узорак. Као и остали пројектни узорци креирања, бави се проблемима креирања објеката без навођења конкретне класе објекта који се креира. Фабрички метод решава овај проблем тако што дефинише посебну методу за креирање објеката, коју онда поткласе преклапају да одреде тачан подтип објекта (тј. изведену класу) који ће бити креиран. Општије, фабрички метод се често користи да означи било коју методу чија је основна сврха креирање објеката.
Дефиниција
Суштина фабричког метода је да „дефинише интерфејс за креирање објеката, али да остави поткласама да одлуче чије објекте креирају. Фабрички метод допушта класи да делегира стварање објекта поткласи.“[1]
Честе употребе
Фабрички метод се често користи у радним оквирима (енгл.frameworks) где кôд радног оквира треба да креира објекте којима је познат само надтип (који дефинише радни оквир), али не и конкретни подтип објекта који дефинише клијентски кôд који користи овај радни оквир.
Паралелне класе хијерархија често захтевају да објекти једне хијерархије креирају објеке из друге хијерархије.
Фабрички методи се често користе у test-driven развоју да би се класе лакше тестирале.
[2]
Претпоставимо да постоји класа Foo која креира други објекат класе Dangerous који се не може аутоматизовано тестирати јединичним тестовима (нпр. комуницира са продукционом базом). Тада се креирање објекта Dangerous ставља у виртуелну фабричку методу createDangerous() у класу Foo. За тестирање, прави се класа TestFoo са преклопљеном методом createDangerous() која креира и враћа лажни објекат FakeDangerous. Јединични тестови сада користе TestFoo да тестирају функционалност коју даје класа Foo без опасности и споредних ефеката који би настали да се користи објекат Dangerous.
Остале предности и варијанте
Иако је основна мотивација фабричког метода да дозволи поткласама да одлуче тип објекта које креирају, постоје и остале предности коришћења фабричког метода, од којих многе не зависе од наслеђивања класа. Отуда, честе се дефинишу „фабрички методи“ који уопште нису полиморфни и који служе за креирање објеката, а само да би се добиле ове остале предности. Такви методи су обично статички.
Описна имена
Фабрички метод може имати јасно име. Проблем је што у многим објектно-оријентисаним језицима, конструктор мора имати исто име као и класа у којој се налази, а то некад може довести до вишезначности ако постоји више од једног начина да се креира објекат. Фабрички методи немају оваква ограничења и могу имати описна имена. Као пример, када се комплексни бројеви креирају од два реална броја, та два броја могу да представљају или картезијанске или поларне координате, али коришћењем фабричке методе, значење је јасно (следећи примери су написани у програмском језику Јава):
classComplex{publicstaticComplexfromCartesian(doublereal,doubleimag){returnnewComplex(real,imag);}publicstaticComplexfromPolar(doublemodulus,doubleangle){returnnewComplex(modulus*cos(angle),modulus*sin(angle));}privateComplex(doublea,doubleb){//...}}Complexc=Complex.fromPolar(1,pi);// Isto kao i fromCartesian(-1, 0)
Када се фабрички методи користе да се овако разреше вишезначности, обично се конструктори стављају да буду приватни да би се на тај начин клијенти приморали да користе фабричке методе.
Енкапсулација
Фабрички методи енкапсулирају креирање објеката.
Ово може бити корисно када је процес креирања објекта јако сложен (нпр. зависи од подешавања у конфигурационим датотекама или од корисничког уноса).
Размотрићемо пример програма који чита слике и од њих прави мање сличице.
Програм подржава различите формате слика, представљене са по једном класом за читање сваког од формата:
Сваки пут када програм прочита слику, мора да, на основу неких информација из датотеке, креира читача за одговарајући формат. Ова логика се може енкапсулирати у фабрички метод:
Исечак кôда из претходног примера користи наредбу switch да споји формат слике са одговорајућом фабриком објеката. Исто тако се могао користити и неки вид мапирања, при чему би се наредба switch заменила са асоцијативним низом чији су индекси формати слике, а чланови фабрике објеката.
Ограничења
Постоје три ограничења везана за коришћење фабричког метода. Први је везан за рефакторисање постојећег кôда, а друга два за наслеђивање.
Прво ограничење је да рефакторисање постојеће класе, да би користила фабрике, нарушава постојеће клијенте те класе. На пример, да је класа Complex нека постојећа стандардна класа, имала би бројне клијенте који је користе на следећи начин:
Complexc=newComplex(-1,0);
Онда када схватимо да нам требају две различите фабрике, изменићемо класу као у кôду приказаном изнад. Међутим, пошто су нам сада конструктори приватни, постојећи кôд више не може да се компајлира.
Друго ограничење је да, пошто се овај пројектни узорак ослања на коришћење приватних конструктора, од класе која садржи овај узорак не може да се изведе нова класа. Свака класа мора да позове наслеђени конструктор, али ово се не може извести пошто је конструктор приватан.
Треће ограничење је да, ако и наследимо класу (нпр. стављањем да је конструктор заштићен -- опасно, али могуће), свака поткласа мора да обезбеди сопствене имплементације свих фабричких метода са истоветним потписима тих метода. На пример, ако је класа StrangeComplex изведена из класе Complex, онда уколико класа StrangeComplex не обезбеди своје имплементације свих фабричких метода, позивом StrangeComplex.fromPolar(1, pi) ће се добити инстанца класе Complex (надкласе) уместо очекиване инстанце поткласе.
Сва три проблема се могу избећи када би се програмски језици тако изменили да фабрике постану првокласни чланови језика.
[3]
Примери
Јава
abstractclassPizza{publicabstractintgetPrice();// uzima cenu}classHamAndMushroomPizzaextendsPizza{publicintgetPrice(){return850;}}classDeluxePizzaextendsPizza{publicintgetPrice(){return1050;}}classHawaiianPizzaextendsPizza{publicintgetPrice(){return1150;}}classPizzaFactory{publicenumPizzaType{HamMushroom,Deluxe,Hawaiian}publicstaticPizzacreatePizza(PizzaTypepizzaType){switch(pizzaType){caseHamMushroom:returnnewHamAndMushroomPizza();caseDeluxe:returnnewDeluxePizza();caseHawaiian:returnnewHawaiianPizza();}thrownewIllegalArgumentException("Tip pice "+pizzaType+" nije prepoznat.");}}classPizzaLover{/* * Pravi sve dostupne pice i ispisuje njihove cene */publicstaticvoidmain(Stringargs[]){for(PizzaFactory.PizzaTypepizzaType:PizzaFactory.PizzaType.values()){System.out.println("Cena pice "+pizzaType+" je "+PizzaFactory.createPizza(pizzaType).getPrice());}}}// Izlaz:// Cena pice HamMushroom je 850// Cena pice Deluxe je 1050// Cena pice Hawaiian je 1150
C#
publicinterfaceIPizza{decimalPrice{get;}}publicclassHamAndMushroomPizza:IPizza{decimalIPizza.Price{get{return8.5m;}}}publicclassDeluxePizza:IPizza{decimalIPizza.Price{get{return10.5m;}}}publicclassHawaiianPizza:IPizza{decimalIPizza.Price{get{return11.5m;}}}publicclassPizzaFactory{publicenumPizzaType{HamMushroom,Deluxe,Hawaiian}publicstaticIPizzaCreatePizza(PizzaTypepizzaType){IPizzaret=null;switch(pizzaType){casePizzaType.HamMushroom:ret=newHamAndMushroomPizza();break;casePizzaType.Deluxe:ret=newDeluxePizza();break;casePizzaType.Hawaiian:ret=newHawaiianPizza();break;default:thrownewArgumentException("Tip pice "+pizzaType+" nije prepoznat.");}returnret;}}publicclassPizzaLover{publicstaticvoidMain(string[]args){Dictionary<PizzaFactory.PizzaType,IPizza>pizzas=newDictionary<PizzaFactory.PizzaType,IPizza>();foreach(PizzaFactory.PizzaTypepizzaTypeinEnum.GetValues(typeof(PizzaFactory.PizzaType))){pizzas.Add(pizzaType,PizzaFactory.CreatePizza(pizzaType));}foreach(PizzaFactory.PizzaTypepizzaTypeinpizzas.Keys){System.Console.WriteLine("Cena pice {0} je {1}",pizzaType,pizzas[pizzaType].Price);}}}// Izlaz:// Cena pice HamMushroom je 8.5// Cena pice Deluxe je 10.5// Cena pice Hawaiian je 11.5
Пајтон
## Pica#classPizza:def__init__(self):self.price=Nonedefget_price(self):returnself.priceclassHamAndMushroomPizza(Pizza):def__init__(self):self.price=8.5classDeluxePizza(Pizza):def__init__(self):self.price=10.5classHawaiianPizza(Pizza):def__init__(self):self.price=11.5## Fabrika pica#classPizzaFactory:@staticmethoddefcreate_pizza(pizza_type):ifpizza_type=='HamMushroom':returnHamAndMushroomPizza()elifpizza_type=='Deluxe':returnDeluxePizza()elifpizza_type=='Hawaiian':returnHawaiianPizza()if__name__=='__main__':forpizza_typein('HamMushroom','Deluxe','Hawaiian'):print'Cena pice {0} je {1}'.format(pizza_type,PizzaFactory.create_pizza(pizza_type).get_price())
PHP
<?phpabstractclassPizza{protected$_price;publicfunctiongetPrice(){return$this->_price;}}classHamAndMushroomPizzaextendsPizza{protected$_price=8.5;}classDeluxePizzaextendsPizza{protected$_price=10.5;}classHawaiianPizzaextendsPizza{protected$_price=11.5;}classPizzaFactory{publicstaticfunctioncreatePizza($type){$baseClass='Pizza';$targetClass=ucfirst($type).$baseClass;if(class_exists($targetClass)&&is_subclass_of($targetClass,$baseClass))returnnew$targetClass;elsethrownewException("Tip pice nije prepoznat.");}}$pizzas=array('HamAndMushroom','Deluxe','Hawaiian');foreach($pizzasas$p){printf("Cena pice %s je %01.2f".PHP_EOL,$p,PizzaFactory::createPizza($p)->getPrice());}// Izlaz:// Cena pice HamMushroom je 8.50// Cena pice Deluxe je 10.50// Cena pice Hawaiian je 11.50?>
Коришћења
У ADO.NET компонентама, IDbCommand.CreateParameter(језик: енглески) је пример коришћења фабричког метода који повезује паралелне хијерархије класа.
У Qt апликативном интерфејсу, QMainWindow::createPopupMenu(језик: енглески) је фабричка метода декларисана у радном оквиру која се може преклопити у клијентском кôду.
У Јави, у пакету javax.xml.parsersАрхивирано на сајту Wayback Machine (6. август 2009)(језик: енглески) се користи неколико фабрика, нпр. javax.xml.parsers.DocumentBuilderFactory или javax.xml.parsers.SAXParserFactory.
^Мајкл Федерс (Октобар 2004). Working Effectively with Legacy Code. ISBN978-0131177055.Проверите вредност парамет(а)ра за датум: |date= (помоћ)
^Agerbo, Aino; Agerbo, Cornils (1998). „How to preserve the benefits of design patterns”. Conference on Object Oriented Programming Systems Languages and Applications. Vancouver, British Columbia, Canada: ACM: 134—143. ISBN978-1-58113-005-8.Непознати параметар |fist= игнорисан (помоћ)
Литература
Федерс, Мајкл (Октобар 2004). Working Effectively with Legacy Code. ISBN978-0131177055.Проверите вредност парамет(а)ра за датум: |date= (помоћ)