Fabrički metod (projektni uzorak)

Fabrički metod je objektno-orijentisani projektni uzorak. Kao i ostali projektni uzorci kreiranja, bavi se problemima kreiranja objekata bez navođenja konkretne klase objekta koji se kreira. Fabrički metod rešava ovaj problem tako što definiše posebnu metodu za kreiranje objekata, koju onda potklase preklapaju da odrede tačan podtip objekta (tj. izvedenu klasu) koji će biti kreiran. Opštije, fabrički metod se često koristi da označi bilo koju metodu čija je osnovna svrha kreiranje objekata.

Fabrički metod u UML-u

Definicija uredi

Suština fabričkog metoda je da „definiše interfejs za kreiranje objekata, ali da ostavi potklasama da odluče čije objekte kreiraju. Fabrički metod dopušta klasi da delegira stvaranje objekta potklasi.“[1]

Česte upotrebe uredi

Fabrički metod se često koristi u radnim okvirima (engl. frameworks) gde kôd radnog okvira treba da kreira objekte kojima je poznat samo nadtip (koji definiše radni okvir), ali ne i konkretni podtip objekta koji definiše klijentski kôd koji koristi ovaj radni okvir.

Paralelne klase hijerarhija često zahtevaju da objekti jedne hijerarhije kreiraju objeke iz druge hijerarhije.

Fabrički metodi se često koriste u test-driven razvoju da bi se klase lakše testirale. [2] Pretpostavimo da postoji klasa Foo koja kreira drugi objekat klase Dangerous koji se ne može automatizovano testirati jediničnim testovima (npr. komunicira sa produkcionom bazom). Tada se kreiranje objekta Dangerous stavlja u virtuelnu fabričku metodu createDangerous() u klasu Foo. Za testiranje, pravi se klasa TestFoo sa preklopljenom metodom createDangerous() koja kreira i vraća lažni objekat FakeDangerous. Jedinični testovi sada koriste TestFoo da testiraju funkcionalnost koju daje klasa Foo bez opasnosti i sporednih efekata koji bi nastali da se koristi objekat Dangerous.

Ostale prednosti i varijante uredi

Iako je osnovna motivacija fabričkog metoda da dozvoli potklasama da odluče tip objekta koje kreiraju, postoje i ostale prednosti korišćenja fabričkog metoda, od kojih mnoge ne zavise od nasleđivanja klasa. Otuda, česte se definišu „fabrički metodi“ koji uopšte nisu polimorfni i koji služe za kreiranje objekata, a samo da bi se dobile ove ostale prednosti. Takvi metodi su obično statički.

Opisna imena uredi

Fabrički metod može imati jasno ime. Problem je što u mnogim objektno-orijentisanim jezicima, konstruktor mora imati isto ime kao i klasa u kojoj se nalazi, a to nekad može dovesti do višeznačnosti ako postoji više od jednog načina da se kreira objekat. Fabrički metodi nemaju ovakva ograničenja i mogu imati opisna imena. Kao primer, kada se kompleksni brojevi kreiraju od dva realna broja, ta dva broja mogu da predstavljaju ili kartezijanske ili polarne koordinate, ali korišćenjem fabričke metode, značenje je jasno (sledeći primeri su napisani u programskom jeziku Java):

class Complex
{
     public static Complex fromCartesian(double real, double imag) {
         return new Complex(real, imag);
     }
 
     public static Complex fromPolar(double modulus, double angle) {
         return new Complex(modulus * cos(angle), modulus * sin(angle));
     }
  
     private Complex(double a, double b) {
         //...
     }
}
  
 Complex c = Complex.fromPolar(1, pi); // Isto kao i fromCartesian(-1, 0)

Kada se fabrički metodi koriste da se ovako razreše višeznačnosti, obično se konstruktori stavljaju da budu privatni da bi se na taj način klijenti primorali da koriste fabričke metode.

Enkapsulacija uredi

Fabrički metodi enkapsuliraju kreiranje objekata. Ovo može biti korisno kada je proces kreiranja objekta jako složen (npr. zavisi od podešavanja u konfiguracionim datotekama ili od korisničkog unosa).

Razmotrićemo primer programa koji čita slike i od njih pravi manje sličice. Program podržava različite formate slika, predstavljene sa po jednom klasom za čitanje svakog od formata:

public interface ImageReader {
    public DecodedImage getDecodedImage();
}
 
public class GifReader implements ImageReader {
    public DecodedImage getDecodedImage() {
        return decodedImage;
    }
}
 
public class JpegReader implements ImageReader {
    // ....
}

Svaki put kada program pročita sliku, mora da, na osnovu nekih informacija iz datoteke, kreira čitača za odgovarajući format. Ova logika se može enkapsulirati u fabrički metod:

public class ImageReaderFactory {
    public static ImageReader getImageReader(InputStream is) {
        int imageType = determineImageType(is);

        switch(imageType) {
            case ImageReaderFactory.GIF:
                return new GifReader(is);
            case ImageReaderFactory.JPEG:
                return new JpegReader(is);
            // etc.
        }
    }
}

Isečak kôda iz prethodnog primera koristi naredbu switch da spoji format slike sa odgovorajućom fabrikom objekata. Isto tako se mogao koristiti i neki vid mapiranja, pri čemu bi se naredba switch zamenila sa asocijativnim nizom čiji su indeksi formati slike, a članovi fabrike objekata.

Ograničenja uredi

Postoje tri ograničenja vezana za korišćenje fabričkog metoda. Prvi je vezan za refaktorisanje postojećeg kôda, a druga dva za nasleđivanje.

  • Prvo ograničenje je da refaktorisanje postojeće klase, da bi koristila fabrike, narušava postojeće klijente te klase. Na primer, da je klasa Complex neka postojeća standardna klasa, imala bi brojne klijente koji je koriste na sledeći način:
Complex c = new Complex(-1, 0);
Onda kada shvatimo da nam trebaju dve različite fabrike, izmenićemo klasu kao u kôdu prikazanom iznad. Međutim, pošto su nam sada konstruktori privatni, postojeći kôd više ne može da se kompajlira.
  • Drugo ograničenje je da, pošto se ovaj projektni uzorak oslanja na korišćenje privatnih konstruktora, od klase koja sadrži ovaj uzorak ne može da se izvede nova klasa. Svaka klasa mora da pozove nasleđeni konstruktor, ali ovo se ne može izvesti pošto je konstruktor privatan.
  • Treće ograničenje je da, ako i nasledimo klasu (npr. stavljanjem da je konstruktor zaštićen -- opasno, ali moguće), svaka potklasa mora da obezbedi sopstvene implementacije svih fabričkih metoda sa istovetnim potpisima tih metoda. Na primer, ako je klasa StrangeComplex izvedena iz klase Complex, onda ukoliko klasa StrangeComplex ne obezbedi svoje implementacije svih fabričkih metoda, pozivom StrangeComplex.fromPolar(1, pi) će se dobiti instanca klase Complex (nadklase) umesto očekivane instance potklase.

Sva tri problema se mogu izbeći kada bi se programski jezici tako izmenili da fabrike postanu prvoklasni članovi jezika. [3]

Primeri uredi

Java uredi

abstract class Pizza {
    public abstract int getPrice(); // uzima cenu
}

class HamAndMushroomPizza extends Pizza {
    public int getPrice() {
        return 850;
    }
}

class DeluxePizza extends Pizza {
    public int getPrice() {
        return 1050;
    }
}

class HawaiianPizza extends Pizza {
    public int getPrice() {
        return 1150;
    }
}

class PizzaFactory {
    public enum PizzaType {
        HamMushroom,
        Deluxe,
        Hawaiian
    }

    public static Pizza createPizza(PizzaType pizzaType) {
        switch (pizzaType) {
            case HamMushroom:
                return new HamAndMushroomPizza();
            case Deluxe:
                return new DeluxePizza();
            case Hawaiian:
                return new HawaiianPizza();
        }
        throw new IllegalArgumentException("Tip pice " + pizzaType + " nije prepoznat.");
    }
}

class PizzaLover {
    /*
     * Pravi sve dostupne pice i ispisuje njihove cene
     */
    public static void main (String args[]) {
        for (PizzaFactory.PizzaType pizzaType : 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# uredi

public interface IPizza
{
    decimal Price { get; }
}

public class HamAndMushroomPizza : IPizza
{
    decimal IPizza.Price
    {
        get
        {
            return 8.5m;
        }
    }
}

public class DeluxePizza : IPizza
{
    decimal IPizza.Price
    {
        get
        {
            return 10.5m;
        }
    }
}

public class HawaiianPizza : IPizza
{
    decimal IPizza.Price
    {
        get
        {
            return 11.5m;
        }
    }
}

public class PizzaFactory
{
    public enum PizzaType
    {
        HamMushroom,
        Deluxe,
        Hawaiian
    }

    public static IPizza CreatePizza(PizzaType pizzaType)
    {
        IPizza ret = null;

        switch (pizzaType)
        {
            case PizzaType.HamMushroom:
                ret = new HamAndMushroomPizza();

                break;
            case PizzaType.Deluxe:
                ret = new DeluxePizza();

                break;
            case PizzaType.Hawaiian:
                ret = new HawaiianPizza();

                break;
            default:
                throw new ArgumentException("Tip pice " + pizzaType + " nije prepoznat.");
        }

        return ret;
    }
}

public class PizzaLover
{
    public static void Main(string[] args)
    {
        Dictionary<PizzaFactory.PizzaType, IPizza> pizzas = new Dictionary<PizzaFactory.PizzaType, IPizza>();
        
        foreach (PizzaFactory.PizzaType pizzaType in Enum.GetValues(typeof(PizzaFactory.PizzaType)))
        {
            pizzas.Add(pizzaType, PizzaFactory.CreatePizza(pizzaType));
        }
        
        foreach (PizzaFactory.PizzaType pizzaType in pizzas.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

Pajton uredi

#
# Pica
#
class Pizza:
    def __init__(self):
        self.price = None
    
    def get_price(self):
        return self.price
        
class HamAndMushroomPizza(Pizza):
    def __init__(self):
        self.price = 8.5
    
class DeluxePizza(Pizza):
    def __init__(self):
        self.price = 10.5
  
class HawaiianPizza(Pizza):
    def __init__(self):
        self.price = 11.5
#
# Fabrika pica
#
class PizzaFactory:
    @staticmethod
    def create_pizza(pizza_type):
        if pizza_type == 'HamMushroom':
            return HamAndMushroomPizza()
        elif pizza_type == 'Deluxe':
            return DeluxePizza()
        elif pizza_type == 'Hawaiian':
            return HawaiianPizza()
 
if __name__ == '__main__':
    for pizza_type in ('HamMushroom', 'Deluxe', 'Hawaiian'):
        print 'Cena pice {0} je {1}'.format(pizza_type, PizzaFactory.create_pizza(pizza_type).get_price())

PHP uredi

<?php

abstract class Pizza
{
    protected $_price;
    public function getPrice()
    {
        return $this->_price;
    }
}
 
class HamAndMushroomPizza extends Pizza
{
    protected $_price = 8.5;
}
 
class DeluxePizza extends Pizza
{
    protected $_price = 10.5;
}
 
class HawaiianPizza extends Pizza
{
    protected $_price = 11.5;
}
 
class PizzaFactory
{
    public static function createPizza($type)
    {
        $baseClass = 'Pizza';
        $targetClass = ucfirst($type).$baseClass;
        
        if (class_exists($targetClass) && is_subclass_of($targetClass, $baseClass))
            return new $targetClass;
        else
            throw new Exception("Tip pice nije prepoznat.");
    }
}

$pizzas = array('HamAndMushroom','Deluxe','Hawaiian');
foreach($pizzas as $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

?>

Korišćenja uredi

Vidi još uredi

Reference uredi

  1. ^ Gang Of Four
  2. ^ Majkl Feders (Oktobar 2004). Working Effectively with Legacy Code. ISBN 978-0131177055.  Proverite vrednost paramet(a)ra za datum: |date= (pomoć)
  3. ^ 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. ISBN 978-1-58113-005-8.  Nepoznati parametar |fist= ignorisan (pomoć)

Literatura uredi

  • Feders, Majkl (Oktobar 2004). Working Effectively with Legacy Code. ISBN 978-0131177055.  Proverite vrednost paramet(a)ra za datum: |date= (pomoć)