1. Het Collections framework in Java
Een datastructuur is in de programmeertaal Java - en ook andere talen - een manier waarop de elementen met elkaar in verband staan. Zo staan de elementen in een ArrayList in een array waarvan de grootte wordt aangepast aan het aantal elementen.
import java.util.ArrayList;
public class Example {
public static void main(String[] args) {
List<String> students = new ArrayList();
students.add("Maria");
students.add("Piet");
students.add("Klaas");
students.add("Katrijn");
System.out.println(students);
}
}
De ArrayList is een onderdeel van het Collections framework . Hieronder zie je daarvan een overzicht (bron wikipedia):
Je ziet dat ieder onderdeel van het framework een interface heeft genaamd Set, List, Deque en Map. Deze laatste heeft een eigen hiërarchie.
Elke klasse of interface heeft zijn eigen functie. Zo kan een Set met concrete klassen HashSet, Treeset en LinkedHashSet geen dubbele elementen bevatten en is een Deque (double ended queue) bedoeld als een stapel borden in een restaurant: je kunt alleen maar aan het begin of aan het einde elementen erin stoppen of eruit halen. Een Map wijst unieke sleutels aan waarden toe. Op basis van deze sleutels kun je de informatie weer ophalen.
Een LinkedList slaat de elementen op een andere manier op. Het eerste element zit gekoppeld aan het tweede element. Het tweede element zit gekoppeld aan element nummer een en element nummer drie. We noemen dit een dubbel gelinkte lijst (doubly linked list). In onderstaande afbeelding zie je het verschil tussen een LinkedList en een ArrayList.
Je ziet dat er een kop (head) en een staart (tail) aan een LinkedList zit. De kop en de staart wijzen beiden naar null. Om een element te kunnen benaderen moeten we door een LinkedList heen hoppen. Bij een ArrayList gaat dit rechtstreeks. Hiermee is een ArrayList sneller. We kunnen dit uitdrukken met een term van Big O.
//LinkedList
get(int index) //O(n)
ArrayList
get(int index) //O(1)
Big O(n) betekent dat in het langzaamste scenario (worst case scenario) het n elementen duurt voordat het element is bereikt (om het maar even simpel uit te drukken). Met O(1) is de tijd altijd constant. Bij het toevoegen van een element is het precies andersom mits we bij de LinkedList geen index hoeven mee te geven.
//LinkedList
Iterator.remove() //O(1)
//ArrayList
remove(int index) // O(n)
Zo zijn er nog meer datastructuren zoals bijvoorbeeld de Stack. Hierin worden de elementen op elkaar gestapeld dus het laatste element ligt bovenop. We noemen dit het LIFO (Last in, First ou) principe.
Het Collections framework valt eigenlijk buiten het kader van deze cursus. Als je er meer over wilt leren neem dan het boek Datastructuren in Java (G.Laan) of Data Structurs & Algorithms in Java (Goodricht, Tamassia) eens door. Dit laatste boek staat inmiddels meerdere keren gratis op het internet.
2. Het Factory pattern
Door middel van het Decorator pattern kunnen we figuren maken bestaande uit cirkels of vierkanten. We hebben dit geleerd in deel 2. Met behulp van het Factory pattern kunnen we dit nog wat overzichtelijker maken. Het Factorypattern heeft de volgende structuur:
In ons geval altijd een cirkel gemaakt met behulp van de methode drawBresenhamsCircle() waarbinnen dan wordt gekozen voor een cirkel of een vierkant. Dit levert dan de volgende afbeeldingen op:
Links zie je dus een cirkel van cirkels, rechts zie je een cirkel van cirkels en vierkanten. Dat is dus wat bedoeld wordt met de klasse Mixed.
We zien ook een klasse ShapeFactory die een figuur (Shape) kan maken aan de hand van de instellingen shapeType, color, distance en grootte.Daarnaast zien we links een interface die de methode draw() voor de onderliggende klassen afdwingt.
De code van de klasse ShapeFactory is als volgt:
public class ShapeFactory {
public Shape getShape(String shapeType, String color, int distance, int size) {
if (shapeType == null || color == null || distance <= 3 || size <= 50) {
return null;
}
if (shapeType.equalsIgnoreCase("circle") && color.equalsIgnoreCase("green")) {
return new CircleDecorator(new GreenColorDecorator(new Figure(size)), distance);
} else if (shapeType.equalsIgnoreCase("circle") && color.equalsIgnoreCase("red")) {
return new CircleDecorator(new RedColorDecorator(new Figure(size)), distance);
}
if (shapeType.equalsIgnoreCase("square") && color.equalsIgnoreCase("green")) {
return new SquareDecorator(new GreenColorDecorator(new Figure(size)), distance);
} else if (shapeType.equalsIgnoreCase("square") && color.equalsIgnoreCase("red")) {
return new SquareDecorator(new RedColorDecorator(new Figure(size)), distance);
}
if (shapeType.equalsIgnoreCase("mixed") && color.equalsIgnoreCase("green")) {
return new MixedDecorator(new GreenColorDecorator(new Figure(size)), distance);
} else if (shapeType.equalsIgnoreCase("mixed") && color.equalsIgnoreCase("red")) {
return new MixedDecorator(new RedColorDecorator(new Figure(size)), distance);
}
return null;
}
}
Het is dus een echte fabriek die wat maakt aan de hand van de parameters. De code equalsIgnoreCase
houdt in dat er geen onderscheid wordt gemaakt tussen hoofd- en kleine letters. Verder hebben we ingesteld dat de image minimaal 50 moet zijn en dat de afstand tussen de 8 figuren minimaal 3 is. Ook ben je verplicht om een kleur mee te geven.
Je ziet dus dat we gebruik maken van code die we ook bij het Decorator pattern hebben gezien:
new CircleDecorator(new GreenColorDecorator(new Figure(size)), distance);
Aan de linkerkant van het pattern zien we een structuur die we eigenlijk al hebben.
Het verschil is dat er een ShapeDecorator tussen zit. Voor de werking van het Factory pattern maakt dat weinig uit. We kunnen nu in Level2 gaan starten het aanroepen van de verschillende figuren. On the fly hebben we de code van de klasse Shapefactory nog wat aangepast. Ook hebben een methode MakeWall gemaakt uit de Greenfoot cursus.
import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
public class Level2 extends World {
public Level2() {
super(600, 400, 1);
makeWalls();
}
private void makeWall(int number, int positionX, int positionY, int size,
boolean isHorizontal, String type, String color, int distance, int imageSize) {
if (isHorizontal) {
for (int i = 0; i < number; i++) {
ShapeFactory shapeFactory = new ShapeFactory(type, color, distance, imageSize);
Shape shape = shapeFactory.getShape();
addObject((Actor) shape, positionX + i * size, positionY);
}
} else {
for (int i = 0; i < number; i++) {
ShapeFactory shapeFactory = new ShapeFactory(type, color, distance, imageSize);
Shape shape = shapeFactory.getShape();
addObject((Actor) shape, positionX, positionY + i * size);
}
}
}
private void makeWalls() {
makeWall(15, 100, 100, 20, false, "circle", "red", 2,30);
makeWall(20, 120, 100, 20, true, "circle", "green", 3,30);
}
}
Qua verantwoordelijkheid vinden we dat de methode makeWall meer bij de klasse Leve2 hoort. Hiermee geven we tevens de klasse ShapeFactory niet meer verantwoordelijkheid dan nodig. Uiteindelijk staat hier weer het aangepaste uml-schema en vind je hier de PlantUML-code.
3. Het Abstract factory pattern
Er zit niet veel verschil in het Factory - en het abstract Factory pattern. We zien wel dat het abstract Factory pattern de code weer wat overzichterlijker maakt. We geven in één keer de code.
public abstract class ShapeFactory {
public static Shape getShape(String shapeType, String color, int distance, int size) {
if (shapeType == null || color == null || distance <= 0 || size < 30) {
return null;
}
if (shapeType.equalsIgnoreCase("circle") && color.equalsIgnoreCase("green")) {
return new CircleDecorator(new GreenColorDecorator(new Figure(size)), distance);
} else if (shapeType.equalsIgnoreCase("circle") && color.equalsIgnoreCase("red")) {
return new CircleDecorator(new RedColorDecorator(new Figure(size)), distance);
}
if (shapeType.equalsIgnoreCase("square") && color.equalsIgnoreCase("green")) {
return new SquareDecorator(new GreenColorDecorator(new Figure(size)), distance);
} else if (shapeType.equalsIgnoreCase("square") && color.equalsIgnoreCase("red")) {
return new SquareDecorator(new RedColorDecorator(new Figure(size)), distance);
}
if (shapeType.equalsIgnoreCase("mixed") && color.equalsIgnoreCase("green")) {
return new MixedDecorator(new GreenColorDecorator(new Figure(size)), distance);
} else if (shapeType.equalsIgnoreCase("mixed") && color.equalsIgnoreCase("red")) {
return new MixedDecorator(new RedColorDecorator(new Figure(size)), distance);
}
return null;
}
}