Refactor case study LAN example

43
Object-Oriented Software Engineering Prof. Dr. Tom Mens [email protected] Software Engineering Lab http://informatique.umons.ac.be/genlog University of Mons Belgium Refactoring Case Study

Transcript of Refactor case study LAN example

Page 1: Refactor case study LAN example

Object-Oriented Software Engineering

Prof. Dr. Tom Mens [email protected] Software Engineering Lab http://informatique.umons.ac.be/genlog University of Mons Belgium

Refactoring Case Study

Page 2: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 2

PART I : LAN case study

Ø Explanation of several concrete refactorings based on the evolution of a Local Area Network simulation implemented in Java

Page 3: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 3

workstation 1

fileserver 1

workstation 2 printer 1

workstation 3

1. originate(p)

2. send(p)

3. accept(p)

4. send(p)

5. accept(p)

6. send(p) 7. accept(p)

8.print(p)

Running example: LAN simulation

Page 4: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 4

UML class diagram

accept(p:Packet) originate(p:Packet)

Workstation

contents

Packet

accept(p:Packet) send(p:Packet)

Node originator

name

accept(p:Packet) print(p:Packet)

PrintServer

accept(p:Packet) save(p:Packet)

FileServer

addressee nextNode

Page 5: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 5

Java source code

public class Node { public String name; public Node nextNode; public void accept(Packet p) { this.send(p); } protected void send(Packet p) { nextNode.accept(p); } }

public class Packet { public String contents; public Node originator; public Node addressee; }

public class Printserver extends Node { public void print(Packet p) { System.out.println(p.contents); } public void accept(Packet p) { if (p.addressee == this) this.print(p); else super.accept(p); } }

public class Workstation extends Node { public void originate(Packet p) { p.originator = this; this.send(p); } public void accept(Packet p) { if (p.originator == this) System.err.println("no destination"); else super.accept(p); } }

Page 6: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 6

General Approach

Ø  Incrementally add new functionality in a two-phase process: Ø Restructure the code to make it easier to add the functionality Ø Add the required functionality

Ø Problem Ø How can we be sure that the existing behaviour is preserved

after the new functionality has been added? Ø Solution

Ø Use regression tests (unit tests)

Page 7: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 7

Revised Approach

Ø  Interleave the two-phase process with unit testing:

Ø Write unit tests (regression tests) that test the original behaviour Ø Restructure the code to make it easier to add the functionality Ø Check if the unit tests still work (and modify them if not) Ø Add the required functionality Ø Check if the tests still work (and modify them if not)

Ø Step 3 is necessary because refactorings can affect the unit tests

Page 8: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 8

Change requests

Ø CR1: Add logging functionality to the Node class Ø CR2: Add two kinds of Printservers

Ø ASCIIPrintserver and PostscriptPrintserver Ø CR3: Separate Documents from Printservers

Ø Allow ASCIIDocument to be printed on ASCIIPrinter and PostscriptPrinter

Ø add new kind of PDFDocument that can be printed on a PDFPrinter and PostscriptPrinter

Page 9: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 9

Change Request 1

Ø  Add logging behaviour to basic LAN example. Ø  In a simplistic increment, we can simply add the following code before

each send: System.out.println( name + "sends to" + nextNode.name);

Ø  To make it more reusable, we perform a refactoring first: Ø  encapsulate all variables that are used more than once

by introducing accessor methods Ø  e.g. replace

public String name

by private String name public String getName() public void setName(String n)

Page 10: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 10

CR1: Add logging functionality

Ø As a second step, we add the logging functionality in a separate log method

Ø e.g. replace protected void send(Packet p) { this.log();

this.getNextNode().accept(p);

}

protected void log() { System.out.println(

this.getName() + "sends to" +

getNextNode().getName());

}

Page 11: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 11

CR1: Refactoring Encapsulate Field

Fowler 1999, page 206 There is a public field

Make it private and provide accessors public class Node { private String name; private Node nextNode; public String getName() { return this.name; } public void setName(String s) { this.name = s; } public Node getNextNode() { return this.nextNode; } public void setNextNode(Node n) { this.nextNode = n; } public void accept(Packet p) { this.send(p); } protected void send(Packet p) { System.out.println( this.getNextNode().accept(p); } }

public class Node { public String name; public Node nextNode; public void accept(Packet p) { this.send(p); } protected void send(Packet p) { System.out.println( nextNode.accept(p); } }

Page 12: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 12

CR1: Add log method

public class Node { private String name; private Node nextNode; public String getName() { return this.name; } public void setName(String s) { this.name = s; } public Node getNextNode() { return this.nextNode; } public void setNextNode(Node n) { this.nextNode = n; } public void accept(Packet p) { this.send(p); } protected void log() { System.out.println( this.getName() + "sends to” + getNextNode().getName()); } protected void send(Packet p) { this.log(p); this.getNextNode().accept(p); } }

public class Node { private String name; private Node nextNode; public String getName() { return this.name; } public void setName(String s) { this.name = s; } public Node getNextNode() { return this.nextNode; } public void setNextNode(Node n) { this.nextNode = n; } public void accept(Packet p) { this.send(p); } protected void send(Packet p) { this.getNextNode().accept(p); } }

Page 13: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 13

Change request 2

Ø Add behaviour for distinguishing between two kinds of Printserver: Ø An ASCIIPrinter that can only print

plain ASCII text files Ø A PostscriptPrinter that can print

both Postscript and ASCII text

Page 14: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 14

CR2: Initial situation Node «class»

accept «operation»

Printserver «class»

accept «operation»

print «operation»

«inherit»

«invoke»

super «invoke»

if (p.addressee == this) this.print(p); else super.accept(p);

this.send(p);

Page 15: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 15

• Add 2 subclasses ASCIIPrinter and PSPrinter of Printserver •  Add 2 methods isASCII and isPS in Printserver to check whether the contents of the Packet to be printed is in the desired format

•  Push down methods print and accept (see next slide)

CR2: Step 1 Node «class»

accept «operation»

Printserver «class» accept «operation»

print «operation»

«inherit»

«invoke»

«invoke»

PSPrinter «class»

isASCII «operation»

isPS «operation»

ASCIIPrinter «class»

«inherit» «inherit»

Page 16: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 16

CR2: Step 2 – Refactoring Push Down Method

Fowler 1999, page 328 Behaviour on a superclass is relevant only for some of its subclasses

Move it to those subclasses public class Printserver extends Node { ... public void print(Packet p) { System.out.println( "Printing packet with contents" + p.getContents()); } public void accept(Packet p) { if (p.getAddressee() == this) this.print(p); else super.accept(p); } } public class ASCIIPrinter extends Printserver { ... } public class PSPrinter extends Printserver { ... }

public class Printserver extends Node { ... } public class ASCIIPrinter extends Printserver { ... public void print(Packet p) { System.out.println( "Printing packet with contents" + p.getContents()); } public void accept(Packet p) { if (p.getAddressee() == this) this.print(p); else super.accept(p); } } public class PSPrinter extends Printserver { ... public void print(Packet p) { // same body as above } public void accept(Packet p) { // same body as above } }

Page 17: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 17

• Create abstract method print in Printserver • Add extra calls from accept in both Printserver subclasses to isASCII and isPS •  Change print functionality in both Printserver subclasses

PSPrinter «class» ASCIIPrinter «class»

CR2: Step 3 Node «class»

accept «operation»

Printserver «class»

print «operation»

«inherit» «invoke»

accept «operation»

print «operation»

accept «operation»

print «operation» «inv

oke»

«inv

oke»

«invoke»

«invoke»

«invoke»

«invoke»

isASCII «operation»

isPS «operation»

«inherit» «inherit»

if (p.getAddressee() == this) if (this.isPS(p.getContents())) this.print(p); else super.accept(p);

Page 18: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 18

CR2: Resulting situation

public class ASCIIPrinter extends Printserver { ... public void print(Packet p) { System.out.println( "Printing packet with contents " + p.getContents() + " on ASCII printer " + this.getName()); } public void accept(Packet p) { if (p.getAddressee() == this) { if (this.isAscii(p.getContents())) this.print(p); } else super.accept(p); } }

public class PSPrinter extends Printserver { ... public void print(Packet p) { System.out.println( "Printing packet with contents " + this.interpretString(p.getContents()) + " on postscript printer " + this.getName()); } public void accept(Packet p) { if (p.getAddressee() == this) { if (this.isPostscript(p.getContents())) this.print(p); } else super.accept(p); } }

Page 19: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 19

Change request 3

Ø  Add a new kind of printer, PDFprinter, that can print either PDF documents or (after conversion) PS documents

Ø  Problem Ø  different types of printers can process different types of

documents, but this is hard-coded in the printer implementation Ø  E.g., an ASCII file can be printed on a AsciiPrinter or

PostscriptPrinter, a PS file can be printed on a PostscriptPrinter or PDFPrinter

Ø  Solution a)  Increase flexibility by decoupling the kinds of documents from

the kinds of printers b)  Introduce Document class hierarchy

Page 20: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 20

CR3a - Refactorings

Ø Refactor the commonalities in the behaviour of the accept method in ASCIIPrinter and PSPrinter Ø Consolidate Conditional Expression Ø Extract Method Ø Pull Up Method

Page 21: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 21

CR3a: Step 1 – Refactoring Consolidate Conditional Expression

Fowler 1999, page 240 You have a sequence of conditional tests with the same result

Combine them into a single conditional expression and extract it

public class ASCIIPrinter extends Printserver { ... public void accept(Packet p) { if (p.getAddressee() == this) { if (this.isASCII(p.getContents())) this.print(p); } else super.accept(p); } }

public class ASCIIPrinter extends Printserver { ... public void accept(Packet p) { if ((p.getAddressee() == this) && (this.isASCII(p.getContents()))) this.print(p); else super.accept(p); } }

and similarly for PSPrinter...

Page 22: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 22

Use the Extract Method refactoring to factor out the conditional expression into a separate method isDestFor (see also next slide)

PSPrinter «class» ASCIIPrinter «class»

CR3a: Step 2 – Refactoring Extract Method

Node «class»

accept «operation»

Printserver «class» accept «operation»

print «operation»

«inherit»

accept «operation»

print «operation»

accept «operation»

print «operation»

«inv

oke»

«inv

oke»

«invoke»

«invoke»

«invoke»

«invoke»

isASCII «operation»

isPS «operation»

«inherit» «inherit»

isDestFor «operation» isDestFor «operation»

if (this.isDestFor(p)) this.print(p); else super.accept(p);

return (p.getAddressee() == this) && (this.isASCII(p.getContents()));

Page 23: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 23

CR3a: Step 2 – Refactoring Extract Method

Fowler 1999, page 110 You have a code fragment that can be grouped together

Turn the fragment into a method whose name explains the purpose of the method

public class ASCIIPrinter extends Printserver { ... public void accept(Packet p) { if ((p.getAddressee() == this) && (this.isASCII(p.getContents()))) this.print(p); else super.accept(p); } }

public class ASCIIPrinter extends Printserver { ... public void accept(Packet p) { if (this.isDestFor(p)) this.print(p); else super.accept(p); } public boolean isDestFor(Packet p) { return ((p.getAddressee() == this) && (this.isASCII(p.getContents()))); } }

and similarly for PSPrinter...

Page 24: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 24

PSPrinter «class» ASCIIPrinter «class»

CR3a: Step 3 – Refactoring Pull Up Method

Node «class»

accept «operation»

Printserver «class» accept «operation»

print «operation»

«inherit» «invoke»

print «operation» print «operation»

«invoke»

«invoke»

«invoke»

isASCII «operation»

isPS «operation»

«inherit» «inherit»

isDestFor «operation» isDestFor «operation»

isDestFor «operation»

«invoke»

•  Use the Pull Up Method refactoring to move the identical implementation of accept in both subclasses to the common parent Printserver •  Specify isDestFor as an abstract method in PrintServer

Page 25: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 25

CR3a: Step 3 – Refactoring Pull Up Method

Fowler 1999, page 322 You have methods with identical results on subclasses

Move them to the superclass public abstract class Printserver { ... } public class ASCIIPrinter extends Printserver { ... public void accept(Packet p) { if (this.isDestinationFor(p)) this.print(p); else super.accept(p); } } public class PSPrinter extends Printserver { ... public void accept(Packet p) { if (this.isDestinationFor(p)) this.print(p); else super.accept(p); } }

public abstract class Printserver { ... public void accept(Packet p) { if (this.isDestinationFor(p)) this.print(p); else super.accept(p); } abstract boolean isDestFor(Packet p); } public class ASCIIPrinter extends Printserver { ... } public class PSPrinter extends Printserver { ... }

Page 26: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 26

CR3a: Intermediate Result

Packet «class»

«invoke» «inherit» «inherit»

String getContents() «op» { return this.contents }

void setContents(String c) «op» { this.contents = c } Printserver «class»

boolean isASCII(String s) «op»

boolean isPS (String s) «op»

PSPrinter «class» void print(Packet p) «op» { … p.getContents() … }

boolean isDestFor(Packet p) «op» { …this.isPS(p.getContents()… }

ASCIIPrinter «class» void print(Packet p) «op» { … p.getContents() … }

boolean isDestFor(Packet p) «op» { …this.isASCII(p.getContents()… }

String contents «attribute»

Page 27: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 27

CR3b Add Document hierarchy

Ø Refactoring Replace data value with object to introduce Document class

Ø Make entire Document hierarchy Ø Implement methods in this hierarchy

Page 28: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 28

CR3b: Step 1 – Refactoring Replace data value with object

Packet «class»

«invoke» «inherit» «inherit»

String getContents() «op» { return this.doc.contents }

void setContents(String c) «op» { this.doc.contents = c } Printserver «class»

boolean isASCII(String s) «op»

boolean isPS (String s) «op»

PSPrinter «class» void print(Packet p) «op» { … p.getContents() … }

boolean isDestFor(Packet p) «op» { …this.isPS(p.getContents()… }

ASCIIPrinter «class» void print(Packet p) «op» { … p.getContents() … }

boolean isDestFor(Packet p) «op» { …this.isASCII(p.getContents()… }

Document «class»

String contents «attribute» Document doc «attribute»

Page 29: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 29

CR3b: Step 1 – Refactoring Replace data value with object

Fowler 1999, page 175 You have a data item that needs additional data or behaviour

Turn the data item into an object

public class Packet { ... private String contents; public Packet(String s, Node n) { this.setContents(s); ... } public String getContents() { return this.contents; } public void setContents(String s) { this.contents = s; } }

public class Packet { ... private Document doc; public Packet(String s, Node n) { this.setContents(s); ... } public String getContents() { return this.doc.contents; } public void setContents(String s) { this.doc.contents = s; } } public class Document { ... public String contents; }

Page 30: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 30

CR3b: Step 2 – Refactoring Use double dispatch

Ø Printing a document depends not only on the type of Printserver, but also on the type of Document Ø for each type of Document, define a different print

method (e.g., printASCII, printPS) Ø each type of printer (e.g., ASCIIPrinter, PSPrinter)

provides its own specific implementation for (some of) these methods

Ø Use double dispatch … … if you have a dispatched interpretation between two

families of objects ([Beck 1997], page 55)

Page 31: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 31

CR3b: Resulting situation

«invoke»

«inherit» «inherit»

Printserver «class»

void printASCII(ASCIIDocument d)

void printPS(PSDocument d)

PSPrintserver «class» void printPS(PSDocument d)

ASCIIPrintserver «class» void printASCII(ASCIIDocument d)

boolean isDestFor(Packet p) { …}

void accept(Packet p) «op» { if (this.isDestFor(p)) p.doc.printOn(this) …}

Document «class» String contents «attribute»

abstract void printOn(Printserver p) «op»

ASCIIDocument «class» void printOn(Printserver p) «op»

{ p.printASCII(this) }

«inherit»

PSDocument «class» void printOn(Printserver p) «op»

{ p.printPS(this) }

void printASCII(PSDocument d)

Page 32: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 32

PART I : LAN case study

END OF PART I

Page 33: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 33

Part II: Assignment

Ø  Implement change requests of the LAN examples using the proposed two-phase process interleaved with unit testing:

Ø Write unit tests (regression tests) that test the original behaviour Ø Restructure the code to make it easier to add the functionality Ø Check if the unit tests still work (and modify them if not) Ø Add the required functionality Ø Check if the tests still work (and modify them if not)

Page 34: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 34

Change Requests ctd.

Ø CR4: Add file servers Ø When these special kinds of nodes accept a packet, they store

its contents as a file on disk. Ø CR5: Acknowledge receipt of packets

Ø When a packet is accepted by the node to which it was addressed, this node should send a message back to the sender of the packet to acknowledge its receipt.

Ø CR6: Provide routers Ø  These are special kinds of nodes that link two LANs together.

Packets can be sent to nodes in the same LAN, or to nodes in another LAN.

Ø As many routers can be added as needed.

Page 35: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 35

workstation 1

action: send

CR6: Router connecting 2 LANs

router 1

Tom’s PC printer 1

workstation 3

originator

fileserver 1

workstation 5

printer 3 printer 4

addressee

Page 36: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 36

CR7: New kinds of packets

Ø Add 3 new kinds of packets: Ø  counting and collecting packets traverse the entire

network and process all nodes in the network Ø  a counting packet counts all nodes of each type (workstations,

asciiprinters, psprinters, …) found in the network Ø  a collecting packet collects the addresses of each type of node

(workstations, asciiprinters, psprinters, …) found in the network Ø  broadcasting packets are processed by multiple nodes

in the network Ø Up to now, when a packet reaches its addressee node, the packet

is handled and the transmission of the packet is terminated. With broadcasting, a packet can be sent to more than one addressee node in the network at the same time. For example, broadcasting makes it possible to save the contents of the same packet on different fileservers of the LAN, or to print the contents of a packet on all printers in the network

Page 37: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 37

Change Requests ctd.

Ø CR8: Add user interface for input Ø CR9: Add visual execution (output) Ø CR10: Allow for other kinds of network

structures

Page 38: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 38

CR8: Add User Interface

Ø Add a user interface for dynamically creating/modifying a LAN Ø Creating new nodes Ø Inserting/removing nodes from the LAN Ø Changing the order of nodes in the LAN Ø Creating new packets Ø Executing the LAN by originating a given packet from

a given workstation

Page 39: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 39

CR8: Add User Interface

Local Area Network Creator

Available nodes !Workstations - Tom’s PC - Dirk’s Mac !Fileservers - fs2 !Printers !ASCII - Ascii Printer 2 !Postscript - Laserwriter 2 - Laserwriter 3

Nodes in the LAN - Serge’s PC - workstation 2 -  Ascii Printer 1 -  Laserwriter 1 insert

remove

Add Remove

Execute

Packets in the LAN - p1 - p2 -  p3 -  p4

Add Remove

Page 40: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 40

CR9: Add a visual execution

Ø Add a visual interface that simulates the execution of the network Ø It displays all nodes in the network and their

connections Ø It shows how packets traverse the network Ø It shows the actions performed on each node

Page 41: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 41

workstation 1

action: send

CR9: Example of visual execution

router 1

Tom’s PC printer 1

workstation 3

originator

fileserver 1

workstation 5

printer 3 printer 4

addressee

Page 42: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 42

CR10: New kinds of networks

Ø Allow for other kinds of networks than token ring networks such as: Ø Star network Ø Bidirectional token ring network

Page 43: Refactor case study LAN example

March 2016 Tom Mens, Service de Génie Logiciel, UMONS 43

PART II : Assignment

END OF PART II