前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

重构

作者头像
用户3467126
发布2019-07-03 18:20:01
8360
发布2019-07-03 18:20:01
举报
文章被收录于专栏:爱编码爱编码爱编码

背景

最近公司做了个项目,深深体会到架构设计以及代码优化有多么的重要。 回头看自己的代码都觉得特别混乱,有时候还要看很久才能看懂,可扩展性特别差,完全是为了完成需求而编码的。说得形象一点就像修水管,最后全部都漏水了。 个人觉得代码重构非常有必要,写程序不但要给机器运行,更让人看的明白。 写代码如写诗一样才行。(内容代码为主,建议实践一下比较好点)

实例

一个图书馆出租书的程序。

  • 计算每一个读者的消费金额并且打印详情清单。
  • 打印信息:
  • 读者租了哪些书、租期多长、根据租借时间和书的类型算出费用。
  • 书分类:普通读本、少儿读本、新书
  • 计算费用,以及计算积分。积分根据书的种类是否为新书而又有所不同。
常见代码

按照实例需求,经常都是类似这样子写代码的,如下: Book书本类 主要是关于书名称和分类信息。

/**
 * 书本
 */
public class Book {

    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 Book() {
    }

    public Book(String title, int priceCode) {
        this.title = title;
        this.priceCode = priceCode;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPriceCode() {
        return priceCode;
    }

    public void setPriceCode(int priceCode) {
        this.priceCode = priceCode;
    }
}

Rental 租借信息 主要是写租借信息,包括书和租借天数的关系。

/**
 * 租借信息
 */
public class Rental {

     private Book book;
     private int daysRented;//租借天数

    public Rental() {
    }

    public Rental(Book book, int daysRented) {
        this.book = book;
        this.daysRented = daysRented;
    }


    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    public int getDaysRented() {
        return daysRented;
    }

    public void setDaysRented(int daysRented) {
        this.daysRented = daysRented;
    }
}

Customer 读者类 主要写租借费用计算以及租借的书的关系。

/**
 * 读者
 */
public class Customer {

    private String name;
    private List<Rental> rentals = new ArrayList();

    public Customer() {
    }

    public Customer(String name) {
        this.name = name;
    }

    //添加租书信息
    public void addRental(Rental rental) {
        rentals.add(rental);
    }

    //生成订单
    public String generateOrder() {

        double total = 0;//计算租借总数量
        int frequentRenterPoints = 0;//计算积分
        String result = "Rental Record for "+getName()+"\n";
        for (Rental rental : rentals) {
            double thisAmount = 0;
            switch (rental.getBook().getPriceCode()){
                case Book.REGULAR:
                    thisAmount += 2;
                    if (rental.getDaysRented() > 2){
                        thisAmount += (rental.getDaysRented() - 2) *1.5;
                    }
                    break;
                case Book.NEW_RELEASE:
                        thisAmount += rental.getDaysRented()*3;
                    break;
                case Book.CHILDRENS:
                    thisAmount += 1.5;
                    if (rental.getDaysRented() > 3){
                        thisAmount += (rental.getDaysRented() - 3) *1.5;
                    }
                    break;
            }

            frequentRenterPoints++;
            if ((rental.getBook().getPriceCode() == Book.NEW_RELEASE) && rental.getDaysRented() >1){
                frequentRenterPoints++;
            }

            if ((rental.getBook().getPriceCode() == Book.NEW_RELEASE) && rental.getDaysRented() >1) {
                frequentRenterPoints++;
            }

            result += "\t"+rental.getBook().getTitle() + "\t"+String.valueOf(thisAmount)+"\n";
            total +=thisAmount;

        }

        result += "Amount owed is "+ String.valueOf(total) +"\n";
        result += "You earned "+ String.valueOf(frequentRenterPoints) +"frequent renter points";

        return result;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

测试类:

/**
 * 一个图书馆出租书的程序。
 * 计算每一个读者的消费金额并且打印详情清单。
 * 打印信息:
 * 读者租了哪些书、租期多长、根据租借时间和书的类型算出费用。
 * 书分类:普通读本、少儿读本、新书
 * 计算费用,以及计算积分。积分根据书的种类是否为新书而又有所不同。
 *
 */
public class Test {

    public static void main(String[] args) {
        Customer customer = new Customer();
        Book book = new Book("Java入门到放弃", Book.NEW_RELEASE);
        Book book1 = new Book("python入门到放弃", Book.CHILDRENS);
        Book book2 = new Book("golang入门到放弃", Book.REGULAR);

        customer.addRental(new Rental(book,8));
        customer.addRental(new Rental(book1,4));
        customer.addRental(new Rental(book2,6));

        customer.setName("zero");
        System.out.println(customer.generateOrder());

    }
}
第一次重构

首先:分析一下上面的代码

  • 1. 结构:

总体结构

  • 2.假如某天产品跑过来(弄死她吧),需要你增加书的分类规则或者计费规则的时候,上面的代码你怎么做呢。估计又写个差不多方法,然而你会发现其实逻辑跟上面的代码非常相似的。还有更好的办法不?

接着:直接看下面的代码重构呗 Book类: 将按照书的不同类型,按照不同价格统计的方法移动到Book类中,因为这个按理应该属于Book类中的。

public class Book {

    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 Book() {
    }

    public Book(String title, int priceCode) {
        this.title = title;
        this.priceCode = priceCode;
    }


    //1.提取统计钱的方法
    public double getCharge(int daysRented) {
        double result = 0;
        switch (getPriceCode()) {
            case Book.REGULAR:
                result += 2;
                if (daysRented > 2) {
                    result += (daysRented - 2) * 1.5;
                }
                break;
            case Book.NEW_RELEASE:
                result += daysRented * 3;
                break;
            case Book.CHILDRENS:
                result += 1.5;
                if (daysRented > 3) {
                    result += (daysRented - 3) * 1.5;
                }
                break;
        }
        return result;
    }



    //1.提取计算会员积分的方法
    public int getFrequentRenterPoints(int daysRented) {
        if ((getPriceCode() == Book.NEW_RELEASE) && daysRented > 1) {
            return 2;
        }
        return 1;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPriceCode() {
        return priceCode;
    }

    public void setPriceCode(int priceCode) {
        this.priceCode = priceCode;
    }
}

Rental 类: 主要是调用提取统计钱和积分的方法。

public class Rental {

    private Book book;
    private int daysRented;

    public Rental() {
    }

    public Rental(Book book, int daysRented) {
        this.book = book;
        this.daysRented = daysRented;
    }

    //1.提取统计钱的方法
    public double getCharge() {
        return book.getCharge(daysRented);
    }

    //1.提取计算会员积分的方法
    public int getFrequentRenterPoints() {
        return book.getFrequentRenterPoints(daysRented);
    }


    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    public int getDaysRented() {
        return daysRented;
    }

    public void setDaysRented(int daysRented) {
        this.daysRented = daysRented;
    }
}

Customer 读者类 主要是去掉多余的临时变量total,frequentRenterPoints等。

public class Customer {

    private String name;
    private List<Rental> rentals = new ArrayList();

    public Customer() {
    }

    public Customer(String name) {
        this.name = name;
    }

    //添加租书信息
    public void addRental(Rental rental) {
        rentals.add(rental);
    }

    //生成订单
    public String generateOrder() {
        String result = "Rental Record for " + getName() + "\n";
        for (Rental rental : rentals) {
            result += "\t" + rental.getBook().getTitle() + "\t" + String.valueOf(rental.getCharge()) + "\n";
        }
        result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
        result += "You earned " + String.valueOf(getFrequentRenterPoints()) + "frequent renter points";

        return result;
    }

    //获取购买总数
    private double getTotalCharge() {
        double result = 0;
        for (Rental rental : rentals) {
            result += rental.getCharge();
        }
        return result;
    }

    //统计积分
    private double getFrequentRenterPoints() {
        double result = 0;
        for (Rental rental : rentals) {
            result += rental.getFrequentRenterPoints();
        }
        return result;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

最后 测试结果跟上面的一样,就是将代码的结果调动一下。 现在大致的UML类图如下:

第二次重构

经过第一次重构,还是没有实现需求修改增加多个分类的效果。那么接下来使用接口抽象来再次重构。

Price接口 接口抽象两个规约方法,具体如下

public abstract class Price {

    abstract int getPriceCode();
    //1.提取统计总价的方法
    abstract double getCharge(int daysRented);

    //1.提取计算会员积分的方法
    public int getFrequentRenterPoints(int daysRented) {
        return 1;
    }

}

RegularPrice 普通的书价格类

public class RegularPrice extends Price {
    @Override
    int getPriceCode() {
        return Book.REGULAR;
    }

    @Override
    public double getCharge(int daysRented) {
        double result = 2;
        if (daysRented >2) {
            result += (daysRented - 2) * 1.5;
        }
        return result;
    }
}

ChildrensPrice 少儿读物类价格

public class ChildrensPrice extends Price {
    @Override
    int getPriceCode() {
        return Book.CHILDRENS;
    }


    @Override
    public double getCharge(int daysRented) {
        double result = 1.5;
        if (daysRented >3) {
            result += (daysRented - 3) * 1.5;
        }
        return  result;
    }
}

NewReleasePrice 新书型类价格

public class NewReleasePrice extends Price {
    @Override
    int getPriceCode() {
        return Book.NEW_RELEASE;
    }

    @Override
    public double getCharge(int daysRented) {
        return daysRented * 3;
    }

    @Override
    public int getFrequentRenterPoints(int daysRented) {
        return (daysRented > 1)?2:1;
    }
}

Book类 将priceCode换成Price。

public class Book {

    public static final int CHILDRENS = 2;
    public static final int REGULAR = 0;
    public static final int NEW_RELEASE = 1;


    private String title;
    private Price _price;

    public Book() {
    }

    public Book(String title, int priceCode) {
        this.title = title;
        setPriceCode(priceCode);
    }

    //1.提取统计数量的方法
    public double getCharge(int daysRented) {
        return _price.getCharge(daysRented);
    }

    //1.提取计算会员积分的方法
    public int getFrequentRenterPoints(int daysRented) {
        if ((getPriceCode() == Book.NEW_RELEASE) && daysRented > 1) {
            return 2;
        }
        return 1;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getPriceCode() {
        return _price.getPriceCode();
    }

    public void setPriceCode(int arg) {
        switch (arg){
            case REGULAR:
                _price = new RegularPrice();
                break;
            case CHILDRENS:
                _price = new ChildrensPrice();
                break;
            case NEW_RELEASE:
                _price = new NewReleasePrice();
                break;
                default:
                    throw new IllegalArgumentException("Incorrect Price code");
        }
    }
}

最终类图如下:

源码:https://github.com/xbmchina/reconsitution
总结

大致的工作如下:

  • 抽离成方法。
  • 移动方法到所属的类。
  • 用多态性替换条件。
  • 自我封装。
  • 用策略替换类型代码。

最后想说: 如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那么就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。 写代码就应该像写诗一样,而不是没BUG,我就不动它。

参考文章

【重构】作者: Martin Fowler

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实例
    • 常见代码
      • 第一次重构
        • 第二次重构
        • 源码:https://github.com/xbmchina/reconsitution
        • 总结
        • 参考文章
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档