重构以两种方式和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();
}
}
}