Deel IV: Transform Self Type Check

De refactoring Transform Self Type Check is een samengestelde refactoring: deze omvat dus de toepassing van een aantal kleinere refactorings. Het is een voorbeeld van het herstructureren van conditionals naar het gebruik van polymorphisme.

Bij Transform Self Type Check willen we af van expressies waarin type-informatie gebruikt wordt. Type-informatie, typisch geïmplementeerd in een type-attribuut dat verschillende waarden kan aannemmen -- voor de verschillende typen -- leiden steevast tot complexe code, waarin de scenario's voor alle typen in eenzelfde context moeten geïmplementeerd worden. Dit zorgt ervoor dat de code erg moeilijk te begrijpen, en dus ook te wijzigen is. Als oplossing zorgt Transform Self Type Check ervoor dat:

Tijdens deze oefening stimuleren we je telkens om je de vraag te stellen: "Waarom moet deze methode eigenlijk weten van welk type de instantie is? Is dit eigenlijk geen verantwoordelijkheid van de klasse zelf? Kan polymorphisme er dus niet voor zorgen dat de juiste implementatie wordt uitgevoerd?". OK, dit is niet 1 vraag, het zijn er 3 ;)

Oefening IV.1: Introduceren van subklassen

Nee hoor, we zijn de testen niet vergeten! :)
Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Creëer subklassen Workstation en Printer van klasse Node. Geef ze elk een constructor die de constructor van super-klasse Node oproept. Je zou dan volgende code moeten bekomen:


Declaratie van klasse Workstation als subklasse van Node


Declaratie van klasse Printer als subklasse van Node

Op dit moment worden deze klassen nog niet gebruikt. Voer toch even de testen uit om jezelf ervan te verzekeren dat je nog steeds over een correct systeem beschikt.

Oefening IV.2: Overstappen op het gebruik van de subklassen

Methode Network::DefaultExample maakt nu nog alle instanties van type Node:


Oorspronkelijke implementatie van DefaultExample in klasse van Network

Uiteraard voeren we eerst de testen uit om ons ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt. Toch?

Vervang elk van de instantiaties van Node zo mogelijk naar instantiaties van de nieuwe klassen Workstation en Printer. Aangezien deze voorlopig louter een constructor bevatten vormt dit een kleine verandering:


Implementatie van DefaultExample na gebruik van juiste subklassen

Voer na deze kleine stap de testen uit om jezelf ervan te verzekeren dat je nog steeds over een correct systeem beschikt.

Oefening IV.3: Herverdelen van verantwoordelijkheden overheen de subklassen

Nu komt het leuke deel! Eindelijk kunnen we nu verantwoordelijkheden van klasse Node overheen de subklassen verdelen. Laten we eerst eens kijken naar de huidige implementatie van Node::printOn. Deze bevat enkele switch-cases die naar de subklassen verplaatst kan worden:


Oorspronkelijke implementatie van printOn in klasse van Node

Willen we gebruik maken van polymorphisme bij het herverdelen van de verantwoordelijkheden overheen de subklassen, dan moeten we 2 stappen uitvoeren:

  1. De methode die we willen overschrijven in de subklassen als virtual declareren.
  2. De methode signatuur declareren in de headers van de subklassen en de methode ook werkelijk overschrijven in elke subklasse.

Polymorphisme mogelijk maken: declareren van de methode als virtual

Vangnet ok? We testen weer even om ons ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt. Toch?

Declareer methode Node::printOn in header-file Node.h als virtual:


Declaratie van printOn in header-file Node.h

Geen neveneffecten geïntroduceerd? We testen nogmaals om ons ervan te verzekeren dat je na het refactoren over een correct systeem beschikt.

Methode Node::printOn overschrijven in de subklassen

Eigenlijk voeren we hier twee stappen uit:

  1. Kopiëren van de implementatie van de oorspronkelijke methode naar de subklassen.
  2. Wegknippen van de delen die niet van toepassing zijn in de basisklasse en subklassen.

Bij het introduceren van polymorphisme maken we vaak fouten. Laten we ons er nog even van verzekeren dat de testen vóór refactoring nog aangeven dat het systeem nog correct is.

Voer bovenstaande stappen stap voor stap uit. Uiteindelijk zullen de implementaties van printOn in subklassen Workstation en Printer alsook basisklasse Node er als volgt uitzien:


Implementatie van printOn in subklasse Workstation


Implementatie van printOn in subklasse Printer


Implementatie van printOn in klasse van Node na verplaatsing naar subklassen

Geven de testen nog aan dat het systeem nog steeds correct is? Mooi zo!

Oefening IV.4: Herverdeling van Node::printXMLOn overheen de subklassen

In deze oefening passen we dezelfde stappen toe als in oefening IV.3. Zoals je merkt is de implementatie van Node::printXMLOn quasi identiek:


Oorspronkelijke implementatie van printXMLOn in klasse Node

We beginnen eraan! Gauw nog even nagaan of de testen bevestigen dat het systeem nog correct werkt.

Herverdeel nu zelfstandig de implementatie van Node::printXMLOn overheen de subklassen Workstation en Printer. Je zou volgend resultaat moeten kunnen bereiken:


Implementatie van printXMLOn in subklasse Workstation


Implementatie van printXMLOn in subklasse Printer


Implementatie van printXMLOn in klasse Node na verplaatsing naar subklassen

Wat zeggen de testen? Geven ze nog aan dat het systeem nog steeds correct is?

Oefening IV.5: Verwijderen van overbodige type-checks

Door het herverdelen van verantwoordelijkheden overheen de subklassen steunend op polymorphism zijn de type-checks overbodig geworden. Ga dit zelf nog even na in de implementaties van printOn in de basisklasse Node en subklassen Workstation en Printer:


Implementatie van printXMLOn in subklasse Workstation


Implementatie van printXMLOn in subklasse Printer


Implementatie van printXMLOn in klasse Node na verplaatsing naar subklassen

We gaan refactoren, dus testen we nog even of onze testen bevestigen dat het systeem nog correct werkt.

Aangezien de type-checks overbodig zijn kunnen we ze verwijderen. Hou enkel nog de inhoud van de gepaste case-block over. Zo zou je volgend resultaat moeten bereiken:


Implementatie van printXMLOn in klasse Workstation zonder switch


Implementatie van printXMLOn in klasse Printer zonder switch


Implementatie van printXMLOn in klasse Node zonder switch

Dat scheelt een pak code! Laten we even nagaan of we geen neven-effecten hebben geïntroduceered door de testen nog eens te verifiëren.

Oefening IV.6: Herverdeling van print-functionaliteit naar de Printer subklasse

Dit is de laatste oefening! (Oef!/Oooh!) In deze oefening passen we Transform Self Type Check nog een laatste maal toe.

Methode Node::printDocument maakt ook gebruik van type-informatie. Je kan al een donkerbruin vermoeden hebben dat Transform Self Type Check de implementatie van printDocument in klasse Node een pak eenvoudiger kan maken. Laten we eens even kijken naar deze methode Node::printDocumentNode::printDocument:


Oorspronkelijke implementatie van printDocument in klasse Node

Je merkt hierbij dat de buitenste if-conditie duidelijk gebruik maakt van type-attribuut Node::type_.

We zijn er bijna! Nog even testen om na te gaan of onze testen bevestigen dat het systeem nog correct werkt.

Herstructureer zelfstandig Node::printDocument d.m.v. eerder geziene stappen voor Transform Self Type Check. Ter herinnering: de toepasbare stappen zijn: (i) declareren van de te overschrijven methode als virtual; en (ii) overschrijven van de methode in de gepaste subklasse(n). Vereenvoudig in een laatste stap ook nog de overbodige if-structuur.

Je zou nu tot volgend resultaat moeten gekomen zijn:


Declaratie van printDocument ter ondersteuning van Transform Self Type Check


Implementatie van printDocument in subklasse Printer na Transform Self Type Check


Implementatie van printDocument na Transform Self Type Check

Als de testen nu ook nog aangeven dat je systeem nog correct werkt dan is de sessie ... geslaagd!!!!

Discussie

In deze discussie bespreken we niet enkel je ervaringen met Transform Self Type Check, maar veralgemenen we tot je ervaringen met alle geziene refactoring: "Hoe denk je nu over de toepassing van refactoring in je alledaagse werkomgeving?..."

Discussieer in groep over volgende discussie-punten:

  1. Vertrouwen:
    Hoe zeker ben je dat deze refactorings het gedrag van de lan-simulatie niet veranderd hebben? Welke neven-effecten kunnen er optreden bij het toepassen van refactoring? Zal je deze neven-effecten altijd opmerken?

  2. Nut:
    Vind je dat de refactorings die je in deze sessie hebt uitgevoerd de moeite lonen? Wat zijn de kosten (of nadelen) van deze refactorings, en wat zijn de geassocieerde baten (of voordelen)? Wanneer zou je dergelijke refactorings wel uitvoeren, en wanneer niet? Welke argumenten kan je gebruiken om de benodigde tijd bij je baas/klant los te weken?

  3. Toepasbaarheid:
    Bevat je huidige software-project opportuniteiten voor refactoring? Denk je dat het nuttig is om met refactoring te starten/verder te gaan?

  4. Tool-support:
    Heeft Eclipse je geholpen bij het uitvoeren van refactoring? Op welke manier kan een tool je bij refactoring helpen? Beschik je in je werkomgeving over dergelijke tools?

Hopelijk heb je flink wat opgestoken van deze sessie. Zelf vinden we de sessie geslaagd indien je tot nieuwe inzichten bent gekomen, in het bijzonder over de verdeling van verantwoordelijkheden overheen het ontwerp van je software systeem.

We zijn erg benieuwd naar je ervaringen, zowel je huidige feedback op de sessie die je zonet hebt uitgevoerd, als eventuele toekomstige ervaringen. Ook wanneer je problemen ondervindt bij toekomstige toepassingen van refactoring, kan je steeds eens polsen of wij soms raad weten. Je mailtjes naar bart dot dubois at ua dot ac dot be zijn steeds welkom! Veel succes, en vooral veel plezier!