程序员人生 网站导航

《重构》第一章示例 ― 体验重构的魔力 RefactorSample

栏目:php教程时间:2015-02-27 08:01:01

       实验室已坐定了,环境和氛围很好,今天老师请来了北京卓尔的孙经理,聊了很多,最大的意外是,任老师是云计算出身,棒,java以后必定是要接触云计算的。

1个月了,没有认真写博客,也没有认真学习,数据库的学习进度1直拖拖拉拉到现在也没有弄定,sad。。。接了1个任务,写1个基于Web的实验室内部的管理系统,拿来练手,以后就是企业项目1个CC呼唤中心的1模块,对接卓尔,第1次接触实际项目,还甚么都不会,激动却又忐忑。但,再1起重新燃起了学习的热火,来吧。


今天,看了1下《重构》的第1章,深深的被吸引了....

Let‘s go,1起走进重构的世界吧!


示例 

情形:顾客租电影,电影分3种,儿童票,普通片,新片,儿童片2天之内1.5元,超过两天,每天加收1.5元,普通片3天之内2元,超过两天,每天加收2元,新片每天3元,每租1次电影积分加1,如果是新片,且租期超过2天,积分加2。

分析:

以面向对象的思想分析,触及对象顾客,影片,外加1个租赁类作为顾客和影片的桥接

编写代码

影片类(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方法

顾客类(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() + " "; 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()⑵)*1.5+1.5):1.5; break; case Movie.REGULARS: thisAmount += (r.getRentDays()>3)?((r.getRentDays()⑶)*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+=" " + r.getMovie().getName() + " " + String.valueOf(thisAmount) + " "; } result += "Amount is " + String.valueOf(totalAmount) + "元 "; 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()); } }

以上代码完成了示例需求,但是代码写的...太烂了,我自己写绝对也是这个模样。。。代码烂,怎样办?重构!

第1步:1劳永逸 ―  修改测试代码


为了方便测试,我们将打印结果作为1个字符串保存起来。

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 朱小妹 " + " 葫芦娃 1.5 " + " 神雕侠侣 2.0 " + " 何以笙箫默 9.0 " + "Amount is 12.5元 " + "frequent is 4分"; System.out.println(c.statement().equals(result)); } }

以后的测试结果,true就是修改正确,false就是修改失败。


第2步:1石2鸟  ―  分解customer类statement方法


纵观所有代码,惟独customer类的statement代码最多,最乱,看着烦,看switch更烦,而switch的作用就是1个计算1个临时变量thisAmount的作用,而临时变量越多对1个程序来讲就越容易出错,既然容易出错,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() + " "; for (Rental r : rentals ) { result += " " + r.getMovie().getName() + " " + String.valueOf(amountFor(r)) + " "; } result += "Amount is " + String.valueOf(getTotalAmount()) + "元 "; 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()⑵)*1.5+1.5):1.5; break; case Movie.REGULARS: thisAmount += (r.getRentDays()>3)?((r.getRentDays()⑶)*2+2):2; break; case Movie.NEW_RELEASE: thisAmount += r.getRentDays() * 3; break; default:throw new Exception("类型异常"); } return thisAmount; } }


这样statement方法庞大和临时变量多的问题都解决了,进行测试,true,修改正确

第3步:各回各家 ― 做自己该干的事


重新审视1下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() + " "; for (Rental r : rentals ) { result += " " + r.getMovie().getName() + " " + String.valueOf(r.getOne()) + " "; } result += "Amount is " + String.valueOf(getTotalAmount()) + "元 "; 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; } }

更改后的rental,起了1个桥梁作用

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; } }

更改后的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⑵)*1.5+1.5):1.5; break; case Movie.REGULARS: thisAmount += (RentDays>3)?((RentDays⑶)*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; } }

测试OK,没有问题。


第4步 利用多态

现在所有的类,都在干自己的事,movie类的getMoney方法的代码量看着还是不太好,既然是3种电影只是类型不同而已,那末久完全可以向上抽取使用抽象类

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⑵)*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⑶)*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() + " "; for (Rental r : rentals ) { result += " " + r.getMovie().getName() + " " + String.valueOf(r.getOne()) + " "; } result += "Amount is " + String.valueOf(getTotalAmount()) + "元 "; 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 朱小妹 " + " 葫芦娃 1.5 " + " 神雕侠侣 2.0 " + " 何以笙箫默 9.0 " + "Amount is 12.5元 " + "frequent is 4分"; System.out.println(c.statement().equals(result)); } }

至此,1个简单的代码重构示例,重构终了

重构最大的启发就是:测试、小改、测试、小改....


------分隔线----------------------------
------分隔线----------------------------

最新技术推荐