Refactoring – Ch7 Moving Features Btw Objects Chen Jing Fung 2011/5/18 http://sourcemaking.com/refactoring
Jan 13, 2015
Refactoring – Ch7
Moving Features Btw Objects
Chen Jing Fung
2011/5/18
http://sourcemaking.com/refactoring
Outline
• Moving Features Between Objects
Decide where to put responsibilities (refactoring?)
– Move Method vs. Move Field
– Extract Class vs. Inline Class
– Hide Delegate vs. Remove Middle Man
– Introduce Foreign Method & Introduce Local Extension
• Summary
Move Method
• Examine all features used by the method on Class S. – Consider what situation should be move? (one or more methods ?)
• Check the sub- & superclasses of Class S – unless Class S and Class T are both the polymorphism
• Naming the method’ in Class T (more sense)
• Copy the method code from Class S to Class T. Adjust the method’ to fit Class T
– Reference back Class S? just send a parameter from Class S
– Exception handler at Class S or Class T?
• Compile Class T
• Determine how to reference back Class S – Class T has existing field or method
– If no existing, create a method in Class T
– or Create a new field (store the target object) in Class S [temporariness]
• Turn the source method into a delegating method
• Compile and test
• Decide whether to remove the source method or retain it as a delegating method
– retain as a delegating method => many references
– If remove the source method, replace all the references and make the reference to link Class T
• Compile and test
Class S
Class T
Method()
Class S
Class T
Method’()
Mechanics:
Motivation:
In Class S, method() used more features from Class T
Example for Move Method (1)
class Account...
double overdraftCharge() {
if (_type.isPremium()) {
double result = 10;
if (_daysOverdrawn > 7)
result += (_daysOverdrawn - 7) * 0.85;
return result;
}
else return _daysOverdrawn * 1.75;
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0)
result += overdraftCharge();
return result;
}
private AccountType _type;
private int _daysOverdrawn;
Original code class AccountType...
double overdraftCharge(int daysOverdrawn) {
if (isPremium()) {
double result = 10;
if (daysOverdrawn > 7)
result += (daysOverdrawn - 7) * 0.85;
return result;
}
else return daysOverdrawn * 1.75;
}
class Account...
double overdraftCharge() {
return _type.overdraftCharge(_daysOverdrawn);
}
class Account...
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0)
result += _type.overdraftCharge(_daysOverdrawn);
return result;
} a parameter
delegation
Many accounts join
& Every account has
self-overdraftCharge
=> refactoring
the original code
Remain
individual
accounts
Class T Class S
Example for Move Method (2)
class AccountType...
double overdraftCharge(Account account) {
if (isPremium()) {
double result = 10;
if (account.getDaysOverdrawn() > 7)
result += (account.getDaysOverdrawn() - 7) * 0.85;
return result;
}
else return account.getDaysOverdrawn() * 1.75;
}
class AccountType...
double overdraftCharge(int daysOverdrawn) {
if (isPremium()) {
double result = 10;
if (daysOverdrawn > 7)
result += (daysOverdrawn - 7) * 0.85;
return result;
}
else return daysOverdrawn * 1.75;
}
Reference
a parameter
Reference
a field
(need several
features)
If there are too
many features
=> future
refactoring
Move Field
• Motivation – Class S’s field(be used by more methods) is used frequently in
Class T • If those methods seem sensible where they are => Move Field
– When doing Extract Class, must do Move Field
• Mechanics – Public field => use Encapsulate Field
• If the field be accessed frequently by many methods => use Self Encapsulate Field
– Compile and test
– Create a field in Class T with getting & setting methods
– Compile Class T
– Determine how to reference back Class S • Class T has existing field or method
• If no existing, create a method in Class T
• or Create a new field (store the target object) in Class S [temporariness]
– Remove Class S’s field
– Replace all field reference in Class S and chose the appropriate link for Class T
• Access field is by variable => replace the reference with a call to the target object’s getting method
• Access field by assignments => replace the reference with a call to the setting method
• If field is not private => look in all subclass S for reference
– Compile and test
Class S
Class T
Field
Class S
Class T
Field’
Example for Move field
class Account...
private AccountType _type;
private double _interestRate;
double interestForAmount_days (double amount, int days) {
return _interestRate * amount * days / 365;
}
class AccountType...
private double _interestRate;
void setInterestRate (double arg) {
_interestRate = arg;
}
double getInterestRate () {
return _interestRate;
}
a field
accessor
pair
private double _interestRate;
double interestForAmount_days (double amount, int days) {
return _type.getInterestRate() * amount * days / 365;
}
class Account...
Example for Move field – Self Encapsulate Field
class Account...
private AccountType _type;
private double _interestRate;
double interestForAmount_days (double amount, int days) {
return getInterestRate() * amount * days / 365;
}
private void setInterestRate (double arg) {
_interestRate = arg;
}
private double getInterestRate () {
return _interestRate;
}
double interestForAmountAndDays (double amount, int days) {
return getInterestRate() * amount * days / 365;
}
private void setInterestRate (double arg) {
_type.setInterestRate(arg);
}
private double getInterestRate () {
return _type.getInterestRate();
}
A lot of methods use
the interest rate field
=> refactoring for
easy expanding class
Asscessor =>
Self Encapsulate
Field (redirection)
accessor
pair
a field
Extract Class
• Motivation – A class is too big to understand easily
• Many methods & a lot of data
• Mechanics – Decide how to split the responsibilities of the class
– Create a new class to express the split-off responsibility • Maybe rename the old class
– Make a link from the old to the new class • May need a 2-way link
– Use Move Field on each field you wish to move
– Compile and test after each move
– Use Move Method to move methods over from old to new • Start with low-level methods(few call) & build to the higher level
– Compile and test after each move
– Review & reduce the interfaces of each class • 2-way link => one way
– Decide whether to expose the new class • as a reference object or as an immutable value object (ch.8)
Person
name
officeAreaCode
officeNumber
getTelephoneNumber
Person
name
getTelephoneNumber
Telephone Number
areaCode
number
getTelephoneNumber
officeTelephone
class Person...
public String getName() {
return _name;
}
public String getTelephoneNumber() {
return ("(" + _officeAreaCode + ") " +
_officeNumber);
}
String getOfficeAreaCode() {
return _officeAreaCode;
}
void setOfficeAreaCode(String arg) {
_officeAreaCode = arg;
}
String getOfficeNumber() {
return _officeNumber;
}
void setOfficeNumber(String arg) {
_officeNumber = arg;
}
private String _name;
private String _officeAreaCode;
private String _officeNumber;
class TelephoneNumber {
}
class Person ...
private TelephoneNumber _officeTelephone = new TelephoneNumber();
accessor
pair
New
class
class TelephoneNumber {
String getAreaCode() {
return _areaCode;
}
void setAreaCode(String arg) {
_areaCode = arg;
}
private String _areaCode;
}
accessor
pair
class Person...
public String getTelephoneNumber() {
return ("(" + getOfficeAreaCode() + ")
" + _officeNumber);
}
String getOfficeAreaCode() {
return _officeTelephone.getAreaCode();
}
void setOfficeAreaCode(String arg) {
_officeTelephone.setAreaCode(arg);
}
accessor
pair
Example for Extract Class (1)
Move Field
Intermediate
process to
fool complier
Example for Extract Class (2)
class Person...
public String getName() {
return _name;
}
public String getTelephoneNumber(){
return
_officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficeTelephone() {
return _officeTelephone;
}
private String _name;
private TelephoneNumber _officeTelephone =
new TelephoneNumber();
class TelephoneNumber...
public String getTelephoneNumber() {
return ("(" + _areaCode + ") " + _number);
}
String getAreaCode() {
return _areaCode;
}
void setAreaCode(String arg) {
_areaCode = arg;
}
String getNumber() {
return _number;
}
void setNumber(String arg) {
_number = arg;
}
private String _number;
private String _areaCode;
Move Method
Inline Class
• Motivation – A class isn’t doing very much
• Maybe the result of refactoring to move other responsibilities out of class
• Mechanics (Move all its features into another class and delete it) – Declare the public protocol of the source class
onto the absorbing class. Delegate all these methods to the source class
• Source class methods have a separate interface => use Extract Interface(ch.11) before inlining
– Change all references (source class -> absorbing class)
• Source class: Declare private to out-of-package reference & change name to fool compiler
– Compile and test
– Use Move Method and Move Field to move features form the source class to the absorbing class (until nothing is left)
– Delete non-necessary class
Person
name
officeAreaCode
officeNumber
getTelephoneNumber
Person
name
getTelephoneNumber
Telephone Number
areaCode
number
getTelephoneNumber
officeTelephone
absorbing class
source class
class Person...
public String getName() {
return _name;
}
public String getTelephoneNumber(){
return _officeTelephone.getTelephoneNumber();
}
TelephoneNumber getOfficeTelephone() {
return _officeTelephone;
}
private String _name;
private TelephoneNumber _officeTelephone = new
TelephoneNumber();
class TelephoneNumber...
public String getTelephoneNumber() {
return ("(" + _areaCode + ") " + _number);
}
String getAreaCode() {
return _areaCode;
}
void setAreaCode(String arg) {
_areaCode = arg;
}
String getNumber() {
return _number;
}
void setNumber(String arg) {
_number = arg;
}
private String _number;
private String _areaCode;
String getAreaCode() {
return _officeTelephone.getAreaCode();
}
void setAreaCode(String arg) {
_officeTelephone.setAreaCode(arg);
}
String getNumber() {
return _officeTelephone.getNumber();
}
void setNumber(String arg) {
_officeTelephone.setNumber(arg);
}
Person martin = new Person();
martin.getOfficeTelephone().setAreaCode ("781");
Person martin = new Person();
martin.setAreaCode ("781");
interface
interface
Declare all the visible
methods about
TelephoneNumber class Person...
Example for Inline class
Hide Delegate Person
getDepartment
Department
getMange
r
Client
Class
Client
Class
Person
getManager Department
Client Server
Method()
Delegate
Method()
Delegate.method()
Client calls delegate class by server
object
Server builds all methods (delegate method) to be used by
client
Changes are limited to
Server-side & don’t
propagate to Client-side
The advantage of
encapsulating
Hide Delegate - mechanics
• Mechanics
– Create a simple delegating method (for each
method) on the server
• Client is not the same package as server => make
the delegate method to visibility
– Compile and test after adjustung each method
– If no client needs to access the delegate
anymore, remove the server’s accessor for
the delegate
– Compile and test
Example for Hide Delegate
class Person…
Department _department;
public Department getDepartment() {
return _department;
}
public void setDepartment(Department
arg) {
_department = arg;
}
manager = john.getDepartment().getManager();
client accesses a person's manager =>
get the department first
manager = john.getManager();
client accesses a person's manager
class Person...
Department _department;
public Person getManager() {
return _department.getManager();
}
Original code …
Refactoring…
class Department…
private String _chargeCode;
private Person _manager;
public Department (Person manager) {
_manager = manager;
}
public Person getManager() {
return _manager;
}
class Department...
private Person _manager;
public Department (Person manager) {
_manager = manager;
}
Remove Middle Man
• Motivation – Add too much delegating on the server, so accessing
becomes painful => server class is a middle man
• Mechanics – Create an accessor for the delegate
– For each client use of a delegate, remove the method from server & make the client call the delegate method directly
– Compile and test after each method
Foo
getbar
Bar
getImpValue
Client
Class
Client
Class
Foo
getImpValue
Bar
A class has too
much simple
delegation
Get the caller to
call the delegate
directly
Example for Remove Middle Man(1)
public class Foo {
Bar bar;
public Foo getImpValue(){
return bar.getImpValue();
}
}
public class Foo {
Bar bar;
public Bar getbar() {
return bar;
}
}
Original code … Refactoring…
http://www.jetbrains.com/idea/webhelp/remove-middleman.html
public class Bar {
private Foo impValue1;
public Bar(Foo impValue){
impValue1 = impValue;
}
public Foo getImpValue(){
return impValue1;
}
}
public class Client {
Foo a;
Foo impValue = a.getImpValue();
}
public class Bar {
private Foo impValue1;
public Bar(Foo impValue){
impValue1 = impValue;
}
public Foo getImpValue(){
return impValue1;
}
}
public class Client {
Foo a;
Foo impValue = a.getbar().getImpValue();
}
Example for Remove Middle Man(2)
http://lostechies.com/seanchambers/2009/08/28/refactoring-day-29-remove-middle-man/
public class Consumer {
public AccountManager AccountManager { get; set; }
public Consumer(AccountManager accountManager) {
AccountManager = accountManager;
}
public void Get(int id) {
Account account = AccountManager.GetAccount(id);
}
}
public class Consumer {
public AccountDataProvider AccountDataProvider { get; set; }
public Consumer(AccountDataProvider dataProvider) {
AccountDataProvider = dataProvider;
}
public void Get(int id) {
Account account = AccountDataProvider.GetAccount(id);
}
}
may have a set of “Ghost” classes in code
public class AccountManager {
public AccountDataProvider DataProvider { get; set; }
public AccountManager(AccountDataProvider
dataProvider) {
DataProvider = dataProvider;
}
public Account GetAccount(int id) {
return DataProvider.GetAccount(id);
}
} public class AccountDataProvider {
public Account GetAccount(int id) {
// get account
}
} Original code …
public class AccountDataProvider {
public Account GetAccount(int id) {
// get account
}
}
Refactoring…
“Ghost” classes
Introduce Foreign Method • Motivation
– Want add foreign method in, but can’t change the source
– Create many (> 1~2) foreign methods on a server class or many other classes need the same foreign method => use Introduce Local Extension
• Mechanics – Create a method in the client
class (you need) • The method should access no
feature on client class.
– Make an instance of the server class the first parameter
– Comment the method as “foreign method; should be in server”
• Mark the comment as text to easy refactoring again
Date newStart = new Date (previousEnd.getYear(),
previousEnd.getMonth(),
previousEnd.getDate() + 1);
Date newStart = nextDay(previousEnd);
private static Date nextDay(Date arg) {
return new Date (arg.getYear(),arg.getMonth(),
arg.getDate() +1);
}
Need to add methods
in server class, but
can’t modify it
Create a method in
client class
server
client
Introduce Local Extension
• Motivation – Can’t modify => group the methods
together & using object-oriented techniques(subclass/ warp) to do (local extension)
– Local extension • A separate class & a subtype of extended
class
• [methods & data should be packaged into well-formed units]
• Mechanics – Create an extension class either as a
subclass or a wrapper of the original
– Add converting constructors to the extension
• Constructor takes the original as an argument
• Subclass calls an superclass constructor
• Wrapper sets the delegate field to the argument
– Add new features to the extension
– Replace the original with the extension where needed
– Move any foreign methods defined for this class onto the extension
Client Class
nextDay(Date):Date
Date
MfDate
nextDay():Date
Q: Need to add
methods(>1~2) in server
class, but can’t modify it
Create a new class
that contains these
extra method
Make this extension
class a subclass or
wrapper of the
original
Class mfDate extends Date
{
public nextDay()...
public dayOfYear()...
Subclass
class mfDate {
private Date _original;
Wrapper
(delegation)
Example for Introduce Local Extension -
subclass
class MfDateSub extends Date…
public MfDateSub (String dateString) {
super (dateString);
};
public MfDateSub (Date arg) {
super (arg.getTime());
}
client class...
private static Date nextDay(Date arg) {
// foreign method, should be on date
return new Date (arg.getYear(),arg.getMonth(), arg.getDate() + 1);
}
class MfDateSub extends Date {
public MfDateSub (String dateString) {
super (dateString);
};
public MfDateSub (Date arg) {
super (arg.getTime());
}
Date nextDay() {
return new Date (getYear(),getMonth(), getDate() + 1);
}
}
Move Method
Add a converting constructor
Original code … Refactoring…
Example for Introduce Local Extension
- wrapper
http://hi.baidu.com/hanframe_ip/blog/item/76c3b154d21aac50d0090659.html
class mfDate {
private Date _original;
}
1. Declare a class
public mfDateWarp (String dateString) {
_original = new Date (dateString);
} ;
2. Set the constructors by a delegation
public mfDateWarp (String arg) {
_original = arg;
}
2.1 Set the instance variable
3. Delegate all methods
public int getYear() {
return _original.getYear();
}
public boolean equals (MfDateWrap arg) {
return (toDate().equals(arg.toDate()));
}
client class...
private static Date nextDay(Date arg) {
// foreign method, should be on date
return new Date (arg.getYear(),arg.getMonth(),
arg.getDate() + 1);
}
class MfDate...
Date nextDay() {
return new Date (getYear(),getMonth(),
getDate() + 1);
}
4. Move Method
Put back to class
Problem: Introduce Local
Extension – wrapper
wrapper problem: Can’t alter
Public boolean after (Date arg)
aWrapper.after(aDate)
aDate.after(aWrapper)
Override => hide
wrapper info.
Public boolean equals (Date arg)
cause problem:
equals is symmetric !! public boolean equalsDate
(MfDateWrap arg)
public boolean equalsDate (Date arg)
Summary –
How to put responsibilities ? • Refactoring – basic idea
– Move Field > Move Method
• Class over responsibilities or less – Extract Class V.S Inline Class
• Class uses another Class – Hide Delegate (hide their relationship)
• As Hide delegate causes owner’s interface change in frequency – Remove Middle Man
• As can’t modify a class, but want add… – Introduce Foreign Method (only for 1~2 methods)
– Introduce Local Extension • Subclass vs. wrapper (delegate field)