Kihagyás

Iterator tervezési minta

A tervezési minták alapvető célja, hogy a sokaknál előforduló problémákra adjon egy olyan általános megoldásvázat, amelyet aztán a konkrét problémára ültetve, arra specializálva egy jó minőségű megoldást kapunk.

Az előadáson is ismertetett egyik tervezési minta az Iterátor (magyarul talán bejárónak mondhatjuk [de nem mondjuk]).

Célja

Tároló-objektum elemeinek sorozatos elérése a reprezentációtól függetlenül. Ez körülbelül annyit jelent, hogy van egy valamilyen tároló osztályunk, amelyben valamilyen elemek vannak és mi ezeket szeretnénk elérni egymás után, anélkül, hogy tudnánk a konkrét belső reprezántációt, azaz, hogy a tárolóban hogy vannak tárolva az elemek.

Tehát nem akarunk sem tömböt indexelni, sem más egyebet használni, csak és kizárólag az iterátoron keresztül szeretnénk elérni az elemeket. Ez azért hasznos, mert időközben változhat az implementáció (pl.: tömb helyett listát veszünk fel, vagy lista helyett adatbázis-kurzurokat kezdünk használni, stb.), és így nem szükséges a kliens kódot hozzáigazítanunk a konkrét implementációhoz.

Szerkezete

Iterator tervezési minta

Az Iterátor minta szerkezete elég egyszerű: van egy kliens (Client), aki egy konténer (Aggregate osztály/interfész egy konkrét megvalósítása, az ábrán ConcreteAggregate) elemeit szeretné elérni, attól függetlenül, hogy az hogy van implementálva, erre használhatjuk az iterátort (Iterator osztály/interfész egy konkrét megvalósítása, az ábrán ConcreteIterator, amelynek egy példányát a konkrét tároló adja majd vissza, ahogy az ábrán is látszik), és ezt be tudjuk majd járni.

Megjegyzés: a Javaban beépítve is megtalálható az iterátor minta implementálásához szükséges interfész (az ábrán Aggregate és Iterator nevűek), mégpedig Iterable (Aggregate, Container) és Iterator néven, de az érthetőség kedvéért (és mert más nyelvekben ez nincs így) a példában ezeket az interfészeket is definiáljuk.

Megjegyzés: Egy jól ismert megvalósítása a tervezési mintának a már gyakorlatról megismert lista, halmaz, és leképezések implementációi. Ott már használtunk a bejáráshoz iterátort, és ott is az iterátor tervezési minta van megvalósítva. Az utolsó gyakorlati anyagban ezek megtalálhatóak.

Példa

Példánkban egy nagyon egyszerű probléma lesz, amelyet már mindenki láthatott például rajzoló programokban: beépített és felhasználó által definiált színpaletta.

Van két tárolónk: egy beégetett tömbbel megvalósított, standrad színpaletta, amelyben van néhány szín, de ezekhez nem adhatunk hozzá, nem törölhetünk, stb. A másik tárolónk a felhasználó által definiált színek palettája, amely kezdetben üres, de adhatunk hozzá új színeket, törölhetünk, stb., tehát futásidőben ez változtatható (mi most a példában ArrayList-tel valósítjuk ezt meg). Nyilvánvaló, hogy a beégetett tömböt másképp kell bejárni, mint a tömbbel megvalósított listát.

Tehát mindkét tárolóban lesznek elemek, és ezeket a tárolókat szeretnénk majd bejárni úgy, hogy nem tudjuk, éppen melyik változatát járjuk be: a beégetett színeket tartalmazó tárolót, vagy éppen a felhasználói színek által definiált tárolót.

Kezdjük az interfészekkel, az iterátor és az Aggregate (példában Iterator és Container):

public interface Iterator {
    public boolean hasNext();
    public Object next();
}
public interface Container {
    public Iterator createIterator();
}

Ezek kész vannak, kelleni fog a konkrét megvalósításuk is: a megvalósításban az iterátort belső osztályként repreztáltuk. Az első fájl a beépített színeket tartalmazó StandardColors nevű osztály lesz. Itt a beépített színeken kívül nem adhatunk hozzá, nem módosíthatunk semmit, így megfelelő egy konstans String tömb erre a célra.

public class StandardColors implements Container {
    private String colors[] = {
        "black",
        "white",
        "red",
        "green",
        "blue",
        "yellow",
        "purple"
    };

    @Override
    public Iterator createIterator() {
        return new StandardColorsIterator();
    }

    private class StandardColorsIterator implements Iterator {
        private int index;

        @Override
        public boolean hasNext() {
            if (index < colors.length) {
                return true;
            }
            return false;
        }

        @Override
        public Object next() {
            if (this.hasNext()) {
                return colors[index++];
            }
            return null;
        }
    }
}

A másik osztály a felhasználói színeket fogja tárolni. Ezeket egy ArrayListben tároljuk, és az ArrayListet járjuk be. Itt szándékosan nem használtuk az ArrayListben megvalósított iterátort, hanem kézzel figyelünk itt is az egyes elemekre.

import java.util.ArrayList;
import java.util.List;

public class UserDefinedColors implements Container {

    private List<String> colors = new ArrayList<>();

    public void addColor(String colorName) {
        this.colors.add(colorName);
    }

    @Override
    public Iterator createIterator() {
        return new UserDefinedColorsIterator();
    }

    private class UserDefinedColorsIterator implements Iterator {
        private int index;

        @Override
        public boolean hasNext() {
            if (colors == null || colors.isEmpty()) {
                return false;
            }

            if (index < colors.size()) {
                return true;
            }
            return false;
        }

        @Override
        public Object next() {
            if (this.hasNext()) {
                return colors.get(index++);
            }
            return null;
        }
    }
}

Kész is vagyunk. Már csak a felhasználása maradt. Erre készítettünk egy egyszerű demó programot, ahol létrehozunk két tárolót. A felhasználói tárolóba felveszünk néhány színt. Ezt követően a két tárolót betesszük egy tömbbe, majd a tömb minden egyes elemét Container-ként kezelve járjuk be (vagyis úgy, hogy nem tudjuk, hogy az konkrétan mit tárol, csak azt tudjuk, hogy tároló, és hogy van iterátora). Az iterátorokat lekérve íratjuk ki az összes színt, anélkül, hogy tudnánk, hogy ténylegesen milyen konkrét tárolóba vagyunk, és hogy abban hogy van reprezentálva a tényleges tárolás (egyik példában egy beégetett konstans String tömb van, míg a másikban egy dinamikus, tömbbel megvalósított lista).

public class Client {

    public static void main(String[] args) {
        //Letrehozunk egy beepitett szineket tartalmazo palettat, ami kontener
        Container standardColors = new StandardColors();
        //Letrehozunk egy felhasznaoi szinpallettat, amibe azt vesz fel a felhasznalo, amit akar.
        UserDefinedColors userDefinedColors = new UserDefinedColors();

        //Vegyunk is fel par szint.
        userDefinedColors.addColor("cyan");
        userDefinedColors.addColor("khaki");
        userDefinedColors.addColor("american gold");
        userDefinedColors.addColor("apricot");
        userDefinedColors.addColor("army green");
        userDefinedColors.addColor("deep coffee");

        //Ezt a ket kontenert betesszuk egy kontener tombbe.
        Container[] allColorContainer = {
            standardColors,
            userDefinedColors
        };

        System.out.println("All colors (standard and user defined):");
        for (Container container: allColorContainer) {
            Iterator containerIterator = container.createIterator();
            while (containerIterator.hasNext()) {
                System.out.print(containerIterator.next() + ", ");
            }
        }

    }
}
Ennek kimenete:

All colors: (standard and user defined)
black, white, red, green, blue, yellow, purple, cyan, khaki, american gold, apricot, army green, deep coffee, 

Utolsó frissítés: 2024-03-27 14:54:36