skip to main content

Design Patterns: Singleton


« Terug naar Software Engineering Skills
» Naar de labo opgave

“Singleton” - Design Pattern

Begeleidende screencast:

Doelstelling

Dive Into Design Patterns: Singleton

Voorbeeld

1. Opzet

Een klassiek voorbeeld van een Singleton patroon is een database connectie, omdat het beheren van diezelfde connecties door diezelfde klasse gebeurt. Stel dat we een website hebben gemaakt met een winkelwagentje.

public class ShoppingCart {
    private int amountOfItems; // and getters/setters
    private int totalMOney;    // ...
}

En de DB accessor:

public class DBHandle {
    public ShoppingCart getShoppingCart() {
        // SELECT * FROM ...
    }
}

Met als REST endpoint:

@Path("/shoppingcart")
public class ShoppingResource {
    @GET
    public ShoppingCart getCart() {
        return new DBHandle().getShoppingCart();    // oops!
    }
}

2. Probleemstelling

Tien gebruikers die op de site terecht komen wensen allemaal hun winkelwagentje te raadplegen. Er zijn maar twee DB connecties beschikbaar, dit opgelegd door de database zelf. Iemand moet die dus beheren (locken, vrijgeven, locken, … - dit heet database pooling).

Als we twee instanties van DBHandle maken, kunnen er plots 2x2 connecties open worden gemaakt naar de database. Die zal dit ook blokkeren, wat resulteert in 2 klanten die een crash ervaren, en twee die hun winkelwagen kunnen raadplegen zonder verdere problemen.

graph TD; A[ShoppingResource Inst1] B[ShoppingResource Inst2] C[DBHandle Inst1] D[DBHandle Inst2] A -->|nieuwe instance| C B -->|nieuwe instance| D

De getCart() methode mag dus in geen geval telkens een nieuwe DBHandle aanmaken.

3. Oplossing

We hebben in dit geval een singleton instance nodig:

@Path("/shoppingcart")
public class ShoppingResource {
    @GET
    public ShoppingCart getCart() {
        return DBHandle.getInstance().getShoppingCart();
    }
}

Waarbij de klasse DBHandle wordt uitgebreid tot:

public class DBHandle {
    private static DBHandle instance;

    public static DBHandle getInstance() {
        if(instance == null) {
            instance = new DBHandle();
        }
        return instance;
    }

    private DBHandle() {
    }

    public ShoppingCart getShoppingCart() {
        // SELECT * FROM ...
    }
}
graph TD; A[ShoppingResource Inst1] B[ShoppingResource Inst2] C[DBHandle Inst] A -->|zelfde instance| C B -->|zelfde instance| C

Op die manier is het aanmaken van een DBHandle instance beperkt tot de klasse zelf, door de private constructor. In de statische methode wordt er eerst gecontroleerd of de instantie null is of niet. In principe zou er maar één keer tijdens de uitvoering van het programma de new DBHandle() regel worden uitgevoerd1.

Eigenschappen van dit patroon

Labo oefeningen

Via Github Classroom.

Opgave 1

Hierin is bovenstaande voorbeeld verwerkt, maar nog zonder Singleton… Voer de unit testen uit in src/main/test: het resultaat zijn gefaalde testen (ROOD), omdat DBHandle verschillende keren wordt aangemaakt. Zorg er voor dat alle testen slagen (GROEN) door het singleton patroon te implementeren!

Opgave 2

Pas ook ShoppingCartResource aan naar een singleton. Is dat nodig om de database niet te overbelasten, als de andere klasse reeds een singleton is, of niet?

Opgave 3

sessy library:

  1. identificeer welke klassen een kans maken om een Singleton te worden. Denk aan bovenstaande voorbeeld. Is er reeds ergens een Singleton patroon geïmplementeerd?
  2. Pas het patroon toe waar jij denkt dat het nodig is.
  3. Hoe kan je afleiden welke gebruikte frameworks op bepaalde plekken Singleton klasses hebben?

Denkvragen


  1. Dit klopt niet helemaal als we kijken naar concurrency problemen, waarbij twee gebruikers op exact hetzelfde tijdstip de methode aanroepen. Dit laten we buiten beschouwing voor dit vak. [return]