Upload
tom-mens
View
471
Download
1
Embed Size (px)
Citation preview
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
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
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
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
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); } }
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)
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
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
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)
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());
}
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); } }
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); } }
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
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);
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»
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 } }
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);
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); } }
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
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
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...
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()));
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...
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
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 { ... }
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»
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
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»
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; }
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)
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)
March 2016 Tom Mens, Service de Génie Logiciel, UMONS 32
PART I : LAN case study
END OF PART I
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)
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.
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
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
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
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
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
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
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
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
March 2016 Tom Mens, Service de Génie Logiciel, UMONS 43
PART II : Assignment
END OF PART II