Kihagyás

Gyártási minták

Az előadás videója elérhető itt.

Gyártási minták

A gyártási minták a példányosítás folyamatában segítenek oly módon, hogy maga a folyamat elkülönüljön a példányosítás módjától. Az hogy mi jön létre, hogyan jön létre, ki hozza létre az nem fontos, csak a létrehozott objektum.

A gyártási minták közül három mintát fogunk alaposabban átnézni. Ezek közül a Factory Method egy általánosan használható minta, az Abstract Factory egy flexibilis, de nagyobb osztályhierarchiát hoz létre a céljai érdekében, a Singleton pedig egy olyan minta, ami lehetővé teszi, hogy egy osztályból csupán egyetlen példány keletkezzen, amely példányt mindenki, akinek szükséges, el tud érni globálisan.

Factory Method

A Factory Method lényege, hogy úgy hozunk létre egy objektumot, hogy a klienst nem terheljük a létrehozás logikájával. Az ősosztály definiálja az összes szokásos és általános viselkedést, de magát a létrehozási részleteket átadja az alosztályoknak. Motiváció lehet, ha a kódban számos ugyanolyan példányosító utasítása van, és ezt szeretnénk egyszerűsíteni.

factory method

Factory Method példa

A példánkban legyen az a feladat, hogy létre kell hozni egy listát, és ahhoz hozzá kell adni pár elemet, csakhogy ne egy üres listával legyen dolgunk.

Ezt hagyományosan a következőképp tesszük meg:

import java.util.ArrayList;
import java.util.List;
public class FactoryMethodsExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("JavaFX");
        list.add("Spring");
        list.add("Hibernate");
        list.add("JSP");
        for (String l: list) {
            System.out.println(l);
        }
    }
}

Kimenet

Java
JavaFX
Spring
Hibernate
JSP

Tulajdonképpen az, hogy a lista konkrétan milyen típusú, illetve hogy kerülnek bele az elemek, a felhasználó szempontjából teljesen mindegy. A Java 9 kibővíti a különböző kollekciók interfészét az of metódussal, ami pontosan a fenti feladatot valósítja meg, és létrehoz egy nem módosítható tárolót a megadott elemek hozzáadásával alkalmazva a Factory Method tervezési mintát.

import java.util.List;
public class FactoryMethodsExample {
    public static void main(String[] args) {
        List<String> list = List.of("Java", "JavaFX", "Spring", "Hibernate", "JSP");
        for (String l : list) {
            System.out.println(l);
        }
    }
}

Abstract Factory

Az Abstract Factory célja kapcsolódó vagy függő objektumcsaládok létrehozása a konkrét osztály megnevezése nélkül. Segítségével egységbe zárhatjuk egy közös témához kapcsolódó egyedi gyártó metódusok egy csoportját anélkül, hogy specifikálnánk azok konkrét osztályait.

Motiváció lehet például egy adott dokumentum létrehozása, amelyet különböző stílusokban hozhatunk létre, de az, hogy a konkrét stílust hogyan kell megvalósítani, az a klienstől elzártan történik, az egyetlen dolog, amit a kliensnek a gyártó felé meg kell mondani, hogy melyik stílus szerint hozza létre az adott dokumentumot.

A minta alkalmazható, ha a rendszer független a termékek szerkezetétől, gyártásától, és több termékcsalád kell hogy legyen, valamint a termékcsalád termékeit együtt kell használni, ráadásul a termékek implementációja rejtett, csak az interfész adott.

abstract factory

Ha megnézzük az osztálydiagramját ennek a tervezési mintának, akkor látjuk, hogy a kliensek csupán annyi ismerete van, hogy milyen konkrét termékek vannak (pl. fontok különböző méretben) , illetve az AbstractFactory interfészen keresztül tud kérést intézni, hogy konkrétan melyikre van szüksége. Attól függően, hogy mely konkrét gyár van munkában (pl. Ariel, Helvetia fontok gyárai), a megfelelő gyár legyártja a kívánt terméket.

A minta használatának következményei, hogy a konkrét osztályok el lesznek szigetelve a klienstől, amely csak egy absztrakt interfészen keresztül éri el azokat. Így a termékek konzisztenciája biztosítva van. Ugyanakkor új terméktípusok bevezetése bonyolult, hiszen valamennyi AbstractProduct osztályt az új terméktípusra definiálni kell.

Abstract Factory példa

Példánkban legyenek alakzataink, amelyek "kerekített" és "sima" változatban lesznek elérhetőek.

public interface Shape {
    void draw();
}
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }
}
public class RoundedRectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside RoundedRectangle::draw() method.");
    }
}
public class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside Square::draw() method.");
    }
}
public class RoundedSquare implements Shape {
    @Override
    public void draw() {
        System.out.println("Inside RoundedSquare::draw() method.");
    }
}

Az egyik absztrakt termékünk a téglalapok (AbstractProductA), a másik a négyzetek (AbstractProductB). Mindegyikből van sima és kerekített változat: sima téglalap (ProductA1), kerekített téglalap (ProductA2), sima négyzet (ProductB1), kerekített négyzet (ProductB2).

public abstract class AbstractFactory {
  abstract Shape getShape(String shapeType);
}

Az AbstractFactory-nak egyetlen getShape metódusa van, amely a paramétere alapján vagy egy négyzetet, vagy egy téglalapot fog visszaadni.

A két konkrét gyárunk közül az egyik a sima alakzatokat fogja tudni legyártani, a másik a kerekített verziókat:

public class ShapeFactory extends AbstractFactory {
    @Override
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        } else if (shapeType.equalsIgnoreCase("SQUARE")) {
            return new Square();
        }
        return null;
    }
}
public class RoundedShapeFactory extends AbstractFactory {
    @Override
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new RoundedRectangle();
        } else if (shapeType.equalsIgnoreCase("SQUARE")) {
            return new RoundedSquare();
        }
        return null;
    }
}

A felhasználónak el kell döntenie, hogy a két konkrét gyár közül melyiket akarja használni. Ezt segíti neki kiválasztani a FactoryProducer osztály:

public class FactoryProducer {
    public static AbstractFactory getFactory(boolean rounded) {
        if (rounded)
            return new RoundedShapeFactory();
        else
            return new ShapeFactory();
    }

A futtatható osztályunkban próbáljuk ki mindkét gyárat, és annak gyártsuk le mindkét "termékét":

public class AbstractFactoryPatternDemo {
    public static void main(String[] args) {
        AbstractFactory shapeFactory = FactoryProducer.getFactory(false);
        Shape shape1 = shapeFactory.getShape("RECTANGLE");
        shape1.draw();
        Shape shape2 = shapeFactory.getShape("SQUARE");
        shape2.draw();
        AbstractFactory shapeFactory1 = FactoryProducer.getFactory(true);
        Shape shape3 = shapeFactory1.getShape("RECTANGLE");
        shape3.draw();
        Shape shape4 = shapeFactory1.getShape("SQUARE");
        shape4.draw();
    }
}

Kimenet

Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside RoundedRectangle::draw() method.
Inside RoundedSquare::draw() method.

Singleton

A Singleton tervezési minta azt garantálja, hogy egy osztályból csak egyetlen egy példányt lehessen példányosítani. Mindenki, aki akarja használni ennek az osztálynak az objektumát, ugyanazt az objektumot fogja elérni. Motiváció lehet például, hogy egy operációs rendszerben csak egy fájlrendszer kezelő lehet (maga az osztály tartja számon a saját példányát). Akkor alkalmazható a minta, ha tehát pontosan egy példány létezhet az adott osztályból, amelyeket a kliensek globálisan elérhetnek.

singleton

Ahhoz, hogy csak egyetlen objektum lehessen, magát a konstruktornak az elérhetőségét kell korlátoznunk. Ezért a konstruktor láthatóságát protectedre állítjuk, vagy ha biztosabbak vagyunk abban, hogy nem akarjuk kibővíteni az egyedüli objektum példányt, akkor private-ra is állíthatjuk. Egy statikus publikus instance metódusunk lesz, ezt elérjük az osztály példányosítása nélkül globálisan. Ha valakinek szüksége van az osztály egyetlen objektumára, ezt a metódust kell hívnia. Ez leellenőrzi, hogy az egyetlen példány már előállt-e (ezt az osztály egy külön private adattagjában tároljuk). Ha már létezik, akkor az instance metódus simán visszaadja ezt az objektumot, ha még nem, akkor legyártja, és úgy adja vissza.

class Singleton {
    public static Singleton instance() {
        if (instance == null)
            instance = new Singleton();
        return instance;
    }
    protected Singleton() {}
    // ...
    private static Singleton instance = null;
    // ...
}

A kód alapján könnyen látható, hogy tetszőlegesen általánosítható ez a megoldás úgy, hogy adott osztályból ne pontosan egy, hanem egy fix darabszámú elem létrehozása legyen engedélyezve. Ha akarjuk, akkor pedig egy gyerek osztályban specializálhatjuk a létrehozandó példányt.


Utolsó frissítés: 2024-04-11 07:54:27