skip to main content

Design Patterns: Decorator


« Terug naar Software Engineering Skills
» Naar de labo opgave

“Decorator” - Design Pattern

Begeleidende screencast:

Doelstelling

Dive Into Design Patterns: Decorator

Voorbeeld

1. Opzet

Stel dat we in een fabriek op plaats X een auto samenstellen. De wagen is een zeer eenvoudige Volkswagen Golf, zonder opties. Op een andere locatie, plaats Y, hebben wij als bedrijf ook fabrieken die sportauto’s bouwen (Volkswagen Scirocco) en op plaats Z luxe wagens (Volkswagen Passat).

public interface Car {
    public void assemble(); // build stuff
}

Fabriek X:

public class VWGolf implements Car {
    @Override
    public void assemble() {
        System.out.println("do not forget the steering wheel!");
    }
}

Fabriek Y:

public class VWScirocco implements Car {
    @Override
    public void assemble() {
        System.out.println("do not forget lots of turbo!");
    }
}

Fabriek Z:

public class VWPassat implements Car {
    @Override
    public void assemble() {
        System.out.println("do not forget all the leather!");
    }
}
graph TD; A["VW Golf"] B["VW Scirocco"] C["VW Passat"] Z{Car} Z --> A Z --> B Z --> C

2. Probleemstelling

De General Manager belt: hij meldt ons vrolijk dat we zijn opgekocht door Geely, een Chinese autofabriekant. De heren in China wensen een mooie combinatie te maken tussen langs de ene kant luxe, en langs de andere kant pure kracht. Een combinatie van de technologie van fabriek Y en Z, dus. Dit mag enkel op aanvraag gebeuren, en is dus niet simpelweg een nieuwe variant van een VW assemblage, maar een dynamische combinatie, waarbij soms meer luxe en soms meer kracht wordt gevraagd. Dat hangt natuurlijk van de klant af.

We zullen dus een custom optie voorzien:

graph TD; A["VW Golf"] B["VW Scirocco"] C["VW Passat"] D["Custom Car Decorator"] Z{Car} Z --> A Z --> B Z --> C B --> D C --> D

3. Oplossing

Deze custom oplossing is nog steeds een implementatie van de interface Car:

public class CustomCarDecorator implements Car {
    private final List<Car> carsToAssemble;

    public CustomCarDecorator(Car... carTypes) {
        this.carsToAssemble = Arrays.asList(carTypes);
    }

    @Override
    public void assemble() {
        for(Car car : carsToAssemble) {
            car.assemble();
        }
    }
}

Op deze manier kunnen we zoveel auto types als de klant wenst doorgeven aan de CustomCarDecorator instantie, die netjes in de bestaande fabriek logica past, gegeven de volgende fabriek klasse:

public class Factory {
    public Car create(Car car) {
        car.assemble();  // works, since it's an interface
        paint(car);
        return car;
    }

    private void paint(Car car) {
        System.out.println("painting...");
    }
}

Dit werkt enkel en alleen omdat we met een interface werken!

Eigenschappen van dit patroon

Labo oefeningen

Via Github Classroom.

Opgave 1

Dit bevat bovenstaande wagenpark voorbeeld. Hier dien je de volgende dingen nog aan te wijzigen:

Opgave 2

Werk verder op opgave 1: bouw een nieuwe Factory in Zweden. Deze Factory in Zweden kan veel meer kleuren op het chassis spuiten dan de klassieke Factory. (Verzin iets anders in het println() statement). Wat als een klant een combinatie van deze factory, en de klassieke wenst? Maak een Decorator voor de factories in plaats van de wagens. Test deze in de Main klasse.

Opgave 3

sessy library:

  1. identificeer waar jij denkt dat een decorator nodig zou kunnen zijn. Waar zou mogelijks dynamische logica gebruikt kunnen worden?
  2. Pas het patroon toe waar jij denkt dat het nodig is.

Denkvragen