前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。
买了《重构 - 改善既有代码的设计 》一书,一直没有好好看,大致过了下也觉得只是有点点印象而已,最后还是决定把代码敲一次,记录一下这些学习过程。
第一例:租赁影片
程序说明:顾客租了哪些影片,租期多长,根据租赁时间和影片类型算出费用和积分。
1. 分解并重组statement()
原代码如下有3个类。
package bean;
/**
* 租赁订单
* @author Administrator
*/
public class Rental {
private Movie _movie ; // 影片
private int _daysRented; // 租赁天数
public Rental(Movie _movie, int _daysRented) {
this._movie = _movie;
this._daysRented = _daysRented;
}
public Movie getMovie() {
return _movie;
}
public int getDaysRented() {
return _daysRented;
}
}
package bean;
/**
* 影片
* @author Administrator
*/
public class Movie {
public static final int CHILDRENS = 2; // 儿童片
public static final int REGULAR = 0; // 普通片
public static final int NEW_RELEASE = 1; // 新片
private String _title;
private int _priceCode;
public Movie(String _title, int _priceCode) {
this._title = _title;
this._priceCode = _priceCode;
}
public int getPriceCode() {
return _priceCode;
}
public void setPriceCode(int _priceCode) {
this._priceCode = _priceCode;
}
public String getTitle() {
return _title;
}
}
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
double thisAmount = 0; // 租金
Rental each = (Rental)rentals.nextElement();
// 确定每种片子的租金
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount += 2;
if(each.getDaysRented() > 2 ){
thisAmount += (each.getDaysRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented()*3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if(each.getDaysRented() > 3 ){
thisAmount += (each.getDaysRented() - 3) * 1.5;
}
break;
}
// 增加积分
frequentRenterPoints ++;
// 新片+租赁时间达2天 积分+1
if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){
frequentRenterPoints ++;
}
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";
totalAmount += thislAmount;
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
}
1.1 抽离switch 语句到独立方法
1.1.1 Customer类改为:
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
double thisAmount = 0; // 租金
Rental each = (Rental)rentals.nextElement();
thisAmount = amountFor(each); // 计算租金
// 增加积分
frequentRenterPoints ++;
// 新片+租赁时间达2天 积分+1
if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){
frequentRenterPoints ++;
}
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";
totalAmount += thisAmount;
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
// 计算租金
private int amountFor(Rental each){
int thisAmount = 0; // 租金
// 确定每种片子的租金
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount += 2;
if(each.getDaysRented() > 2 ){
thisAmount += (each.getDaysRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented()*3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if(each.getDaysRented() > 3 ){
thisAmount += (each.getDaysRented() - 3) * 1.5;
}
break;
}
return thisAmount;
}
}
1.1.2 注意 amountFor 方法的返回类型应该是double类型:
Customer类改为:
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
double thisAmount = 0; // 租金
Rental each = (Rental)rentals.nextElement();
thisAmount = amountFor(each); // 计算租金
// 增加积分
frequentRenterPoints ++;
// 新片+租赁时间达2天 积分+1
if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){
frequentRenterPoints ++;
}
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";
totalAmount += thisAmount;
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
// 计算租金
private double amountFor(Rental each){
double thisAmount = 0; // 租金
// 确定每种片子的租金
switch(each.getMovie().getPriceCode()){
case Movie.REGULAR:
thisAmount += 2;
if(each.getDaysRented() > 2 ){
thisAmount += (each.getDaysRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
thisAmount += each.getDaysRented()*3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if(each.getDaysRented() > 3 ){
thisAmount += (each.getDaysRented() - 3) * 1.5;
}
break;
}
return thisAmount;
}
}
1.3 变量名称应该见名知意:好的代码应该清楚的表达出自己的功能,变量名称是代码清晰的一个关键。
amountFor方法中局部变量 thisAmount , 参数each 要改名。
改变量名 , Customer类改为:
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
double thisAmount = 0; // 租金
Rental each = (Rental)rentals.nextElement();
thisAmount = amountFor(each); // 计算租金
// 增加积分
frequentRenterPoints ++;
// 新片+租赁时间达2天 积分+1
if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){
frequentRenterPoints ++;
}
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";
totalAmount += thisAmount;
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
// 计算租金
private double amountFor(Rental aRental){
double result = 0; // 租金
// 确定每种片子的租金
switch(aRental.getMovie().getPriceCode()){
case Movie.REGULAR:
result += 2;
if(aRental.getDaysRented() > 2 ){
result += (aRental.getDaysRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += aRental.getDaysRented()*3;
break;
case Movie.CHILDRENS:
result += 1.5;
if(aRental.getDaysRented() > 3 ){
result += (aRental.getDaysRented() - 3) * 1.5;
}
break;
}
return result;
}
}
1.4 函数应该放在它所使用的数据所属的对象中, 顾客租金的计算应该移动到Rental类中去。
Rental 类改为 : (计算租金方法去掉参数并改名 )
package bean;
/**
* 租赁订单
* @author Administrator
*/
public class Rental {
private Movie _movie ; // 影片
private int _daysRented; // 租赁天数
public Rental(Movie _movie, int _daysRented) {
this._movie = _movie;
this._daysRented = _daysRented;
}
public Movie getMovie() {
return _movie;
}
public int getDaysRented() {
return _daysRented;
}
// 计算租金
double getCharge(){
double result = 0; // 租金
// 确定每种片子的租金
switch(getMovie().getPriceCode()){
case Movie.REGULAR:
result += 2;
if(getDaysRented() > 2 ){
result += (getDaysRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += getDaysRented()*3;
break;
case Movie.CHILDRENS:
result += 1.5;
if(getDaysRented() > 3 ){
result += (getDaysRented() - 3) * 1.5;
}
break;
}
return result;
}
}
原顾客类中amountFor方法直接调用计算租金方法即可,Customer 类改为:
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
double thisAmount = 0; // 租金
Rental each = (Rental)rentals.nextElement();
thisAmount = amountFor(each); // 计算租金
// 增加积分
frequentRenterPoints ++;
// 新片+租赁时间达2天 积分+1
if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){
frequentRenterPoints ++;
}
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";
totalAmount += thisAmount;
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
// 计算租金
private double amountFor(Rental aRental){
return aRental.getCharge();
}
}
1.5 去掉旧函数amountFor,直接调用新函数getCharge。Customer类改为 :
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
double thisAmount = 0; // 租金
Rental each = (Rental)rentals.nextElement();
thisAmount = each.getCharge(); // 计算租金
// 增加积分
frequentRenterPoints ++;
// 新片+租赁时间达2天 积分+1
if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){
frequentRenterPoints ++;
}
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";
totalAmount += thisAmount;
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
}
1.6 尽量去掉临时变量,临时变量会导致大量参数的传递,没有必要。 thisAmount 是个多余的临时变量,直接去掉。
Customer 改为:
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement();
// 增加积分
frequentRenterPoints ++;
// 新片+租赁时间达2天 积分+1
if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){
frequentRenterPoints ++;
}
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(each.getCharge())+"\n";
totalAmount += each.getCharge();
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
}
1.7 把积分计算方法放到Rental类中,写为常客积分计算方法(getFrequentRenterPoints),并改变Customer类中积分计算代码 。
Rental类改为:
package bean;
/**
* 租赁订单
* @author Administrator
*/
public class Rental {
private Movie _movie ; // 影片
private int _daysRented; // 租赁天数
public Rental(Movie _movie, int _daysRented) {
this._movie = _movie;
this._daysRented = _daysRented;
}
public Movie getMovie() {
return _movie;
}
public int getDaysRented() {
return _daysRented;
}
// 计算租金
double getCharge(){
double result = 0; // 租金
// 确定每种片子的租金
switch(getMovie().getPriceCode()){
case Movie.REGULAR:
result += 2;
if(getDaysRented() > 2 ){
result += (getDaysRented() - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
result += getDaysRented()*3;
break;
case Movie.CHILDRENS:
result += 1.5;
if(getDaysRented() > 3 ){
result += (getDaysRented() - 3) * 1.5;
}
break;
}
return result;
}
// 常客积分计算
int getFrequentRenterPoints(){
// 增加积分
int frequentRenterPoints =0;
frequentRenterPoints++;
// (新片+租赁时间达2天 积分+1 )
if(getMovie().getPriceCode() == Movie.NEW_RELEASE && getDaysRented() > 1){
frequentRenterPoints ++;
}
return frequentRenterPoints;
}
}
Customer类中statement方法改为:
/**
* 生成订单
* @return
*/
public String statement(){
double totalAmount = 0; // 总租金
int frequentRenterPoints = 0; // 积分
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement();
// 积分
frequentRenterPoints += each.getFrequentRenterPoints();
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(each.getCharge())+"\n";
totalAmount += each.getCharge();
}
// 页脚
result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";
result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";
return result;
}
1.8 去掉statement方法中的2个临时变量:totalAmount 和 frequentRenterPoints 。抽离出对应计算方法,并调用。
Customer 类改为:
package bean;
import java.util.Enumeration;
import java.util.Vector;
/**
* 顾客
* @author Administrator
*/
public class Customer {
private String _name; // 顾客名字
private Vector _rentals = new Vector(); // 租赁订单数组
public Customer(String name) {
super();
this._name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName() {
return _name;
}
/**
* 生成订单
* @return
*/
public String statement(){
Enumeration rentals = _rentals.elements();
String result = "Rental Record for "+ getName() + "\n";
while( rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement();
// 本次租赁记录说明
result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(each.getCharge())+"\n";
}
// 页脚
result +="Amount owed is "+ String.valueOf(getTotalCharge())+"\n";
result +="You eared "+String.valueOf(getTotalFrequentRenterPoints())+"frequent renter points";
return result;
}
// 计算总积分
private int getTotalFrequentRenterPoints(){
int result = 0;
Enumeration rentals = _rentals.elements();
while(rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement();
result += each.getFrequentRenterPoints();
}
return result;
}
// 计算总租金
private double getTotalCharge(){
double result = 0;
Enumeration rentals = _rentals.elements();
while(rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement();
result += each.getCharge();
}
return result;
}
}
本步改写作用说明:
这里虽然从1个循环变为3个,但是多了2个查询函数。
1) 使得Customer 类中的任何代码都可以调用这些查询函数。
2) 若系统其它部分需要这些信息,也可以轻松地将查询函数加入 Customer 类接口。而若没有这些查询波函数,其它函数就必须了解 Rental 类,并自行建立循环。
1.9 加功能:打印凭条。
statement 方法改为 htmlStatement :
/**
* 生成订单(打印凭条)
* @return
*/
public String htmlStatement(){
Enumeration rentals = _rentals.elements();
String result = "<P><H1>Rentals for <EM> "+ getName() + "</EM></H1></P>\n";
while( rentals.hasMoreElements()){
Rental each = (Rental)rentals.nextElement();
// 本次租赁记录说明
result += each.getMovie().getTitle()+":"+ String.valueOf(each.getCharge())+"<BR>\n";
}
// 页脚
result +="<P>You owe <EM>"+ String.valueOf(getTotalCharge())+"</EM></P>\n";
result +="<P> on this rental you earned <EM> "+String.valueOf(getTotalFrequentRenterPoints())+"</EM> frequent renter points </P>";
return result;
}
未完,见 : 重构-改善既有代码的设计-第1例:租赁影片(2)