KEMBAR78
Refactoring 2 | PDF | Method (Computer Programming) | Inheritance (Object Oriented Programming)
0% found this document useful (0 votes)
9 views127 pages

Refactoring 2

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views127 pages

Refactoring 2

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 127

Data Clumps

1. Data items that are frequently together in method signatures


and classes belong to a class of their own.
2. attributes that clump together but are not part of the same
class

A test for a Data Clump: Delete one value and see if the others
still make sense.

1. Extract Class - turn related fields into a class.


2. Introduce Parameter Object / Preserve Whole Object - for
reducing method signatures.
3. Look for Feature Envy – Move Method.
Introduce parameter object
Primitive Obsession

Primitive types inhibit change. characterized by a reluctance to


use classes instead of primitive data types

 Replace Data Value with Object - on individual data values.


 If a primitive value is a type code:
 Replace type Code with Class – The value does not affect behavior.
 Conditionals on the type code –
 Replace Type Code with Subclasses.
 Replace Type Code with State/Strategy.
 Extract Class – a set of inter-related value fields.
 Introduce Parameter Object - for method signatures.
 Replace Array with Object - to get rid of arrays of dissimilar
objects.
Replace Data value with object
Replace Array with object
Switch Statements

Switch statements lead to Code Duplication and inhibit change.


Object-Oriented switch = Polymorphism.
Switch statements are often duplicated in code; they can typically be
replaced by use of polymorphism (let OO do your selection for you!)

If the switch is on a type code:


1. Extract method - to extract the switch.
2. Move method - to get the method where polymorphism applies.
3. Replace Type Code with State/Strategy / Replace Type Code with
Subclasses - set up inheritance
4. Replace Conditional with Polymorphism - get rid of the switch.

Few cases that affect a single method; Cases are stable; try:
 Replace Parameter with Explicit Methods – if the switch value is a
method parameter.
 Introduce Null Object – If there is a conditional case comparing with
null.
Replace Type Code with Polymorphism
Replace Type Code with Class
Replace conditional with polymorphism
Replace conditional with polymorphism
Replace type code with state/strategy
State versus Strategy
 The difference simply lies in that they solve different
problems:
 The State pattern deals with what (state or type) an
object is (in) -- it encapsulates state-dependent behavior,
whereas
 the Strategy pattern deals with how an object performs a
certain task -- it encapsulates an algorithm.
State
Problem
Solution (Replace Type Code with Class)
Problem
Solution(Replace type code with state)
Problem
Solution(Replace type code with strategy)
Replace subclasses with fields

You have subclasses that vary only in methods that return constant data.
Parallel Inheritance Hierarchies

Similar to Shotgun Surgery; each time I add a subclass to one


hierarchy, I need to do it for all related hierarchies

If when ever you make a subclass in one corner of the


hierarchy, you must create another subclass in another
corner  Duplication.
Make sure that instances of one hierarchy refer to instance of
the other.
Example: Rental  Movie hierarchies.

 Move Method/Field – might remove the referring


hierarchy.
Parallel Inheritance Hierarchies
Parallel Inheritance Hierarchies
Bad Smells in Code
Lazy Class
 Each class costs money to maintain and understand.
 A class that no longer “pays its way”
 e.g. may be a class that was downsized by refactoring, or
represented planned functionality that did not pan out

If a class (e.g. after refactoring) does not do much, eliminate it.

 Collapse Hierarchy- for subclasses.


 Inline Class - remove a single class.
Collapse hieararchy
Inline Class
Bad Smells in Code
Speculative Generality
“Oh I think we need the ability to do this kind of thing
someday”

If a class has features that are only used in test cases, remove them (and
the test case)..
Think TDD!

 Collapse Hierarchy- for useless abstract classes.


 Inline Class - for useless delegation.
 Remove Parameter – methods with unused parameters.
 Rename Method - methods with odd abstract names should be
brought down to earth.
Temporary Field
An attribute of an object is only set in certain
circumstances; but an object should need all of its
attributes

1. Temporary fields get their values (and thus are needed by


objects) only under certain circumstances. Outside of these
circumstances, they are empty.
2. The Temporary Field smell means a case in which a variable
is in the class scope, when it should be in method scope.
This violates the information hiding principle.
Temporary Field (Solution)
If a class has fields that are only set in special cases, extract
them.
 Extract Class –
 For the special fields and the related methods.
 For fields that serve as variables of a complex algorithm – only
relevant when the algorithm is applied. The resulting object is a
Method Object (Replace Method with Method Object).

 Introduce Null Object – alternative component, when the fields


are not valid.
Introduce Null Object
Message Chains
A client asks an object for another object and then asks that
object for another object etc. Bad because client depends on
the structure of the navigation

Long chains of messages to get to a value are brittle as any


change in the intermittent structure will break the client
code.
Identified by:
 A long line of getters.
 A sequence of temps.

 Hide Delegate - remove a link in a chain.


 Extract Method + Move Method – push the code that uses
the chained objects, down the chain.
Hide Delegate
Hide Delegate (Solution)
Middle Man
If a class is delegating more than half of its
responsibilities to another class, do you really need it?
1. An intermediary object is used too often to get at
encapsulated values.
2. Too many methods are just delegating behavior.

 Remove Middle Man - to talk directly to the target.


 Inline Method – inline the delegating methods in their clients
– if only few delegating methods.
 Replace Delegation with Inheritance - turn the middle man
into a subclass of the target object.
 Only if all methods of the target class are used by the Middle Man.
Inappropriate Intimacy

Pairs of classes that know too much about each other’s private
details

 Move Method/Field - to separate pieces in order to reduce intimacy.


 Change Bidirectional Association to Unidirectional – if relevant.
 Extract Class - make a common class of shared behavior/data.
 Hide delegate – Let another class act as a go-between.
 Replace Inheritance with Delegation - when a subclass is getting too
cozy with its parents.
Change bidirectional association to
delegation
Change bidirectional association to
delegation
Data Class

These are classes that have fields, getting and setting


methods for the fields, and nothing else; they are data
holders, but objects should be about data AND behavior
Classes without behavior.
Natural in early stages of a system evolution.

1. Encapsulate Field.
2. Encapsulate Collection – for collection fields.
3. Remove Setting Method – for final fields.
4. Move Method – from client classes to the data class.
 Extract Method – if can’t move whole methods.
5. Hide Method – on getters and setters.
Encapsulate Field
Refused Bequest

A subclass ignores most of the functionality provided


by its superclass

A subclass refuses or does not need most of its heritage.


The hierarchy is wrong.

 Push Down Method / Push Down Field – create a sibling


class. Push all unused methods to the sibling  parent holds
only the common structure and functionality.
 Replace Inheritance with Delegation – get rid of wrong
hierarchy.
Bad Smells in Code

Comments
 Comments are sometimes used to hide bad code
 “…comments often are used as a deodorant” (!)

Comments are often a sign of unclear code... consider


refactoring

 Extract Method.
 Rename Method.
 Introduce Assertion.
Bad Smells in Code
 Alternative Classes with Different Interfaces
 Symptom:Two or more methods do the same thing
but have different signature for what they do
 Incomplete Library Class
 A framework class doesn’t do everything you need

87
The Catalog
 A few of the more common ones, include:
 Extract Method
 Replace Temp with Query
 Move Method
 Replace Conditional with Polymorphism
 Introduce Null Object

88
Extract Method
 You have a code fragment that can be grouped together
 Turn the fragment into a method whose name explains
the purpose of the fragment

91
Extract Method, continued
void printOwing(double amount) {
printBanner()
//print details
System.out.println(“name: ” + _name);
System.out.println(“amount: ” + amount);
}

void printOwing(double amount) {


printBanner()
printDetails(amount)
}

void printDetails(double amount) {


System.out.println(“name: ” + _name);
System.out.println(“amount: ” + amount);
92 }
Replace Temp with Query
 You are using a temporary variable to hold the result of
an expression
 Extract the expression into a method; Replace all
references to the temp with the expression. The new
method can then be used in other methods

94
Replace Temp with Query
double basePrice = _quantity * _itemPrice

if (basePrice > 1000)


return basePrice * 0.95;
else
return basePrice * 0.98;

if (basePrice() > 1000)


return basePrice() * 0.95;
else
return basePrice() * 0.98;

double basePrice() {
return _quantity * _itemPrice;
95 }
Move Method
 A method is using more features (attributes and
operations) of another class than the class on which it is
defined
 Create a new method with a similar body in the class it
uses most. Either turn the old method into a simple
delegation, or remove it altogether

96
Replace Conditional with Polymorphism
 You have a conditional that chooses different behavior
depending on the type of an object
 Move each “leg” of the conditional to an overriding
method in a subclass. Make the original method abstract

97
Replace Conditional with Polymorphism

double getSpeed() {
switch (_type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() *
_numberOfCoconuts;
case NORWEGIAN_BLUE:
return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
throw new RuntimeException(“Unreachable”)
}

98
Replace Conditional with Polymorphism

Bird

getSpeed()

European African Norwegian_Blue

getSpeed() getSpeed() getSpeed()

99
Introduce Null Object
 Repeated checks for a null value
 Replace the null value with a null object

if (customer == null) { Customer


name = “occupant”
} else { getName()
name = customer.getName()
}

if (customer == null) {
… Null Customer

getName()
100
Introduce Null Object

if (customer.isNull())
name = “occupant”;
else
name = customer.getName();

public class nullCustomer {


public String getName() { return “occupant”;}
}

customer.getName();
The conditional goes away entirely!!

101
When should you refactor?
 The Rule of Three
 Three strikes and you refactor
 refers to duplication of code
 Refactor when you add functionality
 do it before you add the new function to make it easier to add
the function
 or do it after to clean up the code after the function is added
 Refactor when you need to fix a bug
 Refactor as you do a code review

102
Managing Refactoring!
 Manager’s point-of-view
 If my programmers spend time “cleaning up the code” then
that’s less time implementing required functionality (and my
schedule is slipping as it is!)
 To address this concern
 Refactoring needs to be systematic, incremental, and safe.

103
Principles, continued
 When you systematically apply refactoring, you wear two
hats:
 adding function
 functionality is added to the system without spending any time
cleaning the code
 refactoring
 no functionality is added, but the code is cleaned up, made easier to
understand and modify, and sometimes is reduced in size

104
Problems with Refactoring

 Databases
 Business applications are often tightly coupled to
underlying databases
 code is easy to change; databases are not
 Changing Interfaces
 Some refactorings require that interfaces be changed
 if you own all the calling code, no problem
 if not, the interface is “published” and can’t change
 Design Changes that are difficult to refactor
 Better to redo than to refactor
 Software Engineers should have “courage”!

105
Tool Support
 JAVA
 Eclipse
 IntelliJ IDEA
 Borland JBuilder
 NetBeans
 RefactorIT plug-in for Eclipse, Borland JBuilder, NetBeans,
Oracle JDeveloper
 .NET
 IntelliJ Resharper for Visual Studio .NET
Recommended Reading
 Martin Fowler’s refactoring catalog
Refactoring Example

 A simple program for a video store


 Movie
 Rental
 Customer
 Program is told which movies a customer rented and for
how long and it then calculates:
 the charges
 Frequent renter points
 Customer object can print a statement (in ASCII)
 We want to add a new method to generate an HTML
statement

108
Initial Class Diagram
Movie Rental Customer
1 * * 1
priceCode: int daysRented: int

statement()

 statement works by looping through all rentals;


 for each rental, it retrieves its movie and the number of days it
was rented; it also retrieves the price code of the movie.
 it then calculates the price for each movie rental and the
number of frequent renter points and returns the generated
statement as a string;

109
public class Movie {
public static final int CHILDREN = 2;
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
private String _title;
private int _priceCode;
public Movie(String title, int priceCode) {
_title = title;
_priceCode = priceCode
}
public int getPriceCode() { return _priceCode; }
public String getTitle() { return _title; }
public void setPriceCode(int priceCode) {
_priceCode = priceCode;
}
}
110
class Rental {
private Movie _movie;
private int _daysRented;
public Rental(Movie movie, int daysRented) {
_movie = movie;
_daysRented = daysRented;
}
public int getdaysRented() { return _daysRented; }
public Movie getMovie() { return _movie; }
}
Movie Rental Customer
1 * * 1
priceCode: int daysRented: int

statement()
111
class Customer {
private String _name;
private Vector _rentals = new Vector();
public Customer(String name) { _name = name; }
public String getName() { return _name; }
public void addRental (Rental arg) {
_rentals.addElement(arg);
}
public String statement();
}

Movie Rental Customer


1 * * 1
priceCode: int daysRented: int

112
statement()
Initial Class Diagram
Movie Rental Customer
1 * * 1
priceCode: int daysRented: int

statement()

 statement works by looping through all rentals;


 for each rental, it retrieves its movie and the number of days it
was rented; it also retrieves the price code of the movie.
 it then calculates the price for each movie rental and the
number of frequent renter points and returns the generated
statement as a string;

113
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = “Rental record for ” + getName() + “\n”;

while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();

114
// determine amounts for each line
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
115
// add frequent renter points
frequentRenterPoints++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() ==
Movie.NEW_RELEASE)
&&
each.getDaysRented() > 1)
frequentRenterPoints++;
// show figures for this rental
result += “\t” + each.getMovie().getTitle() + “\t” +
String.valueOf(thisAmount) + “\n”;
totalAmount += thisAmount;

}
116 // end while
// add footer lines

result += “Amount owed is ” +


String.valueOf(totalAmount) + “\n”;
result += “You earned ” +
String.valueOf(frequentRenterPoints) + “\n”;
return result;
}

117
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = “Rental record for ” + getName() + “\n”;

while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
// determine amounts for each line

switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}

// add frequent renter points


frequentRenterPoints++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;

// show figures for this rental


result += “\t” + each.getMovie().getTitle() + “\t” + String.valueOf(thisAmount) + “\n”;
totalAmount += thisAmount;
}

// add footer lines


result += “Amount owed is ” + String.valueOf(totalAmount) + “\n”;
result += “You earned ” + String.valueOf(frequentRenterPoints) + “\n”;
return result;
118
}
Does it need refactoring?
 For such a simple system
 probably not
 But it smells bad - long method
 Besides, our purpose is to add a new method to generate an
HTML statement and refactoring statement() may lead to code
that can be used by the new function.
 matches one of Fowler’s conditions for refactoring: cleaning up
the code to make it possible to add new function

119
How to start?

 We want to decompose statement() into smaller


pieces which are easier to manage and move
around.
 We’ll start with “Extract Method”
 target the switch statement first

120
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}

Extract method
121
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}

Watch for local variables


122 each and thisAmount
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
 non-modifiable variable can be passed as parameter to new
method (if required)
 modified variables require more care; since there is only one, we
123 can make it the return value of the new method.
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}

each does not change but


124 thisAmount does
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}

so each is a parameter and value


125 is returned to thisAmount
private double amountFor(Rental each)
int thisAmount = 0;
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
return thisAmount;
}
126
Be careful!
 Pitfalls
 be careful about return types; in the original statement(),
thisAmount is a double, but it would be easy to make a mistake
of having the new method return an int. This will cause
rounding-off error.
 Always remember to test after each change.

127
look at the
private double amountFor(Rental each)
double thisAmount = 0; variable names
switch (each.getMovie().getPriceCode()) { and use more
case Movie.REGULAR: suitable ones
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
return thisAmount;
}
128
each and
private double amountFor(Rental each) thisAmount
double thisAmount = 0;
switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}
return thisAmount;
}
129
each  aRental
thisAmount  result
private double amountFor(Rental aRental)
double result = 0;
switch (aRental.getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (aRental.getDaysRented() > 2)
result += (aRental.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
result += aRental.getDaysRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (aRental.getDaysRented() > 3)
result += (aRental.getDaysRented() - 3) *1.5;
break;
}
return result;
130
}
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = “Rental record for ” + getName() + “\n”;

while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();

thisAmount = amountFor(each);

// the switch statement has been replaced by this


// call to amountFor

… the rest continues as before


131
private double amountFor(Rental aRental)
double result = 0;
switch (aRental.getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (aRental.getDaysRented() > 2)
result += (aRental.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
result += aRental.getDaysRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (aRental.getDaysRented() > 3)
result += (aRental.getDaysRented() - 3) *1.5;
break;
}
return result;
}
132
private double amountFor(Rental aRental)
double result = 0;
switch (aRental.getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (aRental.getDaysRented() > 2)
result += (aRental.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
result += aRental.getDaysRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (aRental.getDaysRented() > 3)
result += (aRental.getDaysRented() - 3) *1.5;
break;
} amountFor() uses information from the
return result; Rental class, but it does not use information
} from the Customer class where it is
133
currently located.
Move method
 Methods should be located close to the data they
operate.
 we can get rid of the parameter this way.
 let’s also rename the method to getCharge() to clarify what it
is doing.
 as a result, back in customer, we must delete the old method
and change the call to amountFor(each) to each.getCharge()
 compile and test

134
private double getCharge()
double result = 0;
switch (getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (getDaysRented() > 2)
result += (getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
result += getDaysRented() * 3;
break;
case Movie.CHILDREN:
result += 1.5;
if (getDaysRented() > 3)
result += (getDaysRented() - 3) *1.5;
break;
}
return result;
}
135
private double amountFor(Rental aRental)
return aRental.getCharge();
}
• initially replace the body with the call
• remove this method later on and call directly
while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();

//thisAmount = amountFor(each);

thisAmount = each.getCharge();

… the rest continues as before


136
New Class Diagram

Movie Rental Customer


1 * * 1
priceCode: int daysRented: int
getCharge() statement()

 No major changes; however, Customer is now a smaller


class and an operation has been moved to the class that
has the data it needs to do the job

137
frequent renter points
// add frequent renter points
frequentRenterPoints++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() ==
Movie.NEW_RELEASE)
&&
each.getDaysRented() > 1)
frequentRenterPoints++;
// show figures for this rental
result += “\t” + each.getMovie().getTitle() + “\t” +
String.valueOf(thisAmount) + “\n”;

138
totalAmount += thisAmount;

} // end while
frequent renter points

 Let’s do the same thing with the logic to calculate


frequent renter points
a) extract method
– each can be parameter
– frequentRenterPoints has a value before the method is invoked,
but the new method does not read it; we simply need to use
appending assignment outside the method.
b) move method
– Again, we are only using information from Rental, not
Customer, so let’s move getFrequentRenterPoints to the Rental
class.
 Be sure to run your test cases after each step

139
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = “Rental record for ” + getName() + “\n”;

while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
// determine amounts for each line

switch (each.getMovie().getPriceCode()) {
case Movie.REGULAR:
thisAmount += 2;
if (each.getDaysRented() > 2)
thisAmount += (each.getDaysRented() - 2) *1.5;
break;
case Movie.NEW_RELAESE:
thisAmount += each.getDaysRented() * 3;
break;
case Movie.CHILDREN:
thisAmount += 1.5;
if (each.getDaysRented() > 3)
thisAmount += (each.getDaysRented() - 3) *1.5;
break;
}

// add frequent renter points


frequentRenterPoints++;
// add bonus for two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints++;

// show figures for this rental


result += “\t” + each.getMovie().getTitle() + “\t” + String.valueOf(thisAmount) + “\n”;
totalAmount += thisAmount;
}

// add footer lines


result += “Amount owed is ” + String.valueOf(totalAmount) + “\n”;
result += “You earned ” + String.valueOf(frequentRenterPoints) + “\n”;
return result;
140
}
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = “Rental record for ” + getName() + “\n”;

while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
// determine amounts for each line

thisAmount = each.getCharge();
frequentRenterPoints += each.getFrequentRenterPoints();

result += “\t” + each.getMovie().getTitle() + “\t” +


String.valueOf(thisAmount) + “\n”;
totalAmount += thisAmount;
}

// add footer lines


result += “Amount owed is ” + String.valueOf(totalAmount) + “\n”;
result += “You earned ” + String.valueOf(frequentRenterPoints) + “\n”;
141
return result;
}
New Class Diagram
Rental Customer
* 1
daysRented: int
getCharge()
statement()
getFrequentRenterPoints()
1*

Movie
 Customer continues to get smaller,
priceCode: int
Rental continues to get larger; but
Rental now has operations that
change it from being a “data holder”
to a useful object.

142
Replace Temp with Query
 Removing temp variable is good thing because they often
cause the need for parameters where none are required
and can also cause problems in long methods

143
remove temp variables
 statement() has two temp variables
 totalAmount and frequentRentalPoints
 Both of these values are going to be needed by statement()
and htmlStatement()
 let’s replace them with query methods
 little more difficult because they were calculated within a loop;
we have to move the loop to the query methods
a) replace totalAmount with getTotalCharge()
b) replace frequentRentalPoints with getTotalFrequentPoints()
 test after each point

144
public String statement() {
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentals = _rentals.elements();
String result = “Rental record for ” + getName() + “\n”;

while (rentals.hasMoreElements()) {
double thisAmount = 0;
Rental each = (Rental) rentals.nextElement();
// determine amounts for each line

thisAmount = each.getCharge();
frequentRenterPoints += each.getFrequentRenterPoints();

result += “\t” + each.getMovie().getTitle() + “\t” +


String.valueOf(thisAmount) + “\n”;
totalAmount += thisAmount;
}

// add footer lines


result += “Amount owed is ” + String.valueOf(totalAmount) + “\n”;
result += “You earned ” + String.valueOf(frequentRenterPoints) + “\n”;
145
return result;
}
public String statement() {
Enumeration rentals = _rentals.elements();
String result = “Rental record for ” + getName() + “\n”;

while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();

// show figures for each rental


result += “\t” + each.getMovie().getTitle() + “\t” +
String.valueOf(each.getCharge()) + “\n”;
}

// add footer lines


result += “Amount owed is ” +
String.valueOf(getTotalCharge()) + “\n”;
result += “You earned ” +
String.valueOf(getTotalFrequentRenterPoints()) + “\n”;
return result;
}
146
Performance

 of course the charge is now calculated


twice through the loop
 we can optimize if we determine it is
slowing us down.

147
New Class Diagram
Rental Customer
* 1
daysRented: int
getCharge() statement()
getFrequentRenterPoints() getTotalCharge()
getTotalFrequentRentorPoints()
1*

Movie
• Customer class is now bigger; but has
priceCode: int two methods that can be shared with
the existing statement() and planned
htmlStatement() method.
• Now we have three loops instead of
one; performance can be a concern but
we should wait until a profiler tells us
so.
148
add htmlStatement()
 We are now ready to add the htmlStatement() function.

149
New Requirements
 It is now anticipated that the store is going to have more
than three initial types of movies
 as a result of these new classifications, renter points and
charges will vary with each new movie type.
 as a result, we should probably move the getCharge() and
getFrequentRenterPoints() methods to the Movie class.

150
move methods
• move getCharge to Movie
 getCharge needs to know the number of days the movie
was rented; since this is information that Rental has, it
needs to be passed as a parameter.
• move getFrequentRenterPoints() to Movie

151
New Class Diagram
Rental Customer
* 1
daysRented: int
getCharge() statement()
getFrequentRenterPoints() getTotalCharge()
getTotalFrequentRentorPoints()
1*

Movie
priceCode: int
getCharge(days: int)
getFrequentRenterPoints(days: int)  Movie has new methods; finally
making the transition from data
holder to object.
 These methods will allow us to
handle new types of movies
152 easily
How to handle new movies
Movie

getCharge()

Regular Movie Children Movie New Release Movie

getCharge() getCharge() getCharge()

 But movies can change type – a New Release Movie can later
become regular movie
 A movie has a particular state: its charge (and its renter points)
depend upon that state; so we can use the state pattern to handle
153 new types of movies (for now, at least)
Replace Type Code with State/Strategy

 We need to get rid of our type code (e.g. Movie.Children)


and replace it with a price object.
 We first modify Movie to get rid of its _priceCode field and
replace it with _priceObject
 This involves changing the customer to make use of the
setPriceCode() method; before it was setting priceCode directly.
 We also have to change getPriceCode and setPriceCode to access the
Price Object
 We of course need to create Price and its subclasses

154
move method
 Now we need to move the method getCharge to the
newly created Price class
 It is a very simple move, we just need to remember to change
Movie to delegate its getCharge operation to Price

155
Replace Conditional with Polymorphism
 Now we move each branch of the switch statement into
the appropriate subclass
 After you have done this, change Price’s getCharge to an
abstract method

156
How to handle new movies

1
Movie Price

getCharge() getCharge()

Regular Price Children Price New Release Price

getCharge() getCharge() getCharge()

157
Handle renter points
 Now we repeat for frequent renter points
 We move the method over to price and use polymorphism to
handle the logic
 we leave a default implementation in Price and have newRelease
override the implementation since it is the only class that returns that
value.

158
We’re done!
 We have added new functionality, changed “data holders”
to “objects” and made it easy to add new types of movies
with special charges and frequent rental points.

159
Item
Library Blob or god class Title
ISBN
Author
Publisher
Library_Main_Control Cost
Quantity
Fine_Amount …….

Check_Out_Item(Item)
Person Check_In_Item(Item)
Name Add_Item(Item)
User_Id Delete_Item(Item)
Items_Out Print_Catalog(Catalog) Catalog
Fines Sort_Catalogs(Catalog)
….. Search_Catalog(topic) TopicInventory
Edit_Item(Item) …..
Find_Item(Item)
List_Catalogs()
Issue_Library_Card(Person)
Calculate_Fine(Person)
…….

“This is the class that is really the heart of our architecture ”


How to identify a Blob?
 One class monopolizes the processing.
 Single class with a large number of attributes, operations or
both.
 A collection of un-related attributes and operations
encapsulated in a single class (Lack of cohesion in OO Class).
 Single controller class with associated simple data object
classes.
 Difficult to incorporate change.
 Too complex to reuse and test.
Preventing Blob
 Be very suspicious of a class whose name contains Driver,
Manager, Controller System or Subsystem.
 Beware of classes that have many getter methods defined in
their public interface.
Having many implies that related data and behavior
are not being kept in one place.
 Beware of classes that have too much non-communicating
behavior, that is, methods that operate on a proper subset of
the data members of a class.
 Distribute system intelligence as uniformly as possible.
How to get rid of a Blob?

The key is to move unrelated behavior and


data away from the Blob.
Item
Title
ISBN
Library_Main_Control Author
Related Publisher
Methods Fine_Amount Cost
Quantity
Check_Out_Item(Item) …….
Check_In_Item(Item)
Add_Item(Item)
Delete_Item(Item)
Catalog
Print_Catalog(Catalog)
Sort_Catalogs(Catalog) Topic_Inventory
Search_Catalog(topic) …..
Edit_Item(Item)
Person Find_Item(Item)
Name List_Catologs()
User_Id Issue_Library_Card(Person)
Items_Out Calculate_Fine(Person) Related
Fines ……. Methods
…..
Item
 Look for “Natural Homes” Title
ISBN
Author
Publisher
Library_Main_Control Cost
Quantity
Fine_Amount …….

Check_Out_Item(Item)
Check_In_Item(Item)
Add_Item(Item)
Delete_Item(Item)
Print_Catalog(Catalog) Catalog
Sort_Catalogs(Catalog)
Search_Catalog(topic) Topic_Inventory
…..
Edit_Item(Item)
Find_Item(Item)
Person
Name List_Catalogs()
User_Id Issue_Library_Card(Person)
Items_Out Calculate_Fine(Person)
Fines …….
…..
Item
 Look for “Natural Homes”…. Title
\ ISBN
Author
Publisher
Cost
Quantity
Library_Main_Control …….
Fine_Amount
Check_Out_Item()
SelectItemMenu(item,menuchoice) Check_In_Item()
Print_Catalog(Catalog) Add_Item()
Sort_Catalogs(Catalog) Delete_Item()
Search_Catalog(topic) Edit_Item()
Find_Item()

Catalog
List_Catalogs()
Person Issue_Library_Card(Person) Topic_Inventory
Name Calculate_Fine(Person) …..
User_Id …….
Items_Out
Fines
…..
Item
Title
ISBN
Author
Publisher
Cost
Quantity
…….
Library_Main_Control Check_Out_Item()
Check_In_Item()
Fine_Amount
Fine_Amount Add_Item()
Delete_Item()
SelectItemMenu(item,menuchoice) Edit_Item()
Find_Item()
SelectCatalogMenu(Catalog,
menuchoice)
Catalog

Topic_Inventory
Person
….
Name
Issue_Library_Card(Person) Print_Catalog()
User_Id
Calculate_Fine(Person) Sort_Catalogs()
Items_Out Calculate_Fine(Person)
……. Search_Catalog(topic)
Fines
List_Catalogs()
…..
Item
Title
ISBN
Author
Publisher
Cost
Quantity
…….
Fine Library_Main_Control
Check_Out_Item()
Fine_Amount Check_In_Item()
Add_Item()
Calculate_Fine(Pers SelectItemMenu(item,menuchoice) Delete_Item()
on) Edit_Item()
SelectCatalogMenu(Catalog, Find_Item()
menuchoice)
Find _fine(Person)
Catalog

Topic_Inventory
Person
….
Name
Print_Catalog()
User_Id
Sort_Catalog()
Items_Out
Issue_Library_Card(Person) Search_Catalog()
Fines
……… List_Catalogs()
…..
Item
 Redefine Relationships Title
ISBN
Author
Publisher
Cost
Quantity
…….
Fine Library_Main_Control Check_Out_Item()
Check_In_Item()
Fine_Amount Add_Item()
Delete_Item()
Calculate_Fine(Pers Edit_Item()
on) SelectItemMenu(item,menuchoice) Find_Item()

SelectCatalogMenu(Catalog,
menuchoice)
Find _fine(Person) Catalog

Person Topic_Inventory
Name ….
User_Id Print_Catalog()
Items_Out Issue_Library_Card(Person) Sort_Catalog()
Fines ……… Search_Catalog(topic)
….. List_Catalogs()
SelectItemMenu(item,
menuchoice)
How do you make refactoring safe?
 First, use refactoring “patterns”
 Fowler’s book assigns “names” to refactorings in the same way that
the GoF’s book assigned names to patterns

 Second, test constantly!


 This ties into the extreme programming paradigm, you write tests
before you write code, after you refactor code, you run the tests and
make sure they all still pass

 if a test fails, the refactoring broke something, but you know


about it right away and can fix the problem before you move
on

170

You might also like