lecture19cis280

Refactoring II
Dealing with Polymorphism
Switch in Rental Switches on
Movie!
class Rental …
public double getCharge()
{
double result = 0;
switch (getMovie().getPriceCode()){
case Movie.REGULAR:
result += 2;
if (getDaysRented()>2)
result += (getDaysRented()-2)*1.5;
break;
case Movie.NEWRELEASE:
result += getDaysRented()*3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (getDaysRented()-3)*1.5;
break;
}
return result;
}
So Put It In Movie
class Movie …
public double getCharge(int daysRented)
{
double result = 0;
switch (getPriceCode(){
case Movie.REGULAR:
result += 2;
if (getDaysRented()>2)
result += (getDaysRented()-2)*1.5;
break;
case Movie.NEWRELEASE:
result += getDaysRented()*3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (getDaysRented()-3)*1.5;
break;
}
return result;
}
Remarks
•
•
•
This needs data from Rental
So make it an int parameter daysRented
instead of a call getDaysRented()
Leave the type of Movie alone for now
 A good candidate for polymorphism
 This type of information is more volatile, i.e.
subject to change
Change getCharge on Rental
class Rental …
public double getCharge(){
return
_movie.getCharge(_daysRented):
}
Similar for
getFrequentRenterPoints()
•
Move it from Rental to Movie
class Rental …
int getFrequentRenterPoints(){
if((getMovie().getPriceCode()== Movie.NEW_RELEASE) &&
getDaysRented() > 1)
return 2 else return 1;
•
Details not shown (exercise)
Problem
•
•
•
•
Movie data used in switch statement
Fix it with inheritance and polymorphism
Not possible since child type can change
over the movie class’s lifetime
So, use state/strategy pattern
The State Pattern
Context
State
request
handle()
state.handle()
ConcreteStateA
handle()
ConcreteStateB
handle()
State Pattern
•
•
•
Behavior is encapsulated in the concrete
state objects
Context delegates to one of the states
It appears that the state class is changing
 But in reality it is just changing its behavior
•
Change behavior by composing with a
different object
State Pattern
•
•
•
•
Context: The class with the internal states
State: The common interface for all
concrete states
ConcreteStates: deal with requests from
the context. Each provides own
implementation for the request
When Context changes state so will the
concrete state’s behavior
We change
To
Replace Type Code with State/Strategy
Step 1: ensure fields accessed
by get and set
•
Work on constructor in movie
•
Use Self Encapsulate Field
class Movie{
…
public Movie (String name, int priceCode){
_title = name;
setPriceCode(priceCode);}
}
Self Encapsulate Field
Why encapsulate data access?
•
•
•
If a datum is accessed in a base class, but
you want to compute a different value from
this in a child.
If this is encapsulated, you can override the
accessors to change the value.
See Self Encapsulate Field
Step 2: Add new classes
abstract class Price{
abstract int getPriceCode();
}
class ChildrensPrice extends Price{
public int getPriceCode(){return Movie.CHILDRENS;}
}
class NewReleasePrice extends Price{
public int getPriceCode(){return Movie.NEW_RELEASE;}
}
Class RegularPrice extends Price{
public int getPriceCode(){return Movie.REGULAR;}
}
Step 3: Change get and set in
Movie class
•
Change the accessors
class Movie{…
private Price _price;
public int getPriceCode(){return _price.getPriceCode;}
public void setPriceCode(int arg){
switch(arg){
case REGULAR:
_price = new RegularPrice(); break;
case CHILDRENS:
_price = new ChildrensPrice();break;
case NEW_RELEASE:
_price = new NewReleasePrice(); break;
}
…
}
Step 4 Move getCharge to
Price
class Price{…
public double getCharge(int daysRented){
double result = 0.0;
switch (getPriceCode()){
case Movie.REGULAR:
result += 2;
if (daysRented > 2) result += (daysRented–2)*1.5;
break;
case Movie.NEW_RELEASE:
result += daysRented*3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (daysRented>3) result += (daysRented-3)*1.5;
break;
}
Move Method
return result;
}
Don’t forget you need it in Movie
class Movie{…
double getCharge(int daysRented){
return _price.getCharge(daysRented);
}
…
}
Get rid of the switch
Do a getCharge() in class
RegularPrice
class RegularPrice{…
double getCharge(int daysRented){
double result = 2.0;
if (daysRented>2)
result += (daysRented-2)*1.5;
return result;
}
…
}
Don’t forget to compile and test
Replace Conditional With Polymorphism
Now same for ChildrensPrice
class ChildrensPrice{…
double getCharge(int daysRented){
double result = 1.5;
if (daysRented > 3)
result += (daysRented-3)*1.5;
return result;
}
…
}
And finally NewReleasePrice
class NewReleasePrice{…
double getCharge(int daysRented){
return daysRented*3;
}
…
}
Finally get rid of the old
getCharge
class Price{…
abstract double getCharge(int daysRented);
…
}
Do the same to
getFrequentRenterPoints
class Movie{…
int getFrequentRenterPoints(int daysRented){
return _price.getFrequentRenterPoints(daysRented);
…
}
Class Price{…
int getFrequentRenterPoints(int daysRented){
if ((getPriceCode()==Movie.NEW_RELEASE)&& daysRented >1)
return 2;
else return 1;
}
Now Override to
NewReleasePrice
class NewReleasePrice{…
int getFrequentRenterPoints(int daysRented){
return (daysRented>1)?2:1;
}
And keep a defined method on superclass Price
Class Price{…
int getFrequentRenterPoints(int daysRented){
return 1;
}
…
}
When do I refactor?
“If it stinks change it”
Hence the Name: Code Smelling
Code Smells
•
•
•
•
•
•
Duplicated Code
Long Method
Large Class
Long Parameter List
Divergent Change
Shotgun Surgery
More Code Smells
•
•
•
•
•
•
Feature Envy
Data Clumps
Primitive Obsession
Switch Statement
Parallel Inheritance Hierarchy
Lazy Class
And Even More
•
•
•
•
•
•
Speculative Generality
Temporary Field
Message Chains
Middle Man
Inappropriate Intimacy
Incomplete Library Class
And Finally
•
•
•
•
Data Class
Comments
Refused Bequest
And Many More