Background image
Alt Text!
Geschreven door Bjarni Coenen op dinsdag, 4 januari 2022

Refactoring: Het vervangen van inheritance door delegates

Inheritance(overerving) is een veel gebruikte techniek in object georiënteerde talen. Objecten met gelijksoortige gegevens en gedrag worden gecategoriseerd. Hierbij worden de gemene delers in de super class geplaatst en in de afgeleide sub classes wordt de mogelijkheid geboden om de functionaliteit aan te passen en/of uit te breiden.

Stel je voor je modelleert een systeem met voertuigen. Waarbij een logische verdeling is: auto’s en boten. Je past inheritance toe waarbij alle voertuigen overerven van de super class auto OF de super class boot.  Het systeem functioneert uitstekend tot dat iemand een sub class wil aanmaken voor dit voertuig.

Is het een auto, boot of beide? Het voorbeeld is natuurlijk absurdistische/humoristische van aard. Echter in de werkelijkheid zijn er tal van voorbeelden waarbij er verschillende doorsnedes/categorieën gemaakt kunnen worden. Neem nu klanten van een e-commerce bedrijf. Dit kunnen particulier of zakelijk klanten zijn. Voor particuliere klanten gelden andere betalingstermijnen dan voor zakelijke klanten. Een andere doorsnede kan zijn: brons, zilver of goud afhankelijk van de omzet. Afhankelijk van in welke categorie een klant valt wordt een kortingspercentage toegepast bij nieuwe bestellingen.

Laten we dit voorbeeld verder uitwerken. Tijdens het initiële ontwerp van dit systeem was maar één doorsnede voorzien, namelijk de classificatie op basis van omzet. Het systeem zou er dan als volgt uit kunnen zien.

static Customer CreateCustomer(double revenue)
{
    return revenue switch
    {
        > 25000 => new GoldCustomer(revenue),
        > 2500 => new SilverCustomer(revenue),
        _ => new BronzeCustomer(revenue),
    };
}

abstract class Customer
{
    private readonly double _revenue;

    public Customer(double revenue)
    {
        _revenue = revenue;
    }

    public abstract double GetDiscountPercentage();

    public double GetRevenue() => _revenue;
}

class BronzeCustomer : Customer
{
    public BronzeCustomer(double revenue) : base(revenue)
    {
    }

    public override double GetDiscountPercentage() => 0.01;
}

class SilverCustomer : Customer
{
    public SilverCustomer(double revenue) : base(revenue)
    {
    }

    public override double GetDiscountPercentage() => 0.02 + Math.Min(0.03, 0.03 * (GetRevenue() / 100000));
}

class GoldCustomer : Customer
{
    public GoldCustomer(double revenue) : base(revenue)
    {
    }

    public override double GetDiscountPercentage() => 0.05 + Math.Min(0.05, 0.05 * (GetRevenue() / 1000000));
}

Het bedrijf bereid uit waardoor het systeem ook het verschil tussen particulier en zakelijke moet kennen. Na een uitgebreide analyse blijkt dat dit moeten worden gemodelleerd met inheritance. In veel talen is het enkel mogelijk om over te erven van één super class, een zogenoemde multiple inheritance behoort niet tot de opties. Er dient dus een andere oplossing te komen voor de categorisering op basis van omzet. Een refactoring techniek die hier oplossing aan bied, is het vervangen van de bestaande inheritance structuur door delegates.

Door de logica die behoort tot het bepalen van het kortingspercentage te extraheren en te delegeren naar een eigen class kan de overerving worden verwijderd.

Stappenplan:

  • Voeg lege classes toe voor de delegates
  • Voeg een field toe aan de super class Customer waarin de delegate wordt opgeslagen
  • Pas de methode voor de instantiatie van Customer sub classes aan zodat deze gebruik maakt van de delegates
  • Verplaats de methode voor de berekening van het kortingspercentage van de sub class naar de delegates
  • In de super class Customer gebruik de delegate voor het bepalen van het kortingspercentage
  • Vervang indien nodig de locaties waar de sub classes worden gebruik door de super class
  • Test, test en test
  • Verwijder de ongebruikte sub classes
static Customer CreateCustomer(double revenue)
{
    IDiscountDelegate discountDelegate = revenue switch
    {
        > 25000 => new GoldDiscountDelegate(revenue),
        > 2500 => new SilverDiscountDelegate(revenue),
        _ => new BronzeDiscountDelegate(),
    };    

    return new Customer(discountDelegate, revenue);
}

class Customer
{
    private readonly IDiscountDelegate _discountDelegate;
    private readonly double _revenue;

    public Customer(IDiscountDelegate discountDelegate, double revenue)
    {
        _discountDelegate = discountDelegate;
        _revenue = revenue;
    }

    public double GetDiscountPercentage() => _discountDelegate.GetDiscountPercentage();

    public double GetRevenue() => _revenue;
}

interface IDiscountDelegate
{
    double GetDiscountPercentage();
}

class BronzeDiscountDelegate : IDiscountDelegate
{
    public double GetDiscountPercentage() => 0.01;
}

class SilverDiscountDelegate : IDiscountDelegate
{
    private readonly double _revenue;

    public SilverDiscountDelegate(double revenue)
    {
        _revenue = revenue;
    }

    public double GetDiscountPercentage() => 0.02 + Math.Min(0.03, 0.03 * (_revenue / 100000));
}

class GoldDiscountDelegate : IDiscountDelegate
{
    private readonly double _revenue;

    public GoldDiscountDelegate(double revenue)
    {
        _revenue = revenue;
    }

    public double GetDiscountPercentage() => 0.05 + Math.Min(0.05, 0.05 * (_revenue / 1000000));
}

Na het doorvoeren van deze refactoring stap is het bedrijf in staat gesteld om het systeem uit te breiden met de tweede doorsnede (particulier of zakelijke) en dit als inheritance te implementeren. Waarbij zaken zoals testbaarheid, uitbreidbaarheid en onderhoudbaarheid voor de bestaande functionaliteit is geborgd.

Ben jij een IT-professional die zich breder wil ontwikkelen?
Neem dan contact met ons op!
Telefoon: 088-81 81 100
E-mail: bjarni.coenen@brightcubes.nl

Deel dit Blog artikel