Zelfstudie: Patroonkoppeling gebruiken om typegestuurde en gegevensgestuurde algoritmen te bouwen

U kunt functionaliteit schrijven die zich gedraagt alsof u uitgebreide typen hebt die zich mogelijk in andere bibliotheken bevinden. Een ander gebruik voor patronen is het maken van functionaliteit die uw toepassing vereist, dat is geen fundamentele functie van het type dat wordt uitgebreid.

In deze zelfstudie leert u het volgende:

  • Situaties herkennen waarin patroonkoppeling moet worden gebruikt.
  • Gebruik patroonkoppelingsexpressies om gedrag te implementeren op basis van typen en eigenschapswaarden.
  • Combineer patroonkoppeling met andere technieken om volledige algoritmen te maken.

Vereisten

In deze zelfstudie wordt ervan uitgegaan dat u bekend bent met C# en .NET, met inbegrip van Visual Studio of de .NET CLI.

Scenario's voor patroonkoppeling

Moderne ontwikkeling omvat vaak het integreren van gegevens uit meerdere bronnen en het presenteren van informatie en inzichten uit die gegevens in één samenhangende toepassing. U en uw team hebben geen controle of toegang voor alle typen die de binnenkomende gegevens vertegenwoordigen.

Het klassieke objectgeoriënteerde ontwerp zou het maken van typen in uw toepassing aanroepen die elk gegevenstype uit die meerdere gegevensbronnen vertegenwoordigen. Vervolgens werkt uw toepassing met deze nieuwe typen, bouw overnamehiërarchieën, maak virtuele methoden en implementeer abstracties. Deze technieken werken en soms zijn ze de beste hulpmiddelen. Andere keren kunt u minder code schrijven. U kunt duidelijkere code schrijven met behulp van technieken die de gegevens scheiden van de bewerkingen waarmee die gegevens worden bewerkt.

In deze zelfstudie maakt en verkent u een toepassing die binnenkomende gegevens uit verschillende externe bronnen gebruikt voor één scenario. U ziet hoe patroonkoppeling een efficiënte manier biedt om die gegevens te gebruiken en te verwerken op manieren die niet deel uitmaken van het oorspronkelijke systeem.

Overweeg een groot grootste grootstedelijk gebied dat tolgelden en piektijdprijzen gebruikt om verkeer te beheren. U schrijft een toepassing die tolgelden voor een voertuig berekent op basis van het type. Latere verbeteringen omvatten prijzen op basis van het aantal inzittenden in het voertuig. Verdere verbeteringen voegen prijzen toe op basis van de tijd en de dag van de week.

Vanuit die korte beschrijving hebt u mogelijk snel een objecthiërarchie getekend om dit systeem te modelleren. Uw gegevens zijn echter afkomstig uit meerdere bronnen, zoals andere systemen voor voertuigregistratiebeheer. Deze systemen bieden verschillende klassen om die gegevens te modelleren en u hebt geen enkel objectmodel dat u kunt gebruiken. In deze zelfstudie gebruikt u deze vereenvoudigde klassen om de voertuiggegevens van deze externe systemen te modelleren, zoals wordt weergegeven in de volgende code:

namespace ConsumerVehicleRegistration
{
    public class Car
    {
        public int Passengers { get; set; }
    }
}

namespace CommercialRegistration
{
    public class DeliveryTruck
    {
        public int GrossWeightClass { get; set; }
    }
}

namespace LiveryRegistration
{
    public class Taxi
    {
        public int Fares { get; set; }
    }

    public class Bus
    {
        public int Capacity { get; set; }
        public int Riders { get; set; }
    }
}

U kunt de starterscode downloaden uit de GitHub-opslagplaats dotnet/samples . U kunt zien dat de voertuigklassen afkomstig zijn van verschillende systemen en zich in verschillende naamruimten bevinden. Geen gemeenschappelijke basisklasse, behalve System.Object kan worden gebruikt.

Patroonkoppelingsontwerpen

In het scenario dat in deze zelfstudie wordt gebruikt, worden de soorten problemen beschreven die overeenkomen met patronen geschikt zijn om op te lossen:

  • De objecten waarmee u moet werken, zijn niet in een objecthiërarchie die overeenkomt met uw doelstellingen. Mogelijk werkt u met klassen die deel uitmaken van niet-gerelateerde systemen.
  • De functionaliteit die u toevoegt, maakt geen deel uit van de kernabstractie voor deze klassen. De tol die door een voertuig wordt betaald, verandert voor verschillende typen voertuigen, maar het tolgeld is geen kernfunctie van het voertuig.

Wanneer de vorm van de gegevens en de bewerkingen op die gegevens niet samen worden beschreven, maken de patroonkoppelingsfuncties in C# het gemakkelijker om mee te werken.

De basisberekeningen voor tol implementeren

De meest eenvoudige tolberekening is alleen afhankelijk van het voertuigtype:

  • A Car is $ 2,00.
  • A Taxi is $ 3,50.
  • A Bus is $ 5,00.
  • A DeliveryTruck is $ 10,00

Maak een nieuwe TollCalculator klasse en implementeer patroonkoppeling op het voertuigtype om het betaalde bedrag op te halen. De volgende code toont de eerste implementatie van de TollCalculator.

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace Calculators;

public class TollCalculator
{
    public decimal CalculateToll(object vehicle) =>
        vehicle switch
    {
        Car c           => 2.00m,
        Taxi t          => 3.50m,
        Bus b           => 5.00m,
        DeliveryTruck t => 10.00m,
        { }             => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
        null            => throw new ArgumentNullException(nameof(vehicle))
    };
}

In de voorgaande code wordt een expressie (niet hetzelfde als een switch instructie) gebruikt waarmee het declaratiepatroon wordt getest.switch Een switchexpressie begint met de variabele, vehicle in de voorgaande code, gevolgd door het switch trefwoord. Vervolgens komen alle schakelarmen binnen accolades. De switch expressie maakt andere verfijningen voor de syntaxis rond de switch instructie. Het case trefwoord wordt weggelaten en het resultaat van elke arm is een expressie. De laatste twee armen tonen een nieuwe taalfunctie. De { } case komt overeen met een niet-null-object dat niet overeenkomt met een eerdere arm. Deze arm onderschept alle onjuiste typen die aan deze methode worden doorgegeven. De { } zaak moet de gevallen voor elk voertuigtype volgen. Als de volgorde werd omgekeerd, zou de { } zaak voorrang krijgen. Ten slotte detecteert het nullconstante patroon wanneer null aan deze methode wordt doorgegeven. Het null patroon kan het laatst zijn omdat de andere patronen alleen overeenkomen met een niet-null-object van het juiste type.

U kunt deze code testen met behulp van de volgende code in Program.cs:

using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

using toll_calculator;

var tollCalc = new TollCalculator();

var car = new Car();
var taxi = new Taxi();
var bus = new Bus();
var truck = new DeliveryTruck();

Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}");
Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}");
Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}");
Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(truck)}");

try
{
    tollCalc.CalculateToll("this will fail");
}
catch (ArgumentException e)
{
    Console.WriteLine("Caught an argument exception when using the wrong type");
}
try
{
    tollCalc.CalculateToll(null!);
}
catch (ArgumentNullException e)
{
    Console.WriteLine("Caught an argument exception when using null");
}

Deze code is opgenomen in het startersproject, maar wordt als commentaar gegeven. Verwijder de opmerkingen en u kunt testen wat u hebt geschreven.

U begint te zien hoe patronen u kunnen helpen bij het maken van algoritmen waarbij de code en de gegevens gescheiden zijn. De switch expressie test het type en produceert verschillende waarden op basis van de resultaten. Dat is alleen het begin.

Prijzen voor bezetting toevoegen

De tolautoriteit wil voertuigen aanmoedigen om maximaal te reizen. Ze hebben besloten meer kosten in rekening te brengen wanneer voertuigen minder passagiers hebben en volledige voertuigen aanmoedigen door lagere prijzen aan te bieden:

  • Auto's en taxi's zonder passagiers betalen een extra $ 0,50.
  • Auto's en taxi's met twee passagiers krijgen een korting van $ 0,50.
  • Auto's en taxi's met drie of meer passagiers krijgen een korting van $ 1,00.
  • Bussen die minder dan 50% vol zijn, betalen een extra $ 2,00.
  • Bussen die meer dan 90% vol zijn, krijgen een korting van $ 1,00.

Deze regels kunnen worden geïmplementeerd met behulp van een eigenschapspatroon in dezelfde switchexpressie. Een eigenschapspatroon vergelijkt een eigenschapswaarde met een constante waarde. Het eigenschapspatroon onderzoekt eigenschappen van het object zodra het type is bepaald. Het ene geval voor een Car breidt zich uit tot vier verschillende gevallen:

vehicle switch
{
    Car {Passengers: 0} => 2.00m + 0.50m,
    Car {Passengers: 1} => 2.0m,
    Car {Passengers: 2} => 2.0m - 0.50m,
    Car                 => 2.00m - 1.0m,

    // ...
};

In de eerste drie gevallen wordt het type getest als een Car, en controleert u vervolgens de waarde van de Passengers eigenschap. Als beide overeenkomen, wordt die expressie geëvalueerd en geretourneerd.

U zou ook de zaken voor taxi's op een vergelijkbare manier uitbreiden:

vehicle switch
{
    // ...

    Taxi {Fares: 0}  => 3.50m + 1.00m,
    Taxi {Fares: 1}  => 3.50m,
    Taxi {Fares: 2}  => 3.50m - 0.50m,
    Taxi             => 3.50m - 1.00m,

    // ...
};

Implementeer vervolgens de bezettingsregels door de gevallen voor bussen uit te breiden, zoals wordt weergegeven in het volgende voorbeeld:

vehicle switch
{
    // ...

    Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
    Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
    Bus => 5.00m,

    // ...
};

De tolinstantie houdt zich niet bezig met het aantal passagiers in de bestelwagens. In plaats daarvan passen ze het tolbedrag als volgt aan op basis van de gewichtsklasse van de vrachtwagens:

  • Vrachtwagens van meer dan 5000 lbs worden extra $5,00 in rekening gebracht.
  • Lichte vrachtwagens onder de 3000 pond krijgen een korting van $ 2,00.

Deze regel wordt geïmplementeerd met de volgende code:

vehicle switch
{
    // ...

    DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
    DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
    DeliveryTruck => 10.00m,
};

De voorgaande code toont de when component van een schakelarm. U gebruikt de when component om andere voorwaarden dan gelijkheid voor een eigenschap te testen. Wanneer u klaar bent, hebt u een methode die er ongeveer als volgt uitziet:

vehicle switch
{
    Car {Passengers: 0}        => 2.00m + 0.50m,
    Car {Passengers: 1}        => 2.0m,
    Car {Passengers: 2}        => 2.0m - 0.50m,
    Car                        => 2.00m - 1.0m,

    Taxi {Fares: 0}  => 3.50m + 1.00m,
    Taxi {Fares: 1}  => 3.50m,
    Taxi {Fares: 2}  => 3.50m - 0.50m,
    Taxi             => 3.50m - 1.00m,

    Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
    Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
    Bus => 5.00m,

    DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
    DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
    DeliveryTruck => 10.00m,

    { }     => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
    null    => throw new ArgumentNullException(nameof(vehicle))
};

Veel van deze schakelarmen zijn voorbeelden van recursieve patronen. Toont bijvoorbeeld Car { Passengers: 1} een constant patroon in een eigenschapspatroon.

U kunt deze code minder herhalend maken met behulp van geneste switches. De Car en Taxi beide hebben vier verschillende armen in de voorgaande voorbeelden. In beide gevallen kunt u een declaratiepatroon maken dat wordt ingevoerd in een constant patroon. Deze techniek wordt weergegeven in de volgende code:

public decimal CalculateToll(object vehicle) =>
    vehicle switch
    {
        Car c => c.Passengers switch
        {
            0 => 2.00m + 0.5m,
            1 => 2.0m,
            2 => 2.0m - 0.5m,
            _ => 2.00m - 1.0m
        },

        Taxi t => t.Fares switch
        {
            0 => 3.50m + 1.00m,
            1 => 3.50m,
            2 => 3.50m - 0.50m,
            _ => 3.50m - 1.00m
        },

        Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
        Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
        Bus b => 5.00m,

        DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
        DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
        DeliveryTruck t => 10.00m,

        { }  => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
        null => throw new ArgumentNullException(nameof(vehicle))
    };

In het voorgaande voorbeeld betekent het gebruik van een recursieve expressie dat u de CarTaxi armen met onderliggende armen die de waarde van de eigenschap testen niet herhaalt. Deze techniek wordt niet gebruikt voor de Bus en DeliveryTruck armen omdat deze armen bereiken testen voor de eigenschap, niet discrete waarden.

Piekprijzen toevoegen

Voor de laatste functie wil de tolinstantie tijdgevoelige piekprijzen toevoegen. Tijdens de ochtend- en avonddrukuren worden de tolgelden verdubbeld. Deze regel is alleen van invloed op verkeer in één richting: binnenkomend naar de stad in de ochtend en uitgaand in het spitsuur van de avond. Tijdens andere tijden tijdens de werkdag nemen tolgelden met 50% toe. Late nacht en vroege ochtend, tolgelden worden verlaagd met 25%. Tijdens het weekend is het de normale snelheid, ongeacht de tijd. U kunt een reeks instructies else gebruiken om dit uit te drukken met behulp van if de volgende code:

public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
{
    if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
        (timeOfToll.DayOfWeek == DayOfWeek.Sunday))
    {
        return 1.0m;
    }
    else
    {
        int hour = timeOfToll.Hour;
        if (hour < 6)
        {
            return 0.75m;
        }
        else if (hour < 10)
        {
            if (inbound)
            {
                return 2.0m;
            }
            else
            {
                return 1.0m;
            }
        }
        else if (hour < 16)
        {
            return 1.5m;
        }
        else if (hour < 20)
        {
            if (inbound)
            {
                return 1.0m;
            }
            else
            {
                return 2.0m;
            }
        }
        else // Overnight
        {
            return 0.75m;
        }
    }
}

De voorgaande code werkt correct, maar kan niet worden gelezen. U moet alle invoercases en de geneste if instructies koppelen om redeneren over de code. In plaats daarvan gebruikt u patroonkoppeling voor deze functie, maar integreert u deze met andere technieken. U kunt één patroonovereenkomstexpressie maken die rekening houdt met alle combinaties van richting, dag van de week en tijd. Het resultaat zou een gecompliceerde expressie zijn. Het zou moeilijk te lezen en moeilijk te begrijpen zijn. Dat maakt het moeilijk om de juistheid te garanderen. Combineer in plaats daarvan deze methoden om een tuple met waarden te bouwen die al deze statussen beknopt beschrijven. Gebruik vervolgens patroonkoppeling om een vermenigvuldiger voor de tol te berekenen. De tuple bevat drie discrete voorwaarden:

  • De dag is een weekdag of een weekend.
  • De periode waarin de tol wordt verzameld.
  • De richting is in de stad of uit de stad

In de volgende tabel ziet u de combinaties van invoerwaarden en de vermenigvuldiger met piekprijzen:

Dag Tijd Richting Premium
Weekday ochtendhas Inkomende x 2.00
Weekday ochtendhas uitgaand x 1.00
Weekday Overdag Inkomende x 1,50
Weekday Overdag uitgaand x 1,50
Weekday 's avonds haast Inkomende x 1.00
Weekday 's avonds haast uitgaand x 2.00
Weekday Overnachting Inkomende x 0,75
Weekday Overnachting uitgaand x 0,75
Weekend ochtendhas Inkomende x 1.00
Weekend ochtendhas uitgaand x 1.00
Weekend Overdag Inkomende x 1.00
Weekend Overdag uitgaand x 1.00
Weekend 's avonds haast Inkomende x 1.00
Weekend 's avonds haast uitgaand x 1.00
Weekend Overnachting Inkomende x 1.00
Weekend Overnachting uitgaand x 1.00

Er zijn 16 verschillende combinaties van de drie variabelen. Door een aantal voorwaarden te combineren, vereenvoudigt u de uiteindelijke switchexpressie.

Het systeem dat de tolheffingen verzamelt, gebruikt een DateTime structuur voor het tijdstip waarop de tol werd verzameld. Bouw lidmethoden waarmee de variabelen uit de voorgaande tabel worden gemaakt. De volgende functie maakt gebruik van een patroonkoppelingsexpressie om aan te geven of een weekend of een weekdag een DateTime expressie vertegenwoordigt:

private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Monday    => true,
        DayOfWeek.Tuesday   => true,
        DayOfWeek.Wednesday => true,
        DayOfWeek.Thursday  => true,
        DayOfWeek.Friday    => true,
        DayOfWeek.Saturday  => false,
        DayOfWeek.Sunday    => false
    };

Die methode is juist, maar het is herhalend. U kunt dit vereenvoudigen, zoals wordt weergegeven in de volgende code:

private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Saturday => false,
        DayOfWeek.Sunday => false,
        _ => true
    };

Voeg vervolgens een vergelijkbare functie toe om de tijd in de blokken te categoriseren:

private enum TimeBand
{
    MorningRush,
    Daytime,
    EveningRush,
    Overnight
}

private static TimeBand GetTimeBand(DateTime timeOfToll) =>
    timeOfToll.Hour switch
    {
        < 6 or > 19 => TimeBand.Overnight,
        < 10 => TimeBand.MorningRush,
        < 16 => TimeBand.Daytime,
        _ => TimeBand.EveningRush,
    };

U voegt een privé enum toe om elk tijdsbereik te converteren naar een discrete waarde. Vervolgens gebruikt de GetTimeBand methode relationele patronen en incrementele or patronen. Met een relationeel patroon kunt u een numerieke waarde testen met behulp van <, >of >=<=. Het or patroon test of een expressie overeenkomt met een of meer patronen. U kunt ook een and patroon gebruiken om ervoor te zorgen dat een expressie overeenkomt met twee verschillende patronen en een not patroon om te testen of een expressie niet overeenkomt met een patroon.

Nadat u deze methoden hebt gemaakt, kunt u een andere switch expressie met het tuple-patroon gebruiken om de prijspremie te berekenen. U kunt een switch expressie bouwen met alle 16 armen:

public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true) => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime, true) => 1.50m,
        (true, TimeBand.Daytime, false) => 1.50m,
        (true, TimeBand.EveningRush, true) => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight, true) => 0.75m,
        (true, TimeBand.Overnight, false) => 0.75m,
        (false, TimeBand.MorningRush, true) => 1.00m,
        (false, TimeBand.MorningRush, false) => 1.00m,
        (false, TimeBand.Daytime, true) => 1.00m,
        (false, TimeBand.Daytime, false) => 1.00m,
        (false, TimeBand.EveningRush, true) => 1.00m,
        (false, TimeBand.EveningRush, false) => 1.00m,
        (false, TimeBand.Overnight, true) => 1.00m,
        (false, TimeBand.Overnight, false) => 1.00m,
    };

De bovenstaande code werkt, maar kan worden vereenvoudigd. Alle acht combinaties voor het weekend hebben dezelfde tol. U kunt alle acht vervangen door de volgende regel:

(false, _, _) => 1.0m,

Zowel inkomend als uitgaand verkeer hebben dezelfde vermenigvuldiger tijdens de weekdag overdag en 's nachts. Deze vier schakelarmen kunnen worden vervangen door de volgende twee lijnen:

(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _)   => 1.5m,

De code moet eruitzien als de volgende code na deze twee wijzigingen:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true)  => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime,     _)     => 1.50m,
        (true, TimeBand.EveningRush, true)  => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight,   _)     => 0.75m,
        (false, _,                   _)     => 1.00m,
    };

Ten slotte kunt u de twee spitstijden verwijderen die de normale prijs betalen. Zodra u deze armen verwijdert, kunt u de false arm vervangen door een verwijdering (_) in de laatste schakelaararm. U hebt de volgende voltooide methode:

public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.Overnight, _) => 0.75m,
        (true, TimeBand.Daytime, _) => 1.5m,
        (true, TimeBand.MorningRush, true) => 2.0m,
        (true, TimeBand.EveningRush, false) => 2.0m,
        _ => 1.0m,
    };

In dit voorbeeld ziet u een van de voordelen van patroonkoppeling: de patroontakken worden op volgorde geëvalueerd. Als u deze opnieuw rangschikt zodat een eerdere vertakking een van uw latere gevallen afhandelt, waarschuwt de compiler u voor de onbereikbare code. Deze taalregels maakten het eenvoudiger om de voorgaande vereenvoudigingen uit te voeren met het vertrouwen dat de code niet is gewijzigd.

Patroonkoppeling maakt sommige typen code beter leesbaar en biedt een alternatief voor objectgeoriënteerde technieken wanneer u geen code aan uw klassen kunt toevoegen. De cloud zorgt ervoor dat gegevens en functionaliteit uit elkaar blijven. De vorm van de gegevens en de bewerkingen erop worden niet noodzakelijkerwijs samen beschreven. In deze zelfstudie hebt u bestaande gegevens op geheel verschillende manieren gebruikt dan de oorspronkelijke functie. Met patroonkoppeling hebt u de mogelijkheid om functionaliteit te schrijven die deze typen overbelasten, ook al kunt u ze niet uitbreiden.

Volgende stappen

U kunt de voltooide code downloaden uit de GitHub-opslagplaats dotnet/samples . Verken zelf patronen en voeg deze techniek toe aan uw reguliere coderingsactiviteiten. Het leren van deze technieken biedt u een andere manier om problemen te benaderen en nieuwe functionaliteit te creëren.

Zie ook