A számítógép-programozásban a sablonfüggvény programtervezési minta (angolul: Template Method pattern), vagy röviden sablonfüggvény minta, egy viselkedési tervezési minta, amely egy algoritmus vázát definiálja egy műveletben (a „sablonfüggvény” vagy „sablonmetódus”), de néhány lépésének végrehajtását az alosztályokra bízza. Ez lehetővé teszi az alosztályok számára, hogy újradefiniálják egy algoritmus bizonyos lépéseit anélkül, hogy megváltoztatnák annak alapvető szerkezetét. Így a kód újrafelhasználása és a keretrendszer-szerű fejlesztés válik hatékonyabbá, miközben az algoritmus konzisztens struktúrája megmarad.
Fontos megjegyezni, hogy az itt használt „sablon” kifejezés nem azonos a C++ nyelv sablon (template) funkciójával, hanem egy metódusra utal, amely mintaként szolgál az algoritmus végrehajtásához.
Bevezetés
Ebben a tervezési mintában a szülőosztályban definiált sablonmetódus tartalmazza az algoritmus általános szerkezetét és a fix lépéseket, míg egy vagy több lépés implementációját az alosztályokra delegálja. Ezeket a változó lépéseket gyakran absztrakt metódusokként deklarálják a szülőosztályban, amelyeket az alosztályoknak kötelező implementálniuk. Lehetőség van továbbá konkrét metódusok (úgynevezett „horog” vagy „hook” metódusok) definiálására is a szülőosztályban, amelyek alapértelmezett viselkedést biztosítanak, de az alosztályok opcionálisan felülírhatják őket, hogy finomhangolják vagy testre szabhassák az algoritmus működését.
Így az alosztályok eltérő viselkedéseket valósíthatnak meg az algoritmus egyes pontjain, miközben a teljes algoritmus logikai menete és szerkezete változatlan marad. Az általános algoritmus tehát egyetlen, központi helyen, a szülőosztályban van definiálva és karbantartva, de a konkrét lépések implementációja az alosztályokon keresztül specializálható. Az általános algoritmus váza tehát a szülőosztályban rögzül, míg a részletek megvalósítása rugalmasan változtatható az alosztályokban.
A sablonfüggvény minta hatékonyan kezeli az algoritmusok magasabb szintű szerkezetét és logikáját. Ezt a magasabb szintű logikát maga a sablonmetódus testesíti meg, amely meghívja a szükséges (absztrakt vagy konkrét) lépéseket. A nem absztrakt metódusok (beleértve magát a sablonmetódust és az esetleges hook metódusokat alapértelmezett implementációval) működését teljesen a szülőosztály irányítja, míg az absztrakt metódusokat kötelezően az alosztályokban kell megvalósítani, ezáltal biztosítva a minta rugalmasságát és az alosztályok szabadságát a részletek kidolgozásában. A „horog” (hook) metódusok lehetővé teszik, hogy az alosztályok opcionálisan beleszóljanak az algoritmus menetébe anélkül, hogy az összes absztrakt metódust implementálniuk kellene, vagy hogy módosítaniuk kellene a sablonmetódust magát. Néhány, vagy akár az összes absztrakt és hook metódus specializálható egy alosztályban, engedélyezve az adott alosztály fejlesztőjének, hogy specifikus viselkedéseket valósítson meg minimális módosításokkal a magasabb szintű algoritmus keretein belül. A sablonmetódus logikája és hívási sorrendje változatlan marad ebben a mintában, biztosítva az alárendelt metódusok (fix, absztrakt és hook) meghívását az eredetileg tervezett szekvenciában.
A sablonfüggvény tervezési minta gyakran természetesen előfordul objektumorientált nyelvekben, különösen akkor, ha polimorf metódusokat használnak. Egy absztrakt vagy polimorf metódus meghívása egy másik metódusból lényegében már maga is egy egyszerű sablonfüggvény lehet. A minta tudatos alkalmazása azonban további strukturális előnyökkel járhat.
A sablonfüggvény minta implementációja jól illeszkedik a GRASP „Protected Variations” (Védett Változatok) elvéhez, mivel az algoritmus változó részeit (az alosztályokban implementált lépéseket) interfészek vagy absztrakt metódusok mögé rejti, így elszigeteli őket az algoritmus stabil vázától. Ezáltal a változások az egyes lépések implementációjában nem befolyásolják a sablonmetódus szerkezetét. Eltérően az Adapter mintától, amely egy meglévő objektum interfészét alakítja át egy másik elvárt interfésszé, a sablonfüggvény minta egy algoritmus vázát definiálja, és annak testreszabását teszi lehetővé alosztályokon keresztül.
Struktúra
Használata A sablonfüggvény minta különösen gyakran használatos keretrendszerek (frameworkök) fejlesztésekor. Ilyenkor a keretrendszer definiálja az általános munkafolyamatot vagy algoritmust (a sablonmetódusban), de a konkrét alkalmazásspecifikus logikát a keretrendszert használó fejlesztőnek kell megadnia az alosztályok megfelelő metódusainak implementálásával vagy felülírásával. Ez egy tipikus példa a kontroll megfordítása (Inversion of Control, IoC) elvre.
Érvek a sablonfüggvény minta használata mellett: Lehetővé teszi az alosztályok számára, hogy az algoritmus bizonyos részeit (a változó lépéseket) saját, specifikus implementációval lássák el a metódusok felülírása vagy implementálása révén. Kerüli a kódduplikációt: Az algoritmus általános szerkezete és a közös, fix lépések csak egyszer, a szülőosztály sablonmetódusában vannak implementálva, elősegítve a DRY (Don't Repeat Yourself) elv betartását. Szabályozott kiterjesztést biztosít az alosztályok számára. Ellentétben egy egyszerű polimorfikus felülírással, ahol az alapmetódus teljes egészében újraírható (ami radikálisan és potenciálisan inkonzisztensen változtathatja meg a folyamatot), a sablonfüggvény minta esetén csak az algoritmus előre meghatározott, specifikus pontjai (lépései) módosíthatók vagy implementálhatók.
Az irányítási struktúra, amely a minta alkalmazásának eredményeként létrejön, gyakran a „Hollywood-elv” („Ne hívjon minket, majd mi hívjuk Önt!” – Don't call us, we'll call you) megtestesüléseként ismert. Ennek értelmében a szülőosztályban lévő sablonmetódus irányítja a teljes folyamatot, és szükség szerint, a megfelelő időben meghívja az alosztályok által biztosított (implementált vagy felülírt) metódusokat. Az irányítás tehát a szülőosztályban összpontosul, nem pedig az alosztályokban. Ezt a működési elvet a lentebbi Java példakód is jól illusztrálja.
A sablonfüggvény minta hasznos lehet automatikusan generált kóddal való integráció során is. Gyakori kihívás, hogy a generált kódot nem szabad manuálisan módosítani, mivel a forrás (pl. egy modell vagy specifikáció) megváltozásakor a kódot újragenerálják, felülírva a kézi változtatásokat. A sablonfüggvény minta használatával ez a probléma kezelhető: a generált kód tartalmazhatja az absztrakt ősosztályt a sablonmetódussal és a fix lépésekkel, míg a manuálisan írt, testreszabott logika egy különálló alosztályba kerülhet, amely implementálja az absztrakt metódusokat. Így a generált és a kézzel írt kódrészek tisztán elkülönülnek, és a generált kód biztonságosan frissíthető a manuális módosítások elvesztése nélkül. A sablonmetódust tartalmazó elem lehet absztrakt osztály, vagy akár interfész is, különösen olyan nyelvekben, amelyek támogatják az interfészek alapértelmezett metódusait (pl. Java 8+).
Példa Az alábbi Java kód egy egyszerű példát mutat be a sablonfüggvény mintára egy általános játékmenet-keretrendszer kontextusában.
/** * Absztrakt osztály, amely közös több olyan játékhoz, * ahol a játékosok egymás ellen játszanak, de egy adott * időpontban csak egy játékos van soron. */ abstract class Game {
protected int playersCount; // Játékosok száma
// Absztrakt metódusok, amelyeket az alosztályoknak kell implementálniuk abstract void initializeGame(); // Játék inicializálása abstract void makePlay(int player); // Egy játékos lépésének végrehajtása abstract boolean endOfGame(); // Ellenőrzi, hogy véget ért-e a játék abstract void printWinner(); // Kiírja a győztest
/** * A sablonmetódus. Meghatározza a játék algoritmusának vázát. * final módosítóval megakadályozzuk, hogy az alosztályok felülírják. */ public final void playOneGame(int playersCount) { this.playersCount = playersCount; initializeGame(); // 1. lépés: Inicializálás (alosztály specifikus) int currentPlayer = 0; while (!endOfGame()) { // 2. lépés: Ciklus amíg a játék véget nem ér (alosztály specifikus feltétel) makePlay(currentPlayer); // 3. lépés: Játékos lépése (alosztály specifikus) currentPlayer = (currentPlayer + 1) % playersCount; // Következő játékos kiválasztása (fix logika)
printWinner(); // 4. lépés: Győztes kihirdetése (alosztály specifikus)
/** * Konkrét játék implementáció: Monopoly. * Kiterjeszti a Game osztályt és implementálja az absztrakt metódusokat. */ class Monopoly extends Game {
/* A szükséges konkrét metódusok implementációja */ @Override void initializeGame() { System.out.println("Monopoly játék inicializálása..."); // Játékosok inicializálása // Pénz kiosztása, tábla előkészítése stb.
@Override void makePlay(int player) { System.out.println("Játékos " + (player + 1) + " lép a Monopolyban."); // Egy játékos körének feldolgozása (kockadobás, lépés, vásárlás stb.)
@Override boolean endOfGame() { // Visszatérési érték: true, ha a játék véget ért a Monopoly szabályai szerint // (pl. csak egy játékos maradt pénzzel) // Ebben a példában egyszerűsítésként feltételezzük, hogy valamilyen feltétel alapján véget ér. return false; // Placeholder
@Override void printWinner() { System.out.println("Monopoly győztesének kihirdetése..."); // Megjeleníti, ki nyert
/* Specifikus deklarációk és metódusok a Monopoly játékhoz. */ // ... }
/** * Konkrét játék implementáció: Sakk. * Kiterjeszti a Game osztályt és implementálja az absztrakt metódusokat. */ class Chess extends Game {
/* A szükséges konkrét metódusok implementációja */ @Override void initializeGame() { System.out.println("Sakkjáték inicializálása..."); // Játékosok inicializálása // Bábuk felhelyezése a táblára
@Override void makePlay(int player) { System.out.println("Játékos " + (player + 1) + " lép a Sakkban."); // Egy játékos lépésének feldolgozása
@Override boolean endOfGame() { // Visszatérési érték: true, ha matt vagy patt helyzet alakult ki // Ebben a példában egyszerűsítésként feltételezzük, hogy valamilyen feltétel alapján véget ér. return false; // Placeholder
@Override void printWinner() { System.out.println("Sakkjátszma győztesének kihirdetése..."); // Megjeleníti a győztes játékost vagy a döntetlen eredményt
/* Specifikus deklarációk és metódusok a Sakk játékhoz. */ // ... }
// Főprogram a használat bemutatására (opcionális) public class TemplateMethodDemo { public static void main(String[] args) { Game game = new Monopoly(); game.playOneGame(2); // Monopoly játék indítása 2 játékossal
System.out.println("\n----\n");
game = new Chess(); game.playOneGame(2); // Sakk játék indítása 2 játékossal
Fordítás Ez a szócikk részben vagy egészben az angol Wikipédia azonos című cikkének korábbi változatán alapul.
Jegyzetek
Kategória:Programtervezési minták Kategória:Java-kódpéldák