《重構》第一章示例 ― 體驗重構的魔力 RefactorSample
來源:程序員人生 發布時間:2015-02-27 08:01:01 閱讀次數:2428次
實驗室已坐定了,環境和氛圍很好,今天老師請來了北京卓爾的孫經理,聊了很多,最大的意外是,任老師是云計算出身,棒,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個簡單的代碼重構示例,重構終了
重構最大的啟發就是:測試、小改、測試、小改....
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈