重構以兩種方式和TDD密切相關。
在以盡可能簡單的手段通過測試之后,(在這個過程中違反了任何編碼規則)我們就進行重構清理。大部分是除去我們為了通過測試而帶來的重復。
如果我們是按TDD行事,那么我們就有了適當的測試的安全網,這使得我們有信心進行重構。
何時重構?
1 重復的時候

public boolean save() throws IOException
{

if (outputFile == null)
{
return false;
}

FileWriter writer = new FileWriter(outputFile);
movies.writeTo(writer);
writer.close();
return true;
}


public boolean saveAs() throws IOException
{
outputFile = view.getFile();

if (outputFile == null)
{
return false;
}

FileWriter writer = new FileWriter(outputFile);
movies.writeTo(writer);
writer.close();
return true;
}


替代成

public boolean saveAs() throws IOException
{
outputFile = view.getFile();
save();
}
2 我們發現代碼,或者/并且它的意圖不清楚的時候。、
代碼是最重要的交付產品,必須盡可能清楚和可理解。
3 我們嗅到代碼味道,微妙的(或者不那么微妙的)跡象表明代碼有問題。
代碼味道不一定總是表明有問題,但是代碼味道表明我們應該仔細察看一下,是否有問題。
如何重構?
析取類
看看下面的例子
public void writeTo(Writer destination) throws IOException {
Iterator movieIterator = movies.iterator();
while (movieIterator.hasNext()) {
Movie movieToWrite = (Movie)movieIterator.next();;
destination.write(movieToWrite.getName());
destination.write('|');
destination.write(movieToWrite.getCategory().toString());
destination.write('|');
try {
destination.write(Integer.toString(movieToWrite.getRating()));
} catch (UnratedException ex) {
destination.write("-1");
}
destination.write('\n');
}
}
MovieList不恰當的包含了太多Movie的特性
public class MovieList {
//
public void writeTo(Writer destination) throws IOException {
Iterator movieIterator = movies.iterator();
while (movieIterator.hasNext()) {
Movie movieToWrite =(Movie)movieIterator.next();;
movieToWrite.writeTo(destination);
}
}
public classMovie {
//
public void writeTo(Writer destination) {
destination.write(getName());
destination.write('|');
destination.write(getCategory(). toString());
destination.write('|');c
try {
destination.write(Integer.toString(getRating()));
} catch (UnratedException ex) {
destination.write("-1");
}
destination.write('\n');
}
}
寫電影列表的工作放在MovieList和Movie當中,應該抽象出來。
public class MovieListWriter
{
Writer destination = null;
public MovieListWriter(Writer aWriter) {
destination = aWriter;
}
public void writeMovieList(MovieList aList) throws IOException {
Iterator movieIterator = aList.getMovies().iterator();
while (movieIterator.hasNext()) {
Movie movieToWrite = (Movie)movieIterator.next();;
writeMovie(movieToWrite);
}
private void writeMovie(Movie aMovie) {
destination.write(aMovie.getName());
destination.write('|');
destination.write(aMovie.getCategory().toString());
destination.write('|');
try {
destination.write(Integer.toString(aMovie.getRating()));
} catch (UnratedException ex) {
destination.write("-1");
}
destination.write('\n');
}
}
寫電影列表的代碼集中起來。而且只有writeMovieList()方法暴露出來,其他的細節都封裝起來。
析取接口
下面是一個有形的類,非常簡單
public class MovieList {
private Collection movies = new ArrayList();
public int size() {
return movies.size();
}
public void add(Movie movieToAdd) {
movies.add(movieToAdd);
}
public boolean contains(Movie movieToCheckFor) {
return movies.contains(movieToCheckFor);
}
}
如果需要模擬他,我們應該析取接口
public interface IMovieList {
int size();
void add(Movie movieToAdd);
boolean contains(Movie movieToCheckFor);
}
public class MovieList implements IMovieList {
//
}
析取方法
過于龐大的方法,應該進行拆分。下面的方法,每個注釋出現的地方可以析取出來
public void init() {
// set the layout
getContentPane().setLayout(new FlowLayout());
// create the list
movieList = new JList(myEditor.getMovies());
JScrollPane scroller = new JScrollPane(movieList);
getContentPane().add(scroller);
// create the field
movieField = new JTextField(16);
getContentPane().add(movieField);
// create the add button
addButton = new JButton("Add");
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myEditor.add(movieField.getText());
movieList.setListData(myEditor.getMovies());
}
});
getContentPane().add(addButton);
}
分解和命名為成一些不證自明的方法。
public void init() {
setLayout();
initMovieList();
initMovieField();
initAddButton();
}
private void setLayout() {
getContentPane().setLayout(new FlowLayout());
}
private void initMovieList() {
movieList = new JList(getMovies());
JScrollPane scroller = new JScrollPane(movieList);
getContentPane().add(scroller);
}
private void initMovieField() {
movieField = new JTextField(16);
getContentPane().add(movieField);
}
private void initAddButton() {
addButton = new JButton("Add");
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myEditor.add(movieField.getText());
movieList.setListData(getMovies());
}
});
getContentPane().add(addButton);
}
用子類代替類型代碼
public class Employee {
// 0 - engineer, 1 - salesman, 2 - manager
private int employeeType;
//..
}
替代成
abstract public class Employee {
//. . .
}
public class Engineer extends Employee {
//. . .
}
public class Salesman extends Employee
{
//. . .
}
public class Manager extends Employee
{
//. . .
}
用多態來替代條件(開關)語句
public class Employee {
// 0 - engineer, 1 - salesman, 2 - manager
private int employeeType;
public String departmentName() {
switch (employeeType) {
case 0:
return "Engineering";
case 1:
return "Sales";
case 2:
return "Management";
default:
return "Unknown";
}
}
}
替代為
abstract public class Employee {
public abstract String departmentName();
}
public class Engineer extends Employee {
public String departmentName() {
return "Engineering";
}
}
public class Salesman extends Employee {
public String departmentName() {
return "Sales";
}
}
public class Manager extends Employee {
public String departmentName() {
return "Management";
}
}
模板方法
public class Engineer extends Employee {
public String asXML() {
StringBuffer buf = new StringBuffer();
buf.append("<employee name=\"");
buf.append(getName());
buf.append("\" department=\"Engineering\">");
//
return buf.toString();
}
//. . .
}
public class Salesman extends Employee {
public String asXML() {
StringBufer buf = new StringBuffer();
buf.append("<employee name=\"");
buf.append(getName());
buf.append("\" department=\"Sales\">");
//. . .
return buf.toString();
}
//
}
public class Manager extends Employee {
public String asXML() {
StringBufer buf = new StringBuffer();
buf.append("<employee name=\"");
buf.append(getName());
buf.append("\" department=\"Management\">");
//. . .
return buf.toString();
}
//
}
用employee很好地解決了問題
public class Employee {
public String asXML() {
StringBuffer buf = new StringBuffer();
buf.append("<employee name=\"");
buf.append(getName());
buf.append("\" department=\"");
buf.append(departmentName());
buf.append("\">");
//
return buf.toString();
}
//. . .
}
引入解釋變量
public Money calculateTotal() {
return getSubtotal().plus((getTaxableSubtotal().times(0.15))).minus((getSubtotal().asDouble()> 100.0)?(getSubtotal().times(0.10)):0);
public Money calculateTotal() {
Money subtotal = getSubtotal();
Money tax = getTaxableSubtotal().times(0.15);
Money total =subtotal.plus(tax);
boolean qualifiesForDiscount = getSubtotal().asDouble()
> 100.0;
Money discount = qualifiesForDiscount
?subtotal.times(0.10)
:newMoney(0.0);
return total.minus(discount);
用工廠方法替代構造函數
public classRating {
private int value = 0;
private String source = null;
private String review = null;
public Rating(int aRating) {
this(aRating, "Anonymous", "");
}
public Rating(int aRating, String aRatingSource) {
this(aRating, aRatingSource, "");
}
public Rating(int aRating, String aRatingSource, String aReview) {
value = aRating;
source = aRatingSource;
review = aReview;
}
//
}
public static Rating newAnonymousRating(int value) {
return new Rating(value, "Anonymous", "");
}
public static Rating newRating(int value, String source) {
return new Rating(value, source, "");
}
public static Rating newReview(int value, String source, String review) {
return new Rating(value, source, review);
}
private Rating(int aRating, String aRatingSource, String aReview) {
value = aRating;
source = aRatingSource;
review = aReview;
}
用代理代替繼承
public class Department extends Vector {
}
public class Department {
private Vector employees = new Vector();
public void hire(Employee newHire) {
employees.add(newHire);
}
public DepartmentIterator iterator() {
return new DepartmentIterator();
}
public class DepartmentIterator {
Iterator underlying = employees.iterator();
public boolean hasNext() {
return underlying.hasNext();
}
public Employee next() {
return (Employee)underlying.next();
}
}
}