Gemakkelijk uitbreiden van je software via interfaces

Geschreven door: op donderdag 11 juni 2020

Leestijd: 5 minuten

In het vorige blogitem heb ik beschreven hoe je interfaces kan toepassen in je software en waarom ze zo handig zijn bij het testen, of dat nou handmatig is of automatisch, via bijvoorbeeld Unit Tests. Een ander voordeel van interfaces is dat je software gemakkelijk kan uitbreiden met nieuwe functionaliteit, zonder dat de bestaande code hoeft worden aangepast, en met een geminimaliseerd risico dat de bestaande functionaliteit per ongeluk is aangepast (wat je kan valideren middels testen).

Eén van de manieren om middels interfaces functionaliteit toe te voegen, is simpelweg een nieuwe implementatie van de bestaande interface. In het vorige blogitem beschreef ik de interface IFileSystem met de implementatie ActualFileSystem. Om testen mogelijk te maken zonder afhankelijk te zijn van een fysieke schijf, met alle onvoorziene externe factoren, maakte ik een implementatie VirtualFileSystem, die bestanden en data virtueel in een Dictionary opslaat.

Interfaces worden weleens omgeschreven als contracten, als gedefiniëerde omschrijvingen van gedrag: ik wéét dat je functie A en B hebt, die bedoeld zijn voor dit gedrag en de return values zijn respectievelijk een string en een object van type X. Of ik nou de ene implementatie van de interface gebruik of de andere, deze afspraken zijn in steen gebeiteld.

Nieuwe implementaties

Nu zijn de requirements aangepast van de software, de klant wil de reviews ook in de cloud opslaan. S3 van AWS is gekozen als cloudoplossing en de software moet een verbinding maken met S3 en de reviews daarin opslaan, in plaats van op een lokale harde schijf. Omdat we een interface gebruiken, hoeven we alleen maar een implementatie van IFileSystem te maken die een koppeling maakt naar AWS, deze implementatie te plaatsen in de code en de rest van de code blijft werken zoals de bedoeling is.

Hieronder een voorbeeld. De implementatie S3FileSystem is nét wat anders, want verwacht een accessKey, waarmee de API van AWS gebruikt kan worden, maar omdat er gebruik wordt gemaakt van een interface, blijft de noodzakelijke functionaliteit behouden.

// in functie:
    string awsAccessKey = "xyzabcdef123456";
    IFileSystem fileSystem = new S3FileSystem(awsAccessKey);
    storeReview(review, fileSystem);

// Implementatie:
private class S3FileSystem : IFileSystem
{
    private string awsAccessKey;
    //private AWS API Object

    public S3FileSystem(string awsAccessKey)
    {
        this.awsAccessKey = awsAccessKey;

        // Voer hier noodzakelijk code uit met betrekking tot AWS API.
    }

    public void AppendContent(string path, string content)
    {
        // Schrijf data weg via AWS API.
    }

    public string ReadFileAsString(string path)
    {
        string content = "";

        // Haal data op via AWS API.

        return content;
    }
}

Extra functionaliteit

Nóg een nieuwe requirement: welke manier van opslaan ook gebruikt wordt, er moet worden bijgehouden wanneer een bestand wordt aangepast, wanneer dat is gebeurd en door wie. Je zal in alle bestaande implementaties (S3FileSystem én ActualFileSystem) deze logfunctionaliteit moeten toevoegen en uiteraard ook wanneer er nieuwe implementaties komen (DatabaseFileSystem, AzureFileSystem?). Gelukkig maken we gebruik van interfaces en dat maakt het mogelijk om een bepaald design pattern te gebruiken, het Decorator Pattern.

Als je een implementatie van een interface wikkelt om een andere implementatie van dezelfde interface om extra functionaliteit toe te voegen, dan noem je die eerste implementatie een Decorator. In ons voorbeeld zou de Decorator bepaalde gegevens loggen en daarna verder gaan met wat de bedoeling was, namelijk opslaan van reviews. Je kan deze Decorator gebruiken als argument in plaats van een daadwerkelijk FileSystem, omdat het ook maar een implementatie van IFileSystem is.

Zie onderstaand voorbeeld. De Decorator heeft minstens één argument nodig, de implementatie van IFileSystem die gebruikt wordt, en in dit voorbeeld ook een username, zodat we weten wie de data heeft weggeschreven. Omdat AccessLogFilesystemDecorator ook een implementatie is van IFileSystem, en gebonden is aan hetzelfde contract als de andere implementaties, hoeven functies die gebruikmaken van IFileSystem niet te worden aangepast.

// in functie:
    IFileSystem fileSystem = new VirtualFileSystem();

    // Maar kan ook zijn:
    // string awsAccessKey = "xyzabcdef123456";
    // IFileSystem fileSystem = new S3FileSystem(awsAccessKey);

    string username = "dominique";
    IFileSystem accessLogDecorator = new AccessLogFilesystemDecorator(fileSystem, username);

    storeReview(review, accessLogDecorator);
    storeReview(review2, accessLogDecorator);

// implementatie decorator pattern:
private class AccessLogFilesystemDecorator : IFileSystem
{
    private IFileSystem fileSystem;
    private string username;

    public AccessLogFilesystemDecorator(IFileSystem fileSystem, string username)
    {
        this.fileSystem = fileSystem;
        this.username = username;
    }

    public void AppendContent(string path, string content)
    {
        Console.WriteLine($"Access: {path} om {DateTime.Now.ToString()} door {username}");
        fileSystem.AppendContent(path, content);
    }

    public string ReadFileAsString(string path)
    {
        return fileSystem.ReadFileAsString(path);
    }
}

Mijn applicatie toont in de console nu:

Access: c:\hierbewaarikreviews\reviews.txt om 11-6-2020 10:05:47 door dominique
Access: c:\hierbewaarikreviews\reviews.txt om 11-6-2020 10:05:47 door dominique
Review bewaard:
Dominique op 23-5-2020: Deze blog laat goed zien hoe je interfaces kan gebruiken om je tests eenvoudiger en accurater te maken!
Dominique op 24-5-2020: Goed voorbeeld, maar statische verwijzingen?!

Er zijn meer design patterns om het ontwikkelen van software gemakkelijker te maken en veel ervan werken met interfaces in plaats van concrete implementaties.


Andere blogartikelen

  • Overeenkomsten tussen Growth Hacking en UX design

    Geschreven door: op zondag 13 september 2020

    Laten we beginnen met wat Growth hacking is, Growth hacking zijn experimenten voor oplossingen die groeiproblemen moeten oplossen. Dit is een nieuwe marketing aanpak die met name wordt gedreven door d ...

    Bekijk het artikel »
  • B2B SEO en Geoptimaliseerde Afbeeldingen

    Geschreven door: op woensdag 12 augustus 2020

    In een tijd waarin 27% van het koopproces van B2B kopers met eigen online research wordt doorgebracht is het essentieel om een goed vindbare website te hebben. Afbeeldingen kunnen, indien geoptimalise ...

    Bekijk het artikel »
  • Prospects zoeken B2B

    Geschreven door: op woensdag 12 augustus 2020

    Het zoeken van B2B prospects is een uitdaging voor elk bedrijf. Met name kwalitatieve B2B prospects.Door vooraf onderzoek te doen naar bedrijven die voldoen aan je criteria en je marketing hierop aan ...

    Bekijk het artikel »
Bel 072 5345 888
Meer dan 40 bedrijven vertrouwen op ons
Allrig is de alles in een leverancier binnen de energie-industrie
AOC is een toonaangevend wereldwijd bedrijf actief in de verkoop van kwaliteitsharsen
ERIKS is een toonaangevende en innovatieve leverancier aan de procesindustrie en aan machinebouwers, die zowel de rol van specialist als die van brede MRO-leverancier vervult
Industrieel dienstverlener Heinen & Hopman Engineering uit Bunschoten is dé wereldwijde specialist op het gebied van klimaatbeheersing
Handicare is een internationale organisatie die ouderen helpt om hun dagelijks leven gemakkelijker te maken door het produceren van hoogwaardige trapliften
Op de hoogte blijven?

Meld u aan voor de gratis nieuwsbrief om op de hoogte te blijven van onze activiteiten