您的位置:首页 > 其它

利用策略枚举对讨厌的Switch Case 语句进行重构

2017-07-14 00:39 627 查看
前言:

重构发生的时机应该在什么时候呢?

正确:一边写着代码,当完成之后,马上想着是否能够进行重构。

错误:写完代码之后,等以后有时间去重构。

即重构本来就不是一件应该特别拨出时间做的事情,重构应该随时随地进行。

书上有这么一句话,我非常喜欢:懒惰是程序员的一种美德。正如有句话说,不要让你的“勤奋”毁掉了你,有着异曲同工之妙。《重构改善既有代码的设计》的作者说,“我是个很懒惰的程序员,我的懒惰表现形式之一就是:我总是记不住自己写过的代码”,“我不是个伟大的程序员,我只是有着一些优秀习惯的好程序员”。

什么是重构:

在不改变代码外部行为的前提下,对代码做出修改,以改进程序的内部结构。本质上说,重构是在代码写好之后改进它的设计(注意:重构不一定能够提高性能,它提供了一种更高效且受控的代码整理技术,重构又与重写不同,重写代码是发生在现有的代码根本不能工作)。

举个栗子:(在笔试的时候经常会有类似重构的例子)

场景:一个影片出租店的程序,计算每一位顾客的消费金额并打印详单。操作者告诉程序:顾客租了哪些影片、租期多长,程序便根据租赁时间和影片单算出费用。影片分为三类:普通片、儿童片和新片。除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而有不同。

未重构前的代码如下:

public class Customer {
//Movie只是一个简单的纯数据类
private static 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){
_title = title;
_priceCode = priceCode;
}

public int getPriceCode(){
return _priceCode;
}

public void setPriceCode(int arg){
_priceCode = arg;
}

public String getTitle(){
return _title;
}
}
//Rental表示某个租客租了一部电影
private static class Rental{
private Movie _movie;
private int _daysRented;

public Rental(Movie movie, int daysRented){
_movie = movie;
_daysRented = daysRented;
}
public int getDaysRented(){
return _daysRented;
}
public Movie getMovie(){
return _movie;
}
}

private String _name;
private Vector _rentals = new Vector();

public Customer(String name){
_name = name;
}
public void addRental(Rental arg){
_rentals.addElement(arg);
}
public String getName(){
return _name;
}
//以下代码是需要重构的主题部分
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();

//determine amounts for each line
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;
}
//add frequent renter points
frequentRenterPoints ++;
//add bonus for a two day new release rental
if((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) &&
each.getDaysRented() > 1)
frequentRenterPoints ++;

//show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" +
String.valueOf(thisAmount)  + "\n";
totalAmount += thisAmount;
}

//add footer lines
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPoints) +
" frequent renter points";
return result;
}
}

分析:

首先,我一看到switch代码,就要看到分支的情况,这里case只有三个分支类型:Movie.REGULAR,Movie.NEW_RELEASE,Movie.CHILDRENS,如果Movie中新增了一种类型此时case就不支持,因此,至少要添加一个default分支,并且throw 自定义找不到该值的异常,这样程序更严谨。对该例重构的原则如下:

(1)对于switch case 这种语句,我习惯用策略枚举进行重构(具体用法参见:Effective Java 中文版 第2版P128-P136),通过枚举中枚举类型来分类型,对于不同类型处理具体策略采用定义一个抽象方法。

(2)根据高内聚、低耦合的原则,如果某个业务只是跟某个类相关,则将这个业务移动到该类中进行处理。

重构后的代码如下:
public class CustomerV2 {
private static class RentalV2{
private MovieV2 _movie;
private int _daysRented;

public RentalV2(MovieV2 movie, int daysRented){
_movie = movie;
_daysRented = daysRented;
}
public int getDaysRented(){
return _daysRented;
}
public MovieV2 getMovie(){
return _movie;
}

public int getRenterPointers(){
if(_movie.equals(MovieV2.NEW_RELEASE) && _daysRented > 1)
return 2;
return 1;
}
}
//通过策略枚举
private static enum MovieV2{
CHILDRENS(2) {
@Override
public double getAmount(int daysRented) {
double amount = 0;
amount += 2;
if(daysRented > 2){
amount += (daysRented -2) * 1.5;
}
return amount;
}
},
REGULAR(0) {
@Override
public double getAmount(int daysRented) {
double amount = 0;
amount += daysRented * 3;
return amount;
}
},
NEW_RELEASE(1) {
@Override
public double getAmount(int daysRented) {
double amount = 0;
if(daysRented > 3)
amount += (daysRented -3) * 1.5;
return amount;
}
};

private String _title;
private final int _priceCode;
MovieV2(int priceCode){
this._priceCode = priceCode;
}

public abstract double getAmount(int daysRented);
}

private String _name;
private Vector _rentals = new Vector();

public CustomerV2(String name){
_name = name;
}
public void addRental(RentalV2 arg){
_rentals.addElement(arg);
}
public String getName(){
return _name;
}
//计算amount和fequent分开
public String statement(){
double totalAmount = getTotalAmounts();
int frequentRenterPointers = getFrequentRenterPointers();
return getReturnData(totalAmount, frequentRenterPointers);
}

private double getTotalAmounts(){
Enumeration rentals = _rentals.elements();
double totalAmount = 0;
while(rentals.hasMoreElements()){
RentalV2 rental = (RentalV2) rentals.nextElement();
MovieV2 movie = rental.getMovie();
double amount = movie.getAmount(rental.getDaysRented());
totalAmount += amount;
}
return totalAmount;
}

private int getFrequentRenterPointers(){
Enumeration rentals = _rentals.elements();
int frequentRenterPointers = 0;
while(rentals.hasMoreElements()){
RentalV2 rental = (RentalV2) rentals.nextElement();
frequentRenterPointers += rental.getRenterPointers();
}
return frequentRenterPointers;
}

private String getReturnData(double totalAmount, int frequentRenterPointers){
String result = "Rental Record for " + getName() + "\n";
result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
result += "You earned " + String.valueOf(frequentRenterPointers) +
" frequent renter points";
return result;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: