25.1. Létrehozási minták

A létrehozási mintáknak az a céljuk, hogy az egyes objektumpéldányok létrehozásakor speciális igényeknek is eleget tudjunk tenni.

Az Egyke (Singleton) minta például lehetővé teszi, hogy egy osztályból csak egyetlen példányt lehessen létrehozni.

Az Elvont gyár (Abstract Factory), Építő (Builder) és Gyártófüggvény (Factory Method) minták szintén a példányosítást támogatják, amikor nem akarjuk, vagy nem tudjuk, milyen típusú is legyen az adott példány.

25.1.1 Egyke (Singleton)

Az Egyke minta akkor hasznos, ha egy osztályból csak egyetlen példány létrehozását szeretnénk engedélyezni. A módszer lényege, hogy kontrolláljuk a példányosítást egy privát konstruktor létrehozásával. Az osztályból csak egyetlen példányt hozunk létre, azt is csak szükség (az első kérés) esetén. A példány elérése egy statikus gyártó metóduson keresztül történik.

Nézzünk először egy általános példát:

class Singleton {
  private static Singleton instance = null;
  private Singleton() { }
  static public Singleton instance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
  public void finalize() {
    instance = null;
  }
}

Példányt létrehozni a következő módon tudunk:

Singleton one = Singleton.instance();

Ha egy újabb példányt kérünk, akkor is ugyanazt az objektumot kapjuk. Hibás lenne viszont, ha közvetlenül próbálnánk példányosítani:

Singleton one = new Singleton(); // hibás

Egy kicsit más megoldást láthatunk egy távoli kapcsolat kiépítését megvalósító osztálynál. Itt a kapcsolat mindig szükséges, a duplikálás elkerülése a fő célunk.

Megjegyzés: Érdemes belegondolni, milyen zavarokkal járna, ha a programozó figyelmetlensége miatt egyszerre két vagy több távoli kapcsolatot próbálnánk kiépíteni és használni ugyanazon erőforrás felé.

final class RemoteConnection {
  private Connect con;
  private static RemoteConnection rc =
      new RemoteConnection(connection);
  private RemoteConnection(Connect c) {
    con = c;
    ....
  }
  public static RemoteConnection getRemoteConnection() {
    return rc;
  }
  public void setConnection(Connect c) {
    this(c);
  }
}

Szerzői megjegyzés: További minták bemutatása tervben van.

25.1.2 Gyártófüggvény (Factory Method) minta

A gyártófüggvény minta az egyik legtöbbet alkalmazott tervezési minta. A minta célja egy objektum létrehozása különböző információk alapján.

A következő ábrán a bemeneti információt az abc jelképezi. Ez alapján a gyártófüggvény az x ősosztály valamelyik leszármazottját (xy vagy xz) fogja példányosítani. A getClass hívására létrejövő objektum tényleges típusáról többnyire nem is kell tudnia a felhasználónak.

Gyártófüggvény

Nézzünk egy konkrét példát. A feladatunk az, hogy a nevek kezelése körüli következő problémát megoldjuk. Az angol nyelvben kétféle módon is megadhatjuk ugyanazt a nevet:

  • Jack London
  • London, Jack

Ha egy beviteli mezőben a felhasználó megadja a nevét, akkor bármelyiket használja. Nekünk az a feladatunk, hogy a név alapján egy olyan objektumot hozzuk létre, amelyikből bármikor elérhetők a szükséges szolgáltatások.

Először nézzük meg azt az ősosztályt, amelynek a szolgáltatásaira végső soron szükségünk lesz:

abstract class Namer {
    protected String last;
    protected String first;
    public String getFirst() {
        return first;
    }
    public String getLast() {
        return last;
    }
}

Nézzük meg a két igen egyszerű leszármazott osztályt is.

Az első leszármazottunk a név megadásánál az első ( szóközös) megadást feltételezi.

class FirstFirst extends Namer {
    public FirstFirst(String s) {
        int i = s.lastIndexOf(" ");
        if (i > 0) {
            first = s.substring(0, i).trim();
            last =s.substring(i+1).trim();
        } else {
            first = "";
            last = s;
        }
    }
}

A másik leszármazott a vessző karaktert keresi elválasztóként:

class LastFirst extends Namer {
    public LastFirst(String s) {
        int i = s.indexOf(",");
        if (i > 0) {
            last = s.substring(0, i).trim();
            first = s.substring(i + 1).trim();
        } else {
            last = s;
            first = "";
        }
    }
}

A tényleges példányosítást (gyártást) a következő osztály végzi:

class NameFactory {
    public Namer getNamer(String entry) {
        int i = entry.indexOf(",");
        if (i>0)
            return new LastFirst(entry);
        else
            return new FirstFirst(entry);
    }
}

Elegendő a getNamer metódust a nevet tartalmazó String paraméterrel meghívni, eredményül pedig egy Namer (leszármazott) objektumot kapunk.

Megjegyezés: A példa láttán felmerülhet az a kifogás, hogy elegendő lett volna a Namer osztály konstruktorában ezt a kétféle inputot megkülönbözteti. Ennél a példánál tényleg járható lenne ez az út is.

A példa egyszerűsége abban rejlik, hogy a leszármazottak csak a konstruktorunkban térnek el egymástól. Más összetettebb szituáció esetén a leszármazottak érdemi működése is jelentősen eltérhet egymástól.

Legerősebb érvként pedig azt érdemes meggondolni, hogy ha a bemutatott struktúrát kell bővítenünk egy újfajta viselkedéssel, akkor elegendő egy új leszármazott osztály létrehozása és a getNamer metódus bővítése, a többi osztályhoz egyáltalán nem kell hozzányúlni. Ez egy nagyobb alkalmazás esetén nagyon erőteljes érv lehet.