java重构学习1:引例

本系列来自《java 重构改善既有代码的设计》一书

实例一:影片租赁出租店的程序设计。

计算每一位顾客的消费金额并打印报表(statement),操作者告诉程序:租客租了哪些影片、租期多长、程序便根据租赁时间和影片类型算出费用。

影片分三类:普通片、儿童片和新片。除了计算费用,我们还需要为常客计算点数:点数会随着(租片种类是否为新片)有所不同。

初始:

Movie实体类:

package com.xuzengqiang.ssb.movie;

/**
 * 影片实体
 * @author xuzengqiang
 * @since 2014-12-16 14:28:49
 */
public class Movie {

	public static final int CHILDRENS = 0;
	public static final int REGULAR = 1;
	public static final int NEW_MOVIE = 2;

	private String title;
	private int priceCode; //代号

	public Movie(String title, int priceCode) {
		this.title = title;
		this.priceCode = priceCode;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public int getPriceCode() {
		return priceCode;
	}

	public void setPriceCode(int priceCode) {
		this.priceCode = priceCode;
	}

}
Rental租赁实体类:

package com.xuzengqiang.ssb.movie;

/**
 * 租赁实体
 * @author xuzengqiang
 * @since 2014-12-16 14:39:38
 */
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 void setMovie(Movie movie) {
		this.movie = movie;
	}

	public int getDaysRented() {
		return daysRented;
	}

	public void setDaysRented(int daysRented) {
		this.daysRented = daysRented;
	}

}
Customer客户:

package com.xuzengqiang.ssb.movie;

import java.util.Enumeration;
import java.util.Vector;

/**
 * 租客
 * @author xuzengqiang
 * @since 2014-12-16 14:41:22
 */
public class Customer {

	/**
	 * 描述:姓名
	 */
	private String name;

	/**
	 * 描述:租赁记录
	 */
	private Vector<Rental> rentalVector = new Vector<Rental>();

	public Customer(String name) {
		this.name = name;
	}

	public void addRental(Rental rental) {
		rentalVector.add(rental);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * 描述:打印报表
	 * @return
	 */
	@SuppressWarnings("unused")
	public String statement() {
		// 总消费金额
		double totalAmount = 0;
		// 常客积分点
		int renterPointer = 0;
		Enumeration<Rental> rental = rentalVector.elements();
		StringBuffer result = new StringBuffer();
		while (rental.hasMoreElements()) {
			double amount = 0;
			Rental temp = rental.nextElement();
			switch (temp.getMovie().getPriceCode()) {
			// 如果是儿童片:前3天1.5,超过3天每天1.5
				case Movie.CHILDRENS:
					amount += 1.5;
					//
					if (temp.getDaysRented() > 3) {
						amount += (temp.getDaysRented() - 3) * 1.5;
					}
					break;
				// 如果是普通片:不超过2天2块钱,超过2天每天1.5
				case Movie.REGULAR:
					amount += 2;
					if (temp.getDaysRented() > 2) {
						amount += (temp.getDaysRented() - 2) * 1.5;
					}
					break;
				// 如果是新片:每天3块S
				case Movie.NEW_MOVIE:
					amount += temp.getDaysRented() * 3;
					break;
			}
			renterPointer++;
			// 如果是新片,且租赁时间超过1天积分+1
			if (temp.getMovie().getPriceCode() == Movie.NEW_MOVIE && temp.getDaysRented() > 1) {
				renterPointer++;
			}
			// result拼接....
			totalAmount += amount;
		}
		// result拼接...
		return result.toString();
	}
	
}

为什么要重构上面的代码:

可能这段代码真的能实现现在的业务逻辑和功能,但是Customer中的statement()方法做了太多的事情,一个类中的某个方法应该只负责一件事情

再者如果以后的需求有变化,如需要以HTML的格式打印报表,从而可以在网页上显示,非常符合潮流。但是通过代码你会发现,目前的statement()根本无法实现打印HTML报表,这时我们唯一可以做的就是写一个全新的htmlStatement()方法,大量重复的使用statement()中的内容。这时候我们也许又会想就也就是简单的拷贝就行了。

如果这是收费的标准变了,这个时候我们又得同时修改statement()和htmlStatement()两个方法,并保证两处修改的一致性。当后续还需要修改,此时带来的问题就会越来越多,随着功能越来越复杂,规则越来越多,修改点也会越来越难找,不犯错的机会也越来越小,这时我们就需要对代码进行重构。

重构步骤:

1、需要一个可靠的测试,保证重构后的代码是正确的。

2、分解statement():

代码区块越小,代码的功能就越容易管理,后期维护也会更加方便。

首先抽离switch语句,考虑到temp并为被修改,amount会被修改,这时候可以将不会被修改的变量作为参数传入新的函数,至于会被修改的变量,如果只有一个变量被修改,我们可以考虑将它做为一个返回值。

修改如下:

package com.xuzengqiang.ssb.movie;

import java.util.Enumeration;
import java.util.Vector;

/**
 * 租客
 * @author xuzengqiang
 * @since 2014-12-16 14:41:22
 */
public class Customer {

	/**
	 * 描述:姓名
	 */
	private String name;

	/**
	 * 描述:租赁记录
	 */
	private Vector<Rental> rentalVector = new Vector<Rental>();

	public Customer(String name) {
		this.name = name;
	}

	public void addRental(Rental rental) {
		rentalVector.add(rental);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * 描述:打印报表
	 * @return
	 */
	@SuppressWarnings("unused")
	public String statement() {
		// 总消费金额
		double totalAmount = 0;
		// 常客积分点
		int renterPointer = 0;
		Enumeration<Rental> rental = rentalVector.elements();
		StringBuffer result = new StringBuffer();
		while (rental.hasMoreElements()) {
			Rental temp = rental.nextElement();
			double amount = getAmount(temp);
			renterPointer++;
			// 如果是新片,且租赁时间超过1天积分+1
			if (temp.getMovie().getPriceCode() == Movie.NEW_MOVIE && temp.getDaysRented() > 1) {
				renterPointer++;
			}
			// result拼接....
			totalAmount += amount;
		}
		// result拼接...
		return result.toString();
	}

	/**
	 * 描述:根据租赁实体获取单笔消费金额
	 * @param rental:租赁对象
	 * @return
	 */
	public double getAmount(Rental temp) {
		double amount = 0;
		switch (temp.getMovie().getPriceCode()) {
		// 如果是儿童片:前3天1.5,超过3天每天1.5
			case Movie.CHILDRENS:
				amount += 1.5;
				//
				if (temp.getDaysRented() > 3) {
					amount += (temp.getDaysRented() - 3) * 1.5;
				}
				break;
			// 如果是普通片:不超过2天2块钱,超过2天每天1.5
			case Movie.REGULAR:
				amount += 2;
				if (temp.getDaysRented() > 2) {
					amount += (temp.getDaysRented() - 2) * 1.5;
				}
				break;
			// 如果是新片:每天3块S
			case Movie.NEW_MOVIE:
				amount += temp.getDaysRented() * 3;
				break;
		}
		return amount;
	}
}

重构的时候以微小的步伐修改程序,这样即使犯下错误,也可以很容易的发现。

对于上面的代码,如果我不喜欢getAmount()中的某些变量名,如参数名temp.获取对于自己而言很好理解,可是可能你这段代码出现错误,而由别个帮你修改的时候可能不能一眼就看出你这个temp是什么意思,所以更改变量名是非常有必要的,因为他可以清楚的表达出自己的功能。

于是做出如下调整:

/**
 * 描述:根据租赁实体获取单笔消费金额
 * @param rental:租赁对象
 * @return
 */
public double getAmount(Rental rental) {
	double result = 0;
	switch (rental.getMovie().getPriceCode()) {
	// 如果是儿童片:前3天1.5,超过3天每天1.5
		case Movie.CHILDRENS:
			result += 1.5;
			//
			if (rental.getDaysRented() > 3) {
				result += (rental.getDaysRented() - 3) * 1.5;
			}
			break;
		// 如果是普通片:不超过2天2块钱,超过2天每天1.5
		case Movie.REGULAR:
			result += 2;
			if (rental.getDaysRented() > 2) {
				result += (rental.getDaysRented() - 2) * 1.5;
			}
			break;
		// 如果是新片:每天3块S
		case Movie.NEW_MOVIE:
			result += rental.getDaysRented() * 3;
			break;
	}
	return result;
}
记住一句话: 任何一个傻瓜都能写出计算机能够理解的代码,惟有写出人类容易理解的代码,才是优秀的程序员

3、搬移金额计算代码:我们发现getAmount()这个代码片使用了Rental的信息,但是却没有使用Customer中的任何信息,所以我们需要怀疑是不是放错位置了。

调整如下:

Rental:

package com.xuzengqiang.ssb.movie;

/**
 * 租赁实体
 * @author xuzengqiang
 * @since 2014-12-16 14:39:38
 */
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 void setMovie(Movie movie) {
		this.movie = movie;
	}

	public int getDaysRented() {
		return daysRented;
	}

	public void setDaysRented(int daysRented) {
		this.daysRented = daysRented;
	}

	/**
	 * 描述:根据租赁实体获取单笔消费金额
	 * @return
	 */
	public double getCharge() {
		double result = 0;
		switch (getMovie().getPriceCode()) {
		// 如果是儿童片:前3天1.5,超过3天每天1.5
			case Movie.CHILDRENS:
				result += 1.5;
				//
				if (getDaysRented() > 3) {
					result += (getDaysRented() - 3) * 1.5;
				}
				break;
			// 如果是普通片:不超过2天2块钱,超过2天每天1.5
			case Movie.REGULAR:
				result += 2;
				if (getDaysRented() > 2) {
					result += (getDaysRented() - 2) * 1.5;
				}
				break;
			// 如果是新片:每天3块S
			case Movie.NEW_MOVIE:
				result += getDaysRented() * 3;
				break;
		}
		return result;
	}

}

而在Customer中就可以使用getCharge获取金额了

public double getAmount(Rental rental) {
	return rental.getCharge();
}

这个时候getAmount实际上就是调用rental.getCharge()方法,所以可以略去该方法,在statement()中直接写:

Rental temp = rental.nextElement();
double amount = temp.getCharge();
这时我们会发现amount这个变量就会变的有点多余了,所以可以删去,这个时候的statement就会变成:

/**
 * 描述:打印报表
 * @return
 */
@SuppressWarnings("unused")
public String statement() {
	// 总消费金额
	double totalAmount = 0;
	// 常客积分点
	int renterPointer = 0;
	Enumeration<Rental> rental = rentalVector.elements();
	StringBuffer result = new StringBuffer();
	while (rental.hasMoreElements()) {
		Rental temp = rental.nextElement();
		renterPointer++;
		// 如果是新片,且租赁时间超过1天积分+1
		if (temp.getMovie().getPriceCode() == Movie.NEW_MOVIE && temp.getDaysRented() > 1) {
			renterPointer++;
		}
		// result拼接....
		totalAmount += temp.getCharge();
	}
	// result拼接...
	return result.toString();
}
尽量去除一些没有用的变量,减少不必要的麻烦。

4、提炼常客积分代码:

renterPointer++;
// 如果是新片,且租赁时间超过1天积分+1
if (temp.getMovie().getPriceCode() == Movie.NEW_MOVIE && temp.getDaysRented() > 1) {
	renterPointer++;
}
这里再一次搜索局部变量temp,可以用做参数传入到新函数中,这里由于renterPointer已经有了初始值,所以没有必要进行进行传参,只需要对它执行加法即可。

通过对获取金额的提取经验,我们将获取常客积分代码抽取出来放到Rental类中:

/**
 * 描述:获取常客积分
 */
public int getRenterPointer() {
	if (getMovie().getPriceCode() == Movie.NEW_MOVIE && getDaysRented() > 1) {
		return 2;
	}
	return 1;
}

此时的statement为:

public String statement() {
	// 总消费金额
	double totalAmount = 0;
	// 常客积分点
	int renterPointer = 0;
	Enumeration<Rental> rental = rentalVector.elements();
	StringBuffer result = new StringBuffer();
	while (rental.hasMoreElements()) {
		Rental temp = rental.nextElement();
		renterPointer += temp.getRenterPointer();
		// result拼接....
		totalAmount += temp.getCharge();
	}
	// result拼接...
	return result.toString();
}
5、去除临时变量,提取totalAmount和renterPointer

/**
 * 描述:获取总金额
 */
public double getTotalCharge() {
	double result = 0;
	Enumeration<Rental> rental = rentalVector.elements();
	while (rental.hasMoreElements()) {
		Rental temp = rental.nextElement();
		result += temp.getCharge();
	}
	return result;
}

/**
 * 描述:获取常客积分
 */
public double getTotalRenterPointer() {
	double result = 0;
	Enumeration<Rental> rental = rentalVector.elements();
	while (rental.hasMoreElements()) {
		Rental temp = rental.nextElement();
		result += temp.getRenterPointer();
	}
	return result;
}
这个时候的statement()为:

/**
 * 描述:打印报表
 * @return
 */
public String statement() {
	StringBuffer result = new StringBuffer();
	result.append("姓名:" + getName() + ",总金额:" + getTotalCharge() + ",积分为:" + getTotalRenterPointer());
	return result.toString();
}
此时我们需要考虑的事情,大多数重构都会减少代码量,但是这次却增加了代码总量,而且相对于前面的代码而言,多计算了一次循环,性能更低。如果数据量非常大,性能的损耗是非常明显的。但是我们不能因为这个原因而不进行或不愿进行这个重构动作,因为写出这两个方法必须考虑到系统其它处可能需要调用这个method,所以单独提取出来是非常有必要的,如果不提供这些query method,那么其它函数就必须了解Rental这个类,并自行建立循环,在一个复杂的系统中,会使得编写难度加大。而且这个不是重构的时候需要担心的问题,这是优化的时候需要考虑的问题。

至此,我们不需要大量的复制粘贴就能够构建出一个htmlStatement()方法,如果计算规则发生改变,我们也只需要对程序的一处做修改等.....

6、运用多态取代与价格相关的条件逻辑

首先考虑Rental上的getCharge()方法,第一考虑switch语句:

switch (getMovie().getPriceCode()) {
在另一个对象的属性基础上运用switch语句,并不很好,如果不得不使用,也应该在对象自己的基础上去使用,而不是在别人的数据上去使用。

所以这个getCharge()方法得移动到Movie上去,当然为了程序运行正常,我们得将daysRented传入进去,于是变成了这样:

Movie

/**
 * 根据影片类型的不同获取费用
 * @param daysRented:租赁时间
 * @return
 */
public double getCharge(int daysRented) {
	double result = 0;
	switch (getPriceCode()) {
	// 如果是儿童片:前3天1.5,超过3天每天1.5
		case Movie.CHILDRENS:
			result += 1.5;
			if (daysRented > 3) {
				result += (daysRented - 3) * 1.5;
			}
			break;
		// 如果是普通片:不超过2天2块钱,超过2天每天1.5
		case Movie.REGULAR:
			result += 2;
			if (daysRented > 2) {
				result += (daysRented - 2) * 1.5;
			}
			break;
		// 如果是新片:每天3块S
		case Movie.NEW_MOVIE:
			result += daysRented * 3;
			break;
	}
	return result;
}
而Rental中的getCharge():

/**
	 * 描述:根据租赁实体获取单笔消费金额
	 * @return
	 */
	public double getCharge() {
		return movie.getCharge(getDaysRented());
	}

同样以相同的手法处理常客积分:

Movie

/**
 * 描述:根据影片的类型以及租期获取常客积分
 * @param daysRented
 * @return
 */
public int getRenterPointer(int daysRented) {
	if (getPriceCode() == Movie.NEW_MOVIE && daysRented > 1) {
		return 2;
	}
	return 1;
}
Rental

/**
 * 描述:获取常客积分
 */
public int getRenterPointer() {
	return movie.getRenterPointer(getDaysRented());
}

影片的类型不同,但是只是以不同的方式回答相同的问题,这个有点像子类,这次我们可以将Movie建立3个子类,每个子类都有自己的计费方法:


这样我就可以使用多态来取代switch,但是这里有一个问题,一部影片在生命周期内可以修改自己的分类,一个对象却不能在生命周期内修改所属的class,可以利用State Pattern(模式)来表现不同的影片,这时就变成了:



这时的做法:

1、确保任何时候都能够通过setting和getting运用这些行为。

2、新建抽象类Price以及子类:

package com.xuzengqiang.ssb.movie;

/**
 * 描述:价格抽象类
 * @author xuzengqiang
 * @since 2014-12-16 16:50:47
 */
public abstract class Price {

	/**
	 * 描述:根据不同影片的类型获取价格代号
	 */
	abstract int getPriceCode();
}
ChildrensPrice

package com.xuzengqiang.ssb.movie;

/**
 * 描述:儿童电影
 * @author xuzengqiang
 * @since 2014-12-16 16:52:32
 */
public class ChildrensPrice extends Price {

	@Override
	int getPriceCode() {
		return Movie.CHILDRENS;
	}

}
RegularPrice

package com.xuzengqiang.ssb.movie;

/**
 * 描述:普通电影
 * @author xuzengqiang
 * @since 2014-12-16 16:52:32
 */
public class RegularPrice extends Price {

	@Override
	int getPriceCode() {
		return Movie.REGULAR;
	}

}
NewMoviePrice

package com.xuzengqiang.ssb.movie;

/**
 * 描述:新片
 * @author xuzengqiang
 * @since 2014-12-16 16:52:32
 */
public class NewMoviePrice extends Price {

	@Override
	int getPriceCode() {
		return Movie.NEW_MOVIE;
	}

}
这个时候意味着我们在Movie中不是保存一个priceCode,而是一个Price对象。所以Movie可以修改成:(同时将getCharge方法移到Price中去)

package com.xuzengqiang.ssb.movie;

/**
 * 影片实体
 * @author xuzengqiang
 * @since 2014-12-16 14:28:49
 */
public class Movie {

	public static final int CHILDRENS = 0;
	public static final int REGULAR = 1;
	public static final int NEW_MOVIE = 2;

	private String title;
	/**
	 * 描述:代号
	 */
	private int priceCode;
	/**
	 * 描述:价格对象
	 */
	private Price price;

	public Movie(String title, int priceCode) {
		this.title = title;
		setPriceCode(priceCode);
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * 获取价格代号
	 * @return
	 */
	public int getPriceCode() {
		return price.getPriceCode();
	}

	/**
	 * 这里不是保存的价格代号,而是生成价格对象
	 * @param priceCode
	 */
	public void setPriceCode(int priceCode) {
		switch (priceCode) {
			case CHILDRENS:
				price = new ChildrensPrice();
				break;
			case REGULAR:
				price = new RegularPrice();
				break;
			case NEW_MOVIE:
				price = new NewMoviePrice();
				break;
			default:
				throw new IllegalArgumentException("Incorrect Price Code");
		}
	}

	/**
	 * 根据影片类型的不同获取费用
	 * @param daysRented:租赁时间
	 * @return
	 */
	public double getCharge(int daysRented) {
		return price.getCharge(daysRented);
	}

	/**
	 * 描述:根据影片的类型以及租期获取常客积分
	 * @param daysRented
	 * @return
	 */
	public int getRenterPointer(int daysRented) {
		if (getPriceCode() == Movie.NEW_MOVIE && daysRented > 1) {
			return 2;
		}
		return 1;
	}

}
Price

package com.xuzengqiang.ssb.movie;

/**
 * 描述:价格抽象类
 * @author xuzengqiang
 * @since 2014-12-16 16:50:47
 */
public abstract class Price {

	/**
	 * 描述:根据不同影片的类型获取价格代号
	 */
	abstract int getPriceCode();
	
	/**
	 * 描述:根据价格代码的不同获取金额
	 * @param daysRented
	 * @return
	 */
	public double getCharge(int daysRented) {
		double result = 0;
		switch (getPriceCode()) {
		// 如果是儿童片:前3天1.5,超过3天每天1.5
			case Movie.CHILDRENS:
				result += 1.5;
				if (daysRented > 3) {
					result += (daysRented - 3) * 1.5;
				}
				break;
			// 如果是普通片:不超过2天2块钱,超过2天每天1.5
			case Movie.REGULAR:
				result += 2;
				if (daysRented > 2) {
					result += (daysRented - 2) * 1.5;
				}
				break;
			// 如果是新片:每天3块S
			case Movie.NEW_MOVIE:
				result += daysRented * 3;
				break;
		}
		return result;
	}
}
这样做仍然不能满足我们的要求,所以我们可以考虑在对应的子类中重写getCharge()方法,然后同样将Price()中的getCharge()方法定义为abstract的:

Price

package com.xuzengqiang.ssb.movie;

/**
 * 描述:价格抽象类
 * @author xuzengqiang
 * @since 2014-12-16 16:50:47
 */
public abstract class Price {

	/**
	 * 描述:根据不同影片的类型获取价格代号
	 */
	abstract int getPriceCode();
	
	/**
	 * 描述:根据价格代码的不同获取金额
	 * @param daysRented
	 * @return
	 */
	abstract double getCharge(int daysRented);
}
ChildrensPrice

package com.xuzengqiang.ssb.movie;

/**
 * 描述:儿童电影
 * @author xuzengqiang
 * @since 2014-12-16 16:52:32
 */
public class ChildrensPrice extends Price {

	@Override
	int getPriceCode() {
		return Movie.CHILDRENS;
	}

	@Override
	public double getCharge(int daysRented) {
		double result = 1.5;
		if (daysRented > 3) {
			result += (daysRented - 3) * 1.5;
		}
		return result;
	}
}
RegularPrice
package com.xuzengqiang.ssb.movie;

/**
 * 描述:普通电影
 * @author xuzengqiang
 * @since 2014-12-16 16:52:32
 */
public class RegularPrice extends Price {

	@Override
	int getPriceCode() {
		return Movie.REGULAR;
	}

	@Override
	public double getCharge(int daysRented) {
		double result = 2;
		if (daysRented > 2) {
			result += (daysRented - 2) * 1.5;
		}
		return result;
	}
}
NewMoviePrice
package com.xuzengqiang.ssb.movie;

/**
 * 描述:新片
 * @author xuzengqiang
 * @since 2014-12-16 16:52:32
 */
public class NewMoviePrice extends Price {

	@Override
	int getPriceCode() {
		return Movie.NEW_MOVIE;
	}
	
	@Override
	public double getCharge(int daysRented) {
		return daysRented * 3;
	}

}

同样的方法我们可以将Movie中的getRenterPointer()方法移动到Price中,只是这个时候我们不将Price中的getRenterPointer()方法设置为abstract,而是一种缺省的方式。

Movie

public int getRenterPointer(int daysRented) {
		return price.getRenterPointer(daysRented);
	}
Price:

/**
 * 描述:根据影片的类型以及租期获取常客积分
 * @param daysRented
 * @return
 */
public int getRenterPointer(int daysRented) {
	return 1;
}
这里的新片需要重写:

NewMoviePrice

@Override
public int getRenterPointer(int daysRented) {
	return daysRented > 1 ? 2 : 1;
}
写到这里可以发现基本上可以满足后续的影片分类、或者修改费用规则、修改积分规则就很简单了












/* * 原始需求背景: * 网宿CDN要按月收取客户的服务费用,根据流量的大小、 * 服务的类型等,收取不同的费用,收费规则如下: * web应用:1000元/M * 流媒体应用:1000元/M*0.7 * 下载应用:1000元/M*0.5 * 月末打印报表时,要罗列每个用户每个频道的费用、客户总费用, * 还要打印该客户的重要性指数,重要性指数=网页流/100+下载流量/600; * * 需求变更场景: * 系统已经开发出来了,接下来,运维部门现在希望对系统做一点修改, * 首先,他们希望能够输出xml,这样可以被其它系统读取和处理,但是, * 这段代码根本不可能在输出xml的代码中复用report()的任何行为,唯一 * 可以做的就是重写一个xmlReport(),大量重复report()中的行为,当然, * 现在这个修改还不费劲,拷贝一份report()直接修改就是了。 * 不久,成本中心又要求修改计费规则,于是我们必须同时修改xmlReport() * 和report(),并确保其一致性,当后续还要修改的时候,复制-黏贴的问题就 * 浮现出来了,这造成了潜在的威胁。 * 再后来,客服部门希望修改服务类型和用户重要性指数的计算规则, * 但还没决定怎么改,他们设想了几种方案,这些方案会影响用户的计费规则, * 程序必须再次同时修改xmlReport()和report(),随着各种规则变得越来越复杂, * 适当的修改点越 来越难找,不犯错误的机会越来越少。 * 现在,我们运用所学的OO原则和方法开始进行改写吧。 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值