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

     一个月了,没有认真写博客,也没有认真学习,数据库的学习进度一直拖拖拉拉到现在也没有搞定,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方法

顾客类(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";
        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));
    }
}

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


第二步:一石二鸟  —  分解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;
    }
}

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

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

测试OK,没有问题。


第四步 利用多态

现在所有的类,都在干自己的事,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));

    }
}

至此,一个简单的代码重构示例,重构完毕

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


你可能感兴趣的:(《重构》第一章示例 — 体验重构的魔力 RefactorSample)