Deel II: Move Behaviour Close to the Data

Bij het extraheren van methoden kom je soms tot methoden die meer interageren met data of methoden van een andere klasse dan de klasse waarin die methode momenteel gelocalizeerd is. Typisch kan je dergelijke methoden zelfs statisch maken, zodat duidelijk wordt dat de methode niets gebruikt van de klasse. Een voorbeeld daarvan heb je gezien in oefening I.1, namelijk de methode sendPacket:


Behaviour far from data

Zoals je merkt in bovenstaande schets, is het gedrag geïmplementeerd in methode sendPacket ver verwijderd van de data die door sendPacket gebruikt wordt. Dit is een schending van het encapsulatie-principe, dat stelt dat entiteiten die vaak samen veranderen ook werkelijk samengebundeld moeten zijn. Immers, indien we het attribuut name_ uit klasse Node van type veranderen blijft deze verandering niet beperkt tot de klasse Node. We moeten dan namelijk ook klasse Network aanpassen, omdat de methode Network::sendPacket van het attribuut accept gebruik maakt.

Daarom verkiezen we om methode sendPacket, die louter data uit klasse Node gebruikt, naar klasse Node te verplaatsen:


Behaviour close to data

In dat geval zullen aanpassingen aan attribuut name_ niet langer een verandering aan klasse Network vereisen door een afhankelijkheid via methode sendPacket.

De refactoring die we hiervoor toepassen heet de Move Method refactoring.

Oefening II.1: Verplaatsen van de methode sendPacket naar de klasse Node

Deze oefening zal je stap voor stap begeleiden bij het verplaatsen van de methode. Als je deze oefening stap voor stap volgt, zal je in staat zijn de volgende oefening zelfstandig uit te voeren.

De implementatie van methode sendPacket, en de clients die deze methode oproepen zien er momenteel als volgt uit:


methode sendPacket vóór Move Method


sendPacket client 1 vóór Move Method


sendPacket client 2 vóór Move Method


sendPacket client 3 vóór Move Method

Het verplaatsen van methode sendPacket naar klasse Node voeren we uit in 2 fasen: (i) het fysiek verplaatsen naar de juiste klasse; en (ii) het aanpassen van de signatuur.

Move Method fase 1: De methode sendPacket fysiek verplaatsen naar klasse Node

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

Hiervoor moeten we volgende veranderingen doorvoeren:
  1. Verplaats de declaratie van methode sendPacket van file Network.h naar file Node.h. In deze fase blijft methode sendPacket een statische methode.

  2. Verplaats de implementatie van methode sendPacket van file Network.cpp naar file Node.cpp, en pas de signatuur aan van Network::sendPacket naar Node::sendPacket. Methode sendPacket ziet er dan als volgt uit:


    sendPacket na fase 1 van Move Method

  3. Pas de 3 clients van methode sendPacket aan door de qualificatie aan te passen van Network::sendPacket naar Node::sendPacket.

Merk op: Hiervoor zal je enkele afhankelijkheden naar library-headers moeten aanpassen:

  1. Header file Node.h zal afhankelijk worden van library-header <sstream> door de referentie naar het type ostream in de signatuur van methode sendPacket. Daarom moet je volgende lijnen toevoegen aan de hoofding van Node.h:

    #include <sstream>
    using std::ostream;

  2. Source file Node.cpp zal voor het eerst gebruik maken van std::endl. Daarom moet je using std::endl; toevoegen aan de hoofding van Node.cpp.

Voer nu de testen uit om jezelf ervan te verzekeren dat je na het verplaatsen van de methode nog steeds over een correct systeem beschikt.

Move Method fase 2: De signatuur van methode sendPacket aanpassen

Nu de methode sendPacket een methode van klasse Node is, is het vreemd dat deze enerzijds een attribuut van parameter sendingNode (van type Node*) refereert, en anderzijds een statische methode van klasse Node is. Om aan te geven dat sendPacket wel degelijk afhangt van een instantie van een Node, kunnen we daarom beter methode sendPacket oproepen op de instantie sendingNode, zodat we sendingNode niet langer als parameter moeten meegeven.

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

Daartoe moeten we volgende veranderingen toepassen:

  1. Verwijder het keyword static uit de declaratie van methode sendPacket in de header-file Node.h.

  2. Verwijder de parameter sendingNode uit de signatuur van methode sendPacket zowel in de declaratie in header-file Node.h als in de implementatie in source-file Node.cpp. Aangezien we methode sendPacket op een instantie van het type Node zullen oproepen, moeten we het attribuut name_ niet langer refereren via de parameter sendingNode, maar via this. Daardoor ziet de signatuur van methode sendPacket er als volgt uit:


    sendPacket na Move Method

  3. Pas de 3 clients van methode sendPacket aan zodat ze niet langer een Node* variabele als parameter meegeven, maar wel de methode sendPacket rechtstreeks op die Node* variabele oproepen. De clients zien er dan als volgt uit:


    sendPacket client 1 na Move Method


    sendPacket client 2 na Move Method


    sendPacket client 3 na Move Method

Voer nu de testen uit om jezelf ervan te verzekeren dat je na het verplaatsen van de methode nog steeds over een correct systeem beschikt.

Op dit moment heb je de verantwoordelijkheid, geïmplementeerd in methode sendPacket van klasse Network naar klasse Node verplaatst. Zo heb je de grote, complexe classe Network een beetje kleiner en eenvoudiger gemaakt, en de klasse Node, die eigenlijk louter een data-klasse was, een echte verantwoordelijkheid toegekend.

Oefening II.2: Verplaatsen van de methode accountForPrintJob

De methode Network::accountForPrintJob maakt geen gebruik van attributen van klasse Network. In dit geval omvat accounting het loggen van een print job, wat een verantwoordelijkheid van een Printer is. Aangezien het concept van een Printer niet bestaat, maar dit slechts één van de vormen van een Node is, zullen we deze methode naar de klasse Node verplaatsen.

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

Voer de eerste fase van Move Method refactoring uit, net als in oefening II.1, om methode accountForPrintJob te verplaatsen. Aangezien accountForPrintJob zijn doelklasse niet als parameter heeft kan je fase 2 niet uitvoeren.

Voer nu de testen uit om jezelf ervan te verzekeren dat je na het verplaatsen van de methode nog steeds over een correct systeem beschikt.

Indien je hiermee klaar bent kan je je oplossing hier evalueren.

Oefening II.3: Verplaatsen van methode printDocument naar klasse Node

Methode Network::printDocument implementeert de functionaliteit van een printer. Daardoor steunt deze methode sterk op data van de klasse Node.

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

Voer beide phasen van Move Method uit zoals in oefening II.1, om methode printDocument te verplaatsen naar de Node klasse.

Merk op: Klasse lanSimulation::internals::Packet wordt nog niet gebruikt in Node.h, en moet dus geïncludeerd worden.

Voer na het verplaatsen van de methode printDocument de testen uit om jezelf ervan te verzekeren dat je nog steeds over een correct systeem beschikt.

Je kan opnieuw je oplossing evalueren op deze pagina.

Discussie

In oefeningen II.1, II.2 en II.3 heb je uitvoerig de Move Method refactoring uitgevoerd. Opnieuw snijden we 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 Move Method refactoring? Zal je deze neven-effecten altijd opmerken?

  2. Nut:
    Vind je dat de refactorings die je net hebt uitgevoerd (voor het verplaatsen van gedrag naar de data) 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 gedrag dat ver van de data staat? Denk je dat het nuttig is om deze verantwoordelijkheden in je huidige project te herverdelen over de klassen?

  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?

Op dit moment heb je de Move Method refactoring onder de knie. Deze methode zullen we in combinatie met Extract Method verder toepassen in de volgende reeks oefeningen op Eliminate Navigation Code.