作者在第一章通过一个影片出租的例子,试图阐述重构的基本过程和步骤。看得出来,作者对这个案例给予厚望,花了很大的篇幅。正因如此,我没有理由不好好学习这一章。
影片出租的例子本身不难,但我足足花了一整个下午学习了这个例子。我先是老老实实的把代码用C++重抄了一遍,然后跟着作者的步伐,一步步重构,以期体验“重构改善设计”的完美过程。
这三个晚上尽管进展缓慢,但收获不少:
一,体验了重构改善设计的过程。
二,对state/strategy有了更进一步的理解。
三,第一次尝试使用了QTestLib对代码进行单元测试,尽管还有很多需要学习,但至少开始了。
四,关于我在开始阅读这本书时的一个疑问?我怎么样开发出我的软件功能?
这里作者给出了答案: 我们应该学会在“重构这顶帽子”和“添加新功能这顶帽子”之间来回切换。当然,你要时刻知道自己戴的是哪一顶帽子!
记住作者的几句忠告:
1. 任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。
2. 更改变量名称是值得的行为吗?绝对值得!!
3. 重构过程中,代码可读性 > 性能。只有在优化时,我们才需要考虑性能。
代码:
#ifndef CUSTOMER_H
#define CUSTOMER_H
#include <QString>
#include <vector>
#include "rental.h"
// 顾客类
class Customer
{
public:
Customer(const QString &name);
void apendRental(const Rental &rental);
QString getName() const;
QString statement() const;
QString htmlStatement() const;
private:
QString m_name;
std::vector<Rental> m_rentals;
double getTotalAmount() const;
int getFreqRenterPoints() const;
};
#endif // CUSTOMER_H
#include "customer.h"
Customer::Customer(const QString &name)
: m_name(name)
{
}
void Customer::apendRental(const Rental &rental)
{
m_rentals.push_back(rental);
}
QString Customer::getName() const
{
return m_name;
}
QString Customer::statement() const
{
QString result = "Rental Record for " + getName() + "\n";
for (std::vector<Rental>::size_type i=0; i<m_rentals.size(); ++i)
{
Rental aRental = m_rentals.at(i);
// 添加条目信息
result += "\t" + aRental.getMovie().getTitle() + "\t" + QString::number(aRental.getCharge()) + "\n";
}
result += "TotalAmount is " + QString::number(getTotalAmount()) + "\n";
result += "FreqRenterPoints is " + QString::number(getFreqRenterPoints());
return result;
}
QString Customer::htmlStatement() const
{
QString result = "html Rental Record for " + getName() + "\n";
for (std::vector<Rental>::size_type i=0; i<m_rentals.size(); ++i)
{
Rental aRental = m_rentals.at(i);
// 添加条目信息
result += "\t" + aRental.getMovie().getTitle() + "\t" + QString::number(aRental.getCharge()) + "\n";
}
result += "html TotalAmount is " + QString::number(getTotalAmount()) + "\n";
result += "html FreqRenterPoints is " + QString::number(getFreqRenterPoints());
return result;
}
double Customer::getTotalAmount() const
{
double totalAmount = 0; // 金额
for (std::vector<Rental>::size_type i=0; i<m_rentals.size(); ++i)
{
Rental aRental = m_rentals.at(i);
totalAmount += aRental.getCharge();
}
return totalAmount;
}
int Customer::getFreqRenterPoints() const
{
int freqRenterPoints = 0; // 积分
for (std::vector<Rental>::size_type i=0; i<m_rentals.size(); ++i)
{
Rental aRental = m_rentals.at(i);
freqRenterPoints += aRental.getFreqRenterPoints();
}
return freqRenterPoints;
}
#ifndef RENTAL_H
#define RENTAL_H
#include "movie.h"
// 租赁类
class Rental
{
public:
Rental(const Movie &movie, int daysRented);
Movie getMovie() const;
int getDaysRented() const;
double getCharge() const;
int getFreqRenterPoints() const;
private:
Movie m_movie;
int m_daysRented;
};
#endif // RENTAL_H
#include "rental.h"
Rental::Rental(const Movie &movie, int daysRented)
: m_movie(movie), m_daysRented(daysRented)
{
}
Movie Rental::getMovie() const
{
return m_movie;
}
int Rental::getDaysRented() const
{
return m_daysRented;
}
double Rental::getCharge() const
{
return m_movie.getCharge(m_daysRented);
}
int Rental::getFreqRenterPoints() const
{
return m_movie.getFreqRenterPoints(m_daysRented);
}
#ifndef MOVIE_H
#define MOVIE_H
#include <QString>
// 影片类
class Movie
{
public:
static const int CHILDRENS = 2;
static const int REGULAR = 0;
static const int NEWRELEASE = 1;
public:
Movie(const QString &title, int priceCode);
void setTitle(const QString &title);
QString getTitle() const;
void setPriceCode(int priceCode);
int getPriceCode() const;
double getCharge(int daysRented) const;
int getFreqRenterPoints(int daysRented) const;
private:
QString m_title;
int m_priceCode;
};
#endif // MOVIE_H
#include "movie.h"
Movie::Movie(const QString &title, int priceCode)
: m_title(title), m_priceCode(priceCode)
{
}
void Movie::setTitle(const QString &title)
{
m_title = title;
}
QString Movie::getTitle() const
{
return m_title;
}
void Movie::setPriceCode(int priceCode)
{
m_priceCode = priceCode;
}
int Movie::getPriceCode() const
{
return m_priceCode;
}
double Movie::getCharge(int daysRented) const
{
double thisAmount = 0;
// 计算金额
switch (getPriceCode())
{
case REGULAR:
thisAmount += 2;
if (daysRented > 2)
thisAmount += (daysRented - 2) * 1.5;
break;
case NEWRELEASE:
thisAmount += daysRented * 3;
break;
case CHILDRENS:
thisAmount += 1.5;
if (daysRented > 3)
thisAmount += (daysRented - 3) * 1.5;
break;
default:
//assert();
break;
}
return thisAmount;
}
int Movie::getFreqRenterPoints(int daysRented) const
{
// 计算积分
if ((m_priceCode == NEWRELEASE) && (daysRented > 1 ))
{
return 2;
}
else
{
return 1;
}
}
重构至Strategy模式:
#ifndef MOVIE_H
#define MOVIE_H
#include <QString>
class PriceStrategy;
// 影片类
class Movie
{
public:
static const int CHILDRENS = 2;
static const int REGULAR = 0;
static const int NEWRELEASE = 1;
public:
Movie(const QString &title, int priceCode);
void setTitle(const QString &title);
QString getTitle() const;
void setPriceCode(int priceCode);
int getPriceCode() const;
double getCharge(int daysRented) const;
int getFreqRenterPoints(int daysRented) const;
private:
QString m_title;
PriceStrategy *m_priceStrategy;
};
#endif // MOVIE_H
#include "movie.h"
#include "pricestrategy.h"
Movie::Movie(const QString &title, int priceCode)
: m_title(title)
{
setPriceCode(priceCode);
}
void Movie::setTitle(const QString &title)
{
m_title = title;
}
QString Movie::getTitle() const
{
return m_title;
}
void Movie::setPriceCode(int priceCode)
{
switch (priceCode)
{
case REGULAR:
m_priceStrategy = new RegularPriceStrategy();
break;
case NEWRELEASE:
m_priceStrategy = new NewReleasePriceStrategy();
break;
case CHILDRENS:
m_priceStrategy = new ChildrenPriceStrategy();
break;
default:
//assert();
break;
}
}
int Movie::getPriceCode() const
{
return m_priceStrategy->getPriceCode();
}
double Movie::getCharge(int daysRented) const
{
return m_priceStrategy->getCharge(daysRented);
}
int Movie::getFreqRenterPoints(int daysRented) const
{
return m_priceStrategy->getFreqRenterPoints(daysRented);
}
策略类代码文件:
#ifndef PRICESTRATEGY_H
#define PRICESTRATEGY_H
class PriceStrategy
{
public:
PriceStrategy();
virtual int getPriceCode() const = 0;
virtual double getCharge(int daysRented) const = 0;
virtual int getFreqRenterPoints(int daysRented) const;
};
class RegularPriceStrategy : public PriceStrategy
{
public:
RegularPriceStrategy() {}
virtual int getPriceCode() const;
virtual double getCharge(int daysRented) const;
};
class ChildrenPriceStrategy : public PriceStrategy
{
public:
ChildrenPriceStrategy() {}
virtual int getPriceCode() const;
virtual double getCharge(int daysRented) const;
};
class NewReleasePriceStrategy : public PriceStrategy
{
public:
NewReleasePriceStrategy() {}
virtual int getPriceCode() const;
virtual double getCharge(int daysRented) const;
virtual int getFreqRenterPoints(int daysRented) const;
};
#endif // PRICESTRATEGY_H
#include "pricestrategy.h"
#include "movie.h"
PriceStrategy::PriceStrategy()
{
}
int PriceStrategy::getFreqRenterPoints(int daysRented) const
{
Q_UNUSED(daysRented);
return 1;
}
int RegularPriceStrategy::getPriceCode() const
{
return Movie::REGULAR;
}
double RegularPriceStrategy::getCharge(int daysRented) const
{
double result = 2;
if (daysRented > 2)
result += (daysRented - 2)*1.5;
return result;
}
int ChildrenPriceStrategy::getPriceCode() const
{
return Movie::CHILDRENS;
}
double ChildrenPriceStrategy::getCharge(int daysRented) const
{
double result = 1.5;
if (daysRented > 3)
result += (daysRented - 3)*1.5;
return result;
}
int NewReleasePriceStrategy::getPriceCode() const
{
return Movie::NEWRELEASE;
}
double NewReleasePriceStrategy::getCharge(int daysRented) const
{
return daysRented*3;
}
int NewReleasePriceStrategy::getFreqRenterPoints(int daysRented) const
{
return (daysRented > 1) ? 2 : 1;
}