Template Method (Sablonmódszer)
Kategória: Viselkedési minta (Behavioral Pattern)
Lényege és célja
A Template Method minta egy algoritmus vázát (a lépések sorrendjét) egy ősosztály final metódusába zárja, és csak azokat a lépéseket bízza az alosztályokra, amelyek tényleg változnak. Az algoritmus felépítése fix, csak a részletek cserélhetők.
A klasszikus példa az italkészítés: a folyamat mindig víz forralása → főzés → kitöltés → ízesítés. Ami változik: a főzés (kávé vs. teafű áztatása) és az ízesítés (cukor+tej vs. citrom+méz) – a BeverageMaker ősosztály ezt a két lépést hagyja absztraktan, a többit maga elvégzi.
Mit oldunk meg vele?
- Kódduplikáció megszüntetése: a közös lépéseket (víz forralása, csészébe töltés) egyszer írjuk meg, nem minden alosztályban.
- A folyamat sorrendjének védelme: mivel a sablon metódus
final, az alosztály nem tudja átdrótozni a lépések sorrendjét – csak a tartalmukat változtatja. - Hollywood-elv: "Don't call us, we'll call you." Nem az alosztály vezényli az ősosztályt, hanem az ősosztály hívja vissza ("hook") az alosztályt a megfelelő pillanatban. Ez ugyanaz a kontroll-megfordítás (IoC), amit a keretrendszerek (Spring, JUnit) is használnak.
Mikor használjuk?
- Amikor több osztály ugyanazt a folyamatot hajtja végre, csak az egyes lépések különböznek (pl. különböző fájlformátumok parsolása: nyitás → fejléc → sorok → zárás).
- Amikor keretrendszert írunk, és a felhasználónak csak bizonyos "hook" metódusokat akarunk engedni felülírni, a vázat zárva tartani.
- Amikor el akarjuk kerülni, hogy ugyanaz a közös kód több alosztályban is megjelenjen (DRY).
Megjegyzés: A Template Method öröklésen keresztül variál, ezért szorosabb csatolást ad, mint a Strategy. Ha a "változó lépést" futásidőben is cserélgetni szeretnénk, gyakran a Strategy a jobb választás; ha a teljes folyamatváz fix és csak fordítási időben kell több verzió, a Template Method egyszerűbb.
Mermaid Diagram
A diagramon az absztrakt ősosztály (BeverageMaker) és a két konkrét készítő (CoffeeMaker, TeaMaker) kapcsolata látszik. A final makeBeverage() a sablon: rögzíti a lépések sorrendjét, és visszahívja az absztrakt brew() / addCondiments() metódusokat.
classDiagram
class BeverageMaker {
<<abstract>>
+makeBeverage() void «final»
#boilWater() void
#pourInCup() void
#brew()* void
#addCondiments()* void
}
class CoffeeMaker {
#brew() void
#addCondiments() void
}
class TeaMaker {
#brew() void
#addCondiments() void
}
class Client
CoffeeMaker --|> BeverageMaker : "örökli"
TeaMaker --|> BeverageMaker : "örökli"
Client --> BeverageMaker : "csak makeBeverage()-t hívja"
note for BeverageMaker "makeBeverage() {\n boilWater();\n brew(); // alosztály\n pourInCup();\n addCondiments(); // alosztály\n}" Forráskód
package behavioral.template_method;
abstract class BeverageMaker {
final void makeBeverage() {
boilWater();
brew();
pourInCup();
addCondiments();
}
protected abstract void addCondiments();
protected abstract void brew();
private void pourInCup() {
System.out.println("Kitöltés a bögrébe...");
}
private void boilWater() {
System.out.println("Víz forralása...");
}
}
class CoffeeMaker extends BeverageMaker {
@Override
protected void addCondiments() {
System.out.println("Cukor és tej hozzáadása.");
}
@Override
protected void brew() {
System.out.println("Kávé lefőzése.");
}
}
class TeaMaker extends BeverageMaker {
@Override
protected void addCondiments() {
System.out.println("Citrom és méz hozzáadása.");
}
@Override
protected void brew() {
System.out.println("Teafű áztatása 3 percig.");
}
}
class Main {
static void main() {
var coffeeMaker = new CoffeeMaker();
coffeeMaker.makeBeverage();
System.out.println();
var teaMaker = new TeaMaker();
teaMaker.makeBeverage();
}
}