Object-oriented software engineering: Example (for teaching purposes) of a refactoring case study based on a very simple Java example of a Local Area Network. Used as part of the software engineering and software evolution courses of the University of Mons, taught by Prof. Tom Mens, Software Engineering Lab.
Object-Oriented Software Engineering Refactoring Case Study
1. Object-Oriented
Software Engineering
Prof. Dr. Tom Mens
tom.mens@umons.ac.be
Software Engineering Lab
http://informatique.umons.ac.be/genlog
University of Mons
Belgium
Refactoring Case Study
2. 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
3. March 2016 Tom Mens, Service de Génie Logiciel, UMONS 3
workstation 1
fileserver 1
workstation 2printer 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
4. 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
5. 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);
}
}
6. 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)
7. 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
8. 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
9. 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)
10. 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());
}
11. 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); }
}
12. 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); }
}
13. 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
14. 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);
15. 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»
16. 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 }
}
17. 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»
«invoke»
«invoke»
«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);
18. 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); }
}
19. 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
20. 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
21. 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...
22. 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»
«invoke»
«invoke»
«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()));
23. 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...
24. 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
25. 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
{ ...
}
27. 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
29. 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;
}
30. 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)
32. March 2016 Tom Mens, Service de Génie Logiciel, UMONS 32
PART I : LAN case study
END OF
PART I
33. 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)
34. 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.
35. March 2016 Tom Mens, Service de Génie Logiciel, UMONS 35
workstation 1
action: send
CR6: Router connecting 2 LANs
router 1
Tom’s PCprinter 1
workstation 3
originator
fileserver 1
workstation 5
printer 3printer 4
addressee
36. 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
37. 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
38. 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
39. 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
40. 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
41. March 2016 Tom Mens, Service de Génie Logiciel, UMONS 41
workstation 1
action: send
CR9: Example of visual execution
router 1
Tom’s PCprinter 1
workstation 3
originator
fileserver 1
workstation 5
printer 3printer 4
addressee
42. 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
43. March 2016 Tom Mens, Service de Génie Logiciel, UMONS 43
PART II : Assignment
END OF
PART II