Deel III: Eliminate Navigation Code

Navigation code is code die expliciet gebruik maakt van de compositie-structuur van ander klassen, typisch door te navigeren naar de attributen van een object. We werken dergelijke navigation code liever weg, omdat veranderingen aan deze compositie-structuur ook een verandering aan de navigation code zullen vereisen. Bovendien vormt het exposeren van interne ontwerp-beslissingen zoals deze compositie-structuur een schending van het encapsulatie-principe.

Oefening III.1: Begeleide oefening op het elimineren van navigatie-code

Methode Network::requestWorkstationPrintsDocument biedt een goed voorbeeld van navigation code, zoals weergegeven in Fragment (e.1).


Fragment (e.1) uit requestWorkstationPrintsDocument voor Extract Method

Het type en de naam van het attribuut Node::name_ is een ontwerp-beslissing die gewijzigd moet kunnen worden op zo'n manier dat louter klasse Node aangepast moet worden. Dat is het voordeel van encapsulatie. Stel dat je het type van Node::name_ nu zou veranderen. Momenteel zou je ook Fragment (e.1) moeten wijzigen. Er is met andere een change dependency tussen Node::name_ en fragment (e.1). Via Eliminate Naviation Code kunnen we deze change dependency wegwerken.

Om navigation code te elimineren, voeren we typisch twee stappen uit:

  1. Extraheren van de navigatie code in een aparte methode.
  2. Verhuizen van de navigatie-methode naar de geschikte klasse.
We zullen deze stappen één na één en stap voor stap uitvoeren.

Extraheren van methode atDestination

In deze stap passen we eerst Extract Method toe, zoals gezien in Deel I. Eerst extraheren we een nieuwe methode Network::atDestination waarvan de signatuur hieronder weergegeven wordt:


Declaratie van geëxtraheerde methode atDestination in Network.h

Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

In deze (statische) methode Network::atDestination encapsuleren we de navigatie-code geïmplementeerd in het eerste deel van de complexe while-conditie uit fragment (e.1):


Implementatie van geëxtraheerde methode atDestination in Network.cpp

Merk op dat we de atDestination-methode uitdrukken als een gelijkheids-evaluatie (==), en dat we daarom in Fragment (e.2) een not-operator (!) voor de oproep van atDestination plaatsen.

Door het extraheren van deze methode kunnen we Network::requestWorkstationPrintsDocument vereenvoudigen, zoals hieronder weergegeven in Fragment (e.2):


Fragment (e.2) uit requestWorkstationPrintsDocument na Extract Method

Voer nu de testen uit om jezelf ervan te verzekeren dat je na de Extract Method nog steeds over een correct systeem beschikt.

Verhuizen van methode atDestination naar klasse Node

De tweede stap van Eliminate Navigation Code omvat het verhuizen van de methode naar de juiste locatie. Hiervoor voeren we opnieuw dezelfde stappen uit zoals eerder gezien bij de oefeningen op Move Method in Deel II.

Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Als eerste fase van het verhuizen van methode atDestination verplaatsen we de methode naar klasse Node:


Implementatie van geëxtraheerde methode atDestination in Node.cpp

Daarvoor moeten we methode requestWorkstationPrintsDocument opnieuw aanpassen:


Fragment (e.3) uit requestWorkstationPrintsDocument na Move Method fase 1

Voer nu de testen uit om jezelf ervan te verzekeren dat je na de eerste fase van de Move Method nog steeds over een correct systeem beschikt.

Als tweede fase van het verhuizen van methode atDestination maken we deze methode niet langer statisch, maar laten we ze uitvoeren op de voormalige parameter, die we nu kunnen verwijderen. Zo kunnen we de declaratie van Node::atDestination omvormen tot:


Declaratie van geëxtraheerde methode atDestination in Node.h

In de implementatie van Node::atDestination vervangen we het gebruik van de parameter node naar this:


Implementatie van geëxtraheerde methode atDestination in Node.cpp

Tenslotte passen we Network::requestWorkstationPrintsDocument opnieuw aan om met deze parameter-verwijdering rekening te houden:


Fragment (e.4) uit requestWorkstationPrintsDocument na Move Method fase 2

Voer nu de testen uit om jezelf ervan te verzekeren dat je na de tweede fase van Move Method nog steeds over een correct systeem beschikt.

Oefening III.2: Zelfstandige oefening op Eliminate Navigation Code

Fragment (e.4) bevat ook nog een tweede stuk navigation code in de while-conditie. In deze oefening zal je deze navigation code elimineren naar een methode atOrigin, die uiteindelijk ook in klasse Node moet belanden.

Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Voer dan de stappen uit die je in oefening III.1 gezien hebt.

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

Je oplossing van de oefening kan je hier evalueren.

Oefening III.3: Hergebruiken van de geëxtraheerde methode atDestination

De navigation code die je in oefening III.2 hebt geëlimineerd was eigenlijk ook een stuk duplicate code, aangezien ze ook nog in methode Network::requestWorkstationPrintsDocument gebruikt werd:


Fragment (f.1) uit requestWorkstationPrintsDocument vóór gebruik van atDestination

Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Vervang de navigation code in de if-conditie door een oproep naar de ge&extraheerde methode Node::atDestionation. Je zou dan volgend resultaat moeten bekomen:


Fragment (f.2) uit requestWorkstationPrintsDocument na gebruik van atDestination

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

Oefening III.4: Extraheren van het verzenden van een packet en verplaatsing naar de klasse Node

Deze oefening is eigenlijk geen pure oefening op Eliminate Navigation Code, maar eerder een oefening op Deel I en Deel II.

Wat we op dit moment trachten te verwezenlijken, is om zoveel mogelijk van de verantwoordelijkheden die niet tot Network behoren, maar wel door Network geïmplementeerd worden, te verhuizen naar klasse Node. Zo maken we van klasse Node een volwaardige klasse, die zelf zijn data (attributen zoals name_ en nextNode_) beheert. Van zodra de klasse Node "groot" genoeg is geworden, zullen we subklassen van klasse Node introduceren.

Laten we eerst methode Network::requestWorkstationPrintsDocument eens bestuderen:


Oorspronkelijke implementatie van methode requestWorkstationPrintsDocument in Network.cpp

We merken dat een behoorlijk deel van methode Network::requestWorkstationPrintsDocument in feite niets te maken heeft met de klasse Network, maar eerder met klasse Node. We zullen dus trachten om dit deel van Network::requestWorkstationPrintsDocument in een aparte methode te extraheren.

Verplaatsen van de methode Network::send naar klasse Node

Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Vooraleer te extraheren zullen we eerst de methode Network::requestWorkstationPrintsDocument wat herordenen:


Herordening van methode requestWorkstationPrintsDocument vóór Extract Method

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

Na de regel startNode = (Node*) workstations_[workstation]; is alle code van Network::requestWorkstationPrintsDocument de verantwoordelijkheid van een Node. Wat er hier eigenlijk gebeurd is het verzenden van een pakket doorheen alle nodes van het netwerk. Daarom kunnen we deze code beter extraheren in een aparte methode send, waarvan je hieronder zowel de declaratie als de implementatie ziet, alsook de wijziging die benodigd is aan methode Network::requestWorkstationPrintsDocument:


Declaratie van geëxtraheerde methode send in Network.h


Implementatie van geëxtraheerde methode send in Network.cpp


Implementatie van methode requestWorkstationPrintsDocument na Extract Method

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

Je merkt nu dat methode Network::requestWorkstationPrintsDocument heel wat eenvoudiger geworden is. Laten we in een volgende fase de methode Network::send verplaatsen naar waar ze eigenlijk thuishoort.

Verplaatsen van de methode Network::send naar klasse Node

Het uitvoeren van Move Method in twee fasen zal ondertussen erg vertrouwd aanvoelen. Zoniet kan je even terugduiken naar Deel II.

Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Verplaats de methode Network::send zelfstandig naar klasse Node. Hierna zou je code er als volgt moeten uitzien:


Implementatie van verplaatste methode send in Node.cpp


Implementatie van methode requestWorkstationPrintsDocument na Move Method

Oefening III.5: Opvullen van klasse Node met print-gerelateerde verantwoordelijkheden

Methode Network::printOn is een beetje in het zelfde bedje ziek als Network::requestWorkstationPrintsDocument: opnieuw is een groot deel van de code niet echt de verantwoordelijkheid van klasse Network, maar wel van klasse Node. We passen opnieuw volgende stappen toe:

  1. Extraheren van een methode die de verantwoordelijkheid die niet tot Network behoort encapsuleert.
  2. Verplaatsen van deze methode naar klasse Node.

Extraheren van een deel van de methode Network::printOn

Bestudeer eerst methode Network::printOn. Merk je al welk deel we kunnen extraheren?


Implementatie van printOn in Network.cpp

Voer eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

De gehele switch-case kan eigenlijk geëxtraheerd worden. Extraheer deze switch-case in een methode met naam printOn, die evenwel andere parameters heeft dan de huidige methode Network::printOn. Pas uiteraard ook de methode Network::printOn aan om de geëxtraheerde methode op te roepen:


Geëxtraheerde methode printOn in Network.cpp


Implementatie van printOn na extractie.

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

Zo, op dit moment is de geëxtraheerde methode klaar om verhuisd te worden naar klasse Node.

Verplaatsen van de geëxtraheerde methode Network::printOn naar klasse Node

Het verplaatsen van de methode bespreken we niet langer in detail, vermits je hier nu uitgebreide ervaring in hebt.

Voer desalniettemin eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Verplaats nu de methode geëxtraheerde Network::printOn naar klasse Node. Dit zou volgende resultaat moeten geven:


Implementatie van printOn in Node.cpp na Move Method fase 2


Implementatie van printOn in Network.cpp na move.

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

Analoge extractie en verplaatsing voor methode Network::printXMLOn

Voer, zoals gewoonlijk, eerst de testen uit om jezelf ervan te verzekeren dat je vóór het refactoren over een correct systeem beschikt.

Voer soortgelijke operaties uit voor printXMLOn en hergebruik de geëxtraheerde methode printOn in printHTMLOn.

We tonen eerst het resultaat voor de extractie en het verplaatsen van printXMLOn:


Implementatie van printOn in Node.cpp na Move Method fase 2


Implementatie van printOn in Network.cpp na move.

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

Het resultaat van het hergebruiken van de eerder geëxtraheerde methode en verplaatste methode Node::printOn in methode Network::printHTMLOn levert volgend resultaat op. Vergelijk deze oplossing met je eigen resultaat.


Implementatie van printOn in Network.cpp na reuse.

Voer de testen nogmaals uit om jezelf ervan te verzekeren dat je nog steeds over een correct systeem beschikt.

Discussie

Het toepassen van de Eliminate Navigation Code heb je uitvoerig kunnen oefenen in de voorbije vijf oefeningen. Laten we het opnieuw even hebben over de toepassing van deze refactoring in je alledaagse werkomgeving aan op basis van een discussie.

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 de Eliminate Navigation Code refactoring? Zal je deze neven-effecten altijd opmerken?

  2. Nut:
    Vind je dat de refactorings die je net hebt uitgevoerd (voor het elimineren van navigatie code) 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 navigatie code die change dependencies introduceren? Denk je dat het nuttig is om deze navigation code in je huidige project te encapsuleren en verplaatsen naar de juiste klasse?

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

Je zal nu merken dat we inderdaad een flink pak code van klasse Network naar klasse Node hebben overgeladen. Mooi zo! Nu kunnen we eindelijk verschillende soorten netwerk-knopen onderscheiden (door subklassen van klasse Node te introduceren), en de huidige Node-verantwoordelijkheden te herverdelen over deze soorten knopen.