一个月了,没有认真写博客,也没有认真学习,数据库的学习进度一直拖拖拉拉到现在也没有搞定,sad。。。
今天,看了一下《重构》的第一章,深深的被吸引了....
示例
情景:顾客租电影,电影分三种,儿童票,普通片,新片,儿童片2天以内1.5元,超过两天,每一天加收1.5元,普通片3天以内2元,超过两天,每一天加收2元,新片每天3元,每租一次电影积分加一,如果是新片,且租期超过2天,积分加二。
分析:
以面向对象的思想分析,涉及对象顾客,影片,外加一个租赁类作为顾客和影片的桥接
编写代码
影片类(Movie):
public class Movie { public static final int CHILDRENS = 0; public static final int REGULARS = 1; public static final int NEW_RELEASE = 2; private String name; private int movType; public Movie(String name, int movType) { this.name = name; this.movType = movType; } name: set 和 get 方法 movType: set 和get 方法 }
为了节省篇幅,Movie类的name、movType的set和get就不贴了。
租赁类(Rental):
public class Rental { private Movie movie; private int rentDays; public Rental(Movie movie,int rentDays) { this.movie = movie; this.rentDays = rentDays; } movie 和 rentDays的set和get方法
public class Customer { private String name; private ArrayList<Rental> rentals = new ArrayList<Rental>(); public Customer(String name) { this.name = name; } public void addRent(Rental r){ rentals.add(r); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String statement() throws Exception { String result = "Rental report for " + getName() + "\n"; double totalAmount = 0; int frequent = 0; for(Rental r : rentals){ double thisAmount = 0; switch (r.getMovie().getMovType()){ case Movie.CHILDRENS : thisAmount += (r.getRentDays()>2)?((r.getRentDays()-2)*1.5+1.5):1.5; break; case Movie.REGULARS: thisAmount += (r.getRentDays()>3)?((r.getRentDays()-3)*2+2):2; break; case Movie.NEW_RELEASE: thisAmount += r.getRentDays() * 3; break; default:throw new Exception("类型异常"); } totalAmount += thisAmount; frequent += (r.getMovie().getMovType()==Movie.NEW_RELEASE && r.getRentDays()>2)?2:1; result+="\t" + r.getMovie().getName() + "\t" + String.valueOf(thisAmount) + "\n"; } result += "Amount is " + String.valueOf(totalAmount) + "元\n"; result += "frequent is " + String.valueOf(frequent)+"分"; return result; } }
public class Test { public static void main(String[] args) throws Exception { Movie m1 = new Movie("葫芦娃",0);//儿童片 Movie m2 = new Movie("神雕侠侣",1);//普通片 Movie m3 = new Movie("何以笙箫默",2);//新片 Customer c = new Customer("朱小妹"); c.addRent(new Rental(m1,2));//1.5 c.addRent(new Rental(m2,2));//2 c.addRent(new Rental(m3,3));//9 System.out.println(c.statement()); } }
第一步:一劳永逸 — 修改测试代码
为了方便测试,我们将打印结果作为一个字符串保存起来。
public class Test { public static void main(String[] args) throws Exception { Movie m1 = new Movie("葫芦娃",0);//儿童片 Movie m2 = new Movie("神雕侠侣",1);//普通片 Movie m3 = new Movie("何以笙箫默",2);//新片 Customer c = new Customer("朱小妹"); c.addRent(new Rental(m1,2));//1.5 c.addRent(new Rental(m2,2));//2 c.addRent(new Rental(m3,3));//9 String result = "Rental report for 朱小妹\n" + "\t葫芦娃\t1.5\n" + "\t神雕侠侣\t2.0\n" + "\t何以笙箫默\t9.0\n" + "Amount is 12.5元\n" + "frequent is 4分"; System.out.println(c.statement().equals(result)); } }
第二步:一石二鸟 — 分解customer类statement方法
纵观所有代码,唯独customer类的statement代码最多,最乱,看着烦,看switch更烦,而switch的作用就是一个计算一个临时变量thisAmount的作用,而临时变量越多对一个程序来说就越容易出错,既然容易出错,totalAmount和frement有什么用,还占地方,留它们干嘛,拿走,单独封装。
更改后的customer类:
public class Customer { private String name; private ArrayList<Rental> rentals = new ArrayList<Rental>(); public Customer(String name) { this.name = name; } public void addRent(Rental r){ rentals.add(r); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String statement() throws Exception { String result = "Rental report for " + getName() + "\n"; for (Rental r : rentals ) { result += "\t" + r.getMovie().getName() + "\t" + String.valueOf(amountFor(r)) + "\n"; } result += "Amount is " + String.valueOf(getTotalAmount()) + "元\n"; result += "frequent is " + String.valueOf(getTotalFrequent())+"分"; return result; } private double getTotalAmount() throws Exception { double totalAmount = 0; for(Rental r : rentals){ totalAmount += amountFor(r); } return totalAmount; } private int getTotalFrequent(){ int frequent = 0; for(Rental r : rentals){ frequent += (r.getMovie().getMovType()==Movie.NEW_RELEASE && r.getRentDays()>2)?2:1; } return frequent; } private double amountFor(Rental r) throws Exception { double thisAmount = 0; switch (r.getMovie().getMovType()){ case Movie.CHILDRENS : thisAmount += (r.getRentDays()>2)?((r.getRentDays()-2)*1.5+1.5):1.5; break; case Movie.REGULARS: thisAmount += (r.getRentDays()>3)?((r.getRentDays()-3)*2+2):2; break; case Movie.NEW_RELEASE: thisAmount += r.getRentDays() * 3; break; default:throw new Exception("类型异常"); } return thisAmount; } }
这样statement方法庞大和临时变量多的问题都解决了,进行测试,true,修改正确
第三步:各回各家 — 做自己该干的事
重新审视一下customer类,你会发现它很多它不该干的事,switch计算单次租赁花费和积分这个事,应该电影类干吧,花多钱,得多少分,电影自身最清楚
问题还是存在switch身上,最好不要在另一个对象的属性基础上运用switch,如果非要使用,也应该是在对象自己的数据上使用,而不是在别人的数据上使用。
在rental类中增加getOneMoney方法和getOneFre方法,customer类删除amountFor方法,修改getTotalFrequent方法,switch搬家到Movie类中
更改后的customer类
只变动了getTotalFrequent方法for循环
public class Customer { private String name; private ArrayList<Rental> rentals = new ArrayList<Rental>(); public Customer(String name) { this.name = name; } public void addRent(Rental r){ rentals.add(r); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String statement() throws Exception { String result = "Rental report for " + getName() + "\n"; for (Rental r : rentals ) { result += "\t" + r.getMovie().getName() + "\t" + String.valueOf(r.getOne()) + "\n"; } result += "Amount is " + String.valueOf(getTotalAmount()) + "元\n"; result += "frequent is " + String.valueOf(getTotalFrequent())+"分"; return result; } private double getTotalAmount() throws Exception { double totalAmount = 0; for(Rental r : rentals){ totalAmount += r.getOne(); } return totalAmount; } private int getTotalFrequent(){ int frequent = 0; for(Rental r : rentals){ frequent += r.getOneFre(); } return frequent; } }
public class Rental { private Movie movie; private int rentDays; public Rental(Movie movie,int rentDays) { this.movie = movie; this.rentDays = rentDays; } public int getRentDays() { return rentDays; } public void setRentDays(int rentDays) { this.rentDays = rentDays; } public Movie getMovie() { return movie; } public double getOne() throws Exception { return getMovie().getMoney(rentDays) ; } public int getOneFre(){ return getMovie().getFrequent(rentDays); } public void setMovie(Movie movie) { this.movie = movie; } }
/** * Created by Kevy on 2015/1/21. */ /*编写电影类代码*/ public class Movie { public static final int CHILDRENS = 0; public static final int REGULARS = 1; public static final int NEW_RELEASE = 2; private String name; private int movType; public Movie(String name, int movType) { this.name = name; this.movType = movType; } public int getMovType() { return movType; } public void setMovType(int movType) { this.movType = movType; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney(int RentDays) throws Exception { double thisAmount = 0; switch (getMovType()){ case Movie.CHILDRENS : thisAmount += (RentDays>2)?((RentDays-2)*1.5+1.5):1.5; break; case Movie.REGULARS: thisAmount += (RentDays>3)?((RentDays-3)*2+2):2; break; case Movie.NEW_RELEASE: thisAmount += RentDays * 3; break; default:throw new Exception("类型异常"); } return thisAmount ; } public int getFrequent(int RentDays){ return (getMovType()==NEW_RELEASE && RentDays>2)?2:1; } }
第四步 利用多态
现在所有的类,都在干自己的事,movie类的getMoney方法的代码量看着还是不太好,既然是三种电影只是类型不同而已,那么久完全可以向上抽取使用抽象类。
public interface Type { public int getType(); public double getMovMeony(int days); }
public class ChildrenMov implements Type { public int getType() { return 0; } public double getMovMeony(int days) { return (days>2)?((days-2)*1.5+1.5):1.5; } }
public class RegularMov implements Type { public int getType() { return 1; } public double getMovMeony(int days) { return (days>3)?((days-3)*2+2):2; } }
public class NewMov implements Type { public int getType() { return 2; } public double getMovMeony(int days) { return days * 3; } }
import java.util.ArrayList; /** * Created by Kevy on 2015/1/21. */ public class Customer { private String name; private ArrayList<Rental> rentals = new ArrayList<Rental>(); public Customer(String name) { this.name = name; } public void addRent(Rental r){ rentals.add(r); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String statement() throws Exception { String result = "Rental report for " + getName() + "\n"; for (Rental r : rentals ) { result += "\t" + r.getMovie().getName() + "\t" + String.valueOf(r.getOne()) + "\n"; } result += "Amount is " + String.valueOf(getTotalAmount()) + "元\n"; result += "frequent is " + String.valueOf(getTotalFrequent())+"分"; return result; } private double getTotalAmount() throws Exception { double totalAmount = 0; for(Rental r : rentals){ totalAmount += r.getOne(); } return totalAmount; } private int getTotalFrequent(){ int frequent = 0; for(Rental r : rentals){ frequent += r.getOneFre(); } return frequent; } }
/** * Created by Kevy on 2015/1/21. */ public class Rental { private Movie movie; private int rentDays; public Rental(Movie movie,int rentDays) { this.movie = movie; this.rentDays = rentDays; } public int getRentDays() { return rentDays; } public void setRentDays(int rentDays) { this.rentDays = rentDays; } public Movie getMovie() { return movie; } public double getOne() throws Exception { return getMovie().getMoney(rentDays) ; } public int getOneFre(){ return getMovie().getFrequent(rentDays); } public void setMovie(Movie movie) { this.movie = movie; } }
/** * Created by Kevy on 2015/1/21. */ /*编写电影类代码*/ public class Movie { public static final int CHILDRENS = 0; public static final int REGULARS = 1; public static final int NEW_RELEASE = 2; private String name; private Type t; public Movie(String name, int movType) throws Exception { this.name = name; setT(movType); } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney(int RentDays) { return t.getMovMeony(RentDays); } public int getFrequent(int RentDays){ return (t.getType()==NEW_RELEASE && RentDays>2)?2:1; } public void setT(int d) throws Exception { switch (d){ case CHILDRENS :t = new ChildrenMov(); break; case REGULARS:t = new RegularMov(); break; case NEW_RELEASE:t = new NewMov(); break; default:throw new Exception("类型错误"); } } }
import sun.plugin2.message.ModalityChangeMessage; /** * Created by Kevy on 2015/1/21. */ /* 编写测试代码 */ public class Test { public static void main(String[] args) throws Exception { Movie m1 = new Movie("葫芦娃",0);//儿童片 Movie m2 = new Movie("神雕侠侣",1);//普通片 Movie m3 = new Movie("何以笙箫默",2);//新片 Customer c = new Customer("朱小妹"); c.addRent(new Rental(m1,2));//1.5 c.addRent(new Rental(m2,2));//2 c.addRent(new Rental(m3,3));//9 String result = "Rental report for 朱小妹\n" + "\t葫芦娃\t1.5\n" + "\t神雕侠侣\t2.0\n" + "\t何以笙箫默\t9.0\n" + "Amount is 12.5元\n" + "frequent is 4分"; System.out.println(c.statement().equals(result)); } }
至此,一个简单的代码重构示例,重构完毕
重构最大的启发就是:测试、小改、测试、小改....