本文共 7321 字,大约阅读时间需要 24 分钟。
前言:
重构发生的时机应该在什么时候呢?
正确:一边写着代码,当完成之后,马上想着是否能够进行重构。
错误:写完代码之后,等以后有时间去重构。
即重构本来就不是一件应该特别拨出时间做的事情,重构应该随时随地进行。
书上有这么一句话,我非常喜欢:懒惰是程序员的一种美德。正如有句话说,不要让你的“勤奋”毁掉了你,有着异曲同工之妙。《重构改善既有代码的设计》的作者说,“我是个很懒惰的程序员,我的懒惰表现形式之一就是:我总是记不住自己写过的代码”,“我不是个伟大的程序员,我只是有着一些优秀习惯的好程序员”。
什么是重构:
在不改变代码外部行为的前提下,对代码做出修改,以改进程序的内部结构。本质上说,重构是在代码写好之后改进它的设计(注意:重构不一定能够提高性能,它提供了一种更高效且受控的代码整理技术,重构又与重写不同,重写代码是发生在现有的代码根本不能工作)。
举个栗子:(在笔试的时候经常会有类似重构的例子)
场景:一个影片出租店的程序,计算每一位顾客的消费金额并打印详单。操作者告诉程序:顾客租了哪些影片、租期多长,程序便根据租赁时间和影片单算出费用。影片分为三类:普通片、儿童片和新片。除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而有不同。
未重构前的代码如下:
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; } }
转载地址:http://xmomi.baihongyu.com/