0%

002-重构入门案例

摘要:重构入门案例

概述

示例:影片出租店应用程序,计算每一位顾客的消费金额并打印详单。操作者告诉程序:顾客租客哪些影片、租期多长,程序便根据租赁时间和影片类型计算出费用。

影片分为三类:普通片、儿童片、新片。除了计算费用还要为常客计算积分,积分会根据足片种类是否为新片而有不同。

Ver01 示例-简单逻辑思维,没有扩展

  1. 使用几个类表达上述
    • Movie(影片类):数据类
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public class Movie {
      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 Movie(String title, int priceCode) {
      _title = title;
      _priceCode = priceCode;
      }

      public int getPriceCode() {
      return _priceCode;
      }

      public String getTitle() {
      return _title;
      }

      public void setPriceCode(int priceCode) {
      _priceCode = priceCode;
      }
      }
    • Rental(租赁类) 表示某个顾客租了一部影片
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class Rental {
      private Movie _movie; // 影片
      private int _daysRented; // 租期

      public Rental(Movie movie, int daysRented) {
      _movie = movie;
      _daysRented = daysRented;
      }

      public int getDaysRented() {
      return _daysRented;
      }

      public Movie getMovie() {
      return _movie;
      }
      }
    • Customer(顾客) 数据以及相应的访问函数。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      public class Customer {
      private String _name; // 姓名
      private Vector _rentals = new Vector(); // 租借记

      public Customer(String name) {
      _name = name;
      };

      public void addRental(Rental arg) {
      _rentals.addElement(arg);
      }

      public String getName() {
      return _name;
      }

      public String statement() {
      double totalAmount = 0; // 总消费金。
      int frequentRenterPoints = 0; // 常客积点
      Enumeration rentals = _rentals.elements();
      String result = "Rental Record for " + getName() + "\n";
      while (rentals.hasMoreElements()) {
      double thisAmount = 0;
      Rental each = (Rental) rentals.nextElement(); // 取得一笔租借记。
      // determine amounts for each line
      switch (each.getMovie().getPriceCode()) { // 取得影片出租价格
      case Movie.REGULAR: // 普通片
      thisAmount += 2;
      if (each.getDaysRented() > 2)
      thisAmount += (each.getDaysRented() - 2) * 1.5;
      break;
      case Movie.NEW_RELEASE: // 新片
      thisAmount += each.getDaysRented() * 3;
      break;
      case Movie.CHILDRENS: // 儿童。
      thisAmount += 1.5;
      if (each.getDaysRented() > 3)
      thisAmount += (each.getDaysRented() - 3) * 1.5;
      break;
      }
      // add frequent renter points (累计常客积点。
      frequentRenterPoints++;
      // add bonus for a two day new release rental
      if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE)
      && each.getDaysRented() > 1)
      frequentRenterPoints++;
      // show figures for this rental(显示此笔租借记录)
      result += "\t" + each.getMovie().getTitle() + "\t"
      + String.valueOf(thisAmount) + "\n";
      totalAmount += thisAmount;
      }
      // add footer lines(结尾打印)
      result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
      result += "You earned " + String.valueOf(frequentRenterPoints)
      + " frequent renter points";
      return result;
      }
      }
    • 单元测试
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class CustomerTest {
      @Test
      public void statement() {
      Customer customer = new Customer("John");
      String title = "Titanic";
      int priceCode = 2;
      int _daysRented = 7;
      Movie movie = new Movie(title, priceCode);
      Rental rental = new Rental(movie, _daysRented);
      customer.addRental(rental);
      String result = customer.statement();
      System.out.println(result);
      }
      }
  2. 说明
    单一逻辑,没有扩展性以及面向对象思维

Ver02 分解重组statement()

  1. 避免长函数、逻辑独立的抽成方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    public class Customer {
    private String _name; // 姓名
    private Vector _rentals = new Vector(); // 租借记

    public Customer(String name) {
    _name = name;
    };

    public void addRental(Rental arg) {
    _rentals.addElement(arg);
    }

    public String getName() {
    return _name;
    }

    public String statement() {
    double totalAmount = 0; // 总消费金。
    int frequentRenterPoints = 0; // 常客积点
    Enumeration rentals = _rentals.elements();
    String result = "Rental Record for " + getName() + "\n";
    while (rentals.hasMoreElements()) {
    Rental each = (Rental) rentals.nextElement(); // 取得一笔租借记。
    double thisAmount = amountFor(each);
    // add frequent renter points (累计常客积点。
    frequentRenterPoints++;
    // add bonus for a two day new release rental
    if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE)
    && each.getDaysRented() > 1)
    frequentRenterPoints++;
    // show figures for this rental(显示此笔租借记录)
    result += "\t" + each.getMovie().getTitle() + "\t"
    + String.valueOf(thisAmount) + "\n";
    totalAmount += thisAmount;
    }
    // add footer lines(结尾打印)
    result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
    result += "You earned " + String.valueOf(frequentRenterPoints)
    + " frequent renter points";
    return result;
    }

    private double amountFor(Rental rental) {
    double thisAmount = 0;
    // determine amounts for each line
    switch (rental.getMovie().getPriceCode()) { // 取得影片出租价格
    case Movie.REGULAR: // 普通片
    thisAmount += 2;
    if (rental.getDaysRented() > 2)
    thisAmount += (rental.getDaysRented() - 2) * 1.5;
    break;
    case Movie.NEW_RELEASE: // 新片
    thisAmount += rental.getDaysRented() * 3;
    break;
    case Movie.CHILDRENS: // 儿童。
    thisAmount += 1.5;
    if (rental.getDaysRented() > 3)
    thisAmount += (rental.getDaysRented() - 3) * 1.5;
    break;
    }
    return thisAmount;
    }
    }

Ver03 金额计算代码,迁移至Rental类

参看:github ver03

Ver04 同类聚合,单一原则

  1. Movie中放置与影片直接相关方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
        public class Movie {
    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 Movie(String title, int priceCode) {
    _title = title;
    _priceCode = priceCode;
    }

    public int getPriceCode() {
    return _priceCode;
    }

    public String getTitle() {
    return _title;
    }

    public void setPriceCode(int priceCode) {
    _priceCode = priceCode;
    }

    public double getCharge(int daysRented) {
    double result = 0;
    switch (getPriceCode()) {
    case Movie.REGULAR:
    result += 2;
    if (daysRented > 2)
    result += (daysRented - 2) * 1.5;
    break;
    case Movie.NEW_RELEASE:
    result += daysRented * 3;
    break;
    case Movie.CHILDRENS:
    result += 1.5;
    if (daysRented > 3)
    result += (daysRented - 3) * 1.5;
    break;
    }
    return result;
    }

    public int getFrequentRenterPoints(int daysRented) {
    if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1)
    return 2;
    else
    return 1;
    }
    }
    ```

    2. 租赁放置相关方法
    ```java
    public class Rental {
    private Movie _movie; // 影片
    private int _daysRented; // 租期

    public Rental(Movie movie, int daysRented) {
    _movie = movie;
    _daysRented = daysRented;
    }

    public int getDaysRented() {
    return _daysRented;
    }

    public Movie getMovie() {
    return _movie;
    }

    double getCharge() {
    return _movie.getCharge(_daysRented);
    }

    public int getFrequentRenterPoints() {
    return _movie.getFrequentRenterPoints(_daysRented);
    }
    }
  2. 顾客相关

    public class Customer {
        private String _name; // 姓名
        private Vector _rentals = new Vector(); // 租借记
    
        public Customer(String name) {
            _name = name;
        };
    
        public void addRental(Rental arg) {
            _rentals.addElement(arg);
        }
    
        public String getName() {
            return _name;
        }
    
        public String statement() {
            int frequentRenterPoints = 0;
            Enumeration rentals = _rentals.elements();
            String result = "Rental Record for " + getName() + "\n";
            while (rentals.hasMoreElements()) {
                Rental each = (Rental) rentals.nextElement();
                frequentRenterPoints += each.getFrequentRenterPoints();
                // show figures for this rental
                result += "\t" + each.getMovie().getTitle() + "\t"
                        + String.valueOf(each.getCharge()) + "\n";
            }
            // add footer lines
            result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
            result += "You earned " + String.valueOf(getTotalFrequentRenterPoints())
                    + " frequent renter points";
            return result;
        }
    
        public String htmlStatement() {
            Enumeration rentals = _rentals.elements();
            String result = "<H1>Rentals for <EM>" + getName() + "</EM></ H1><P>\n";
            while (rentals.hasMoreElements()) {
                Rental each = (Rental) rentals.nextElement();
                // show figures for each rental
                result += each.getMovie().getTitle() + ": "
                        + String.valueOf(each.getCharge()) + "<BR>\n";
            }
            // add footer lines
            result += "<P>You owe <EM>" + String.valueOf(getTotalCharge())
                    + "</EM><P>\n";
            result += "On this rental you earned <EM>"
                    + String.valueOf(getTotalFrequentRenterPoints())
                    + "</EM> frequent renter points<P>";
            return result;
        }
    
        // 译注:此即所谓query method
        private int getTotalFrequentRenterPoints() {
            int result = 0;
            Enumeration rentals = _rentals.elements();
            while (rentals.hasMoreElements()) {
                Rental each = (Rental) rentals.nextElement();
                result += each.getFrequentRenterPoints();
            }
            return result;
        }
    
        // 译注:此即所谓query method
        private double getTotalCharge() {
            double result = 0;
            Enumeration rentals = _rentals.elements();
            while (rentals.hasMoreElements()) {
                Rental each = (Rental) rentals.nextElement();
                result += each.getCharge();
            }
            return result;
        }
    
    }

重新分析,抽象模型

  1. 抽象过程一、

    • 影片实体:抽象类Movie,子类:普通片、儿童片、新片.每个子类有各自计费方法实现。常见操作是抽象功能接口
      影片可能会根据不同的类型有不同的收费规则,所以影片可以根据类型有不同的子类,但是有个问题,影片如果换了类型,但是class是不能换的,所以可以考虑影片没有子类,但是影片包含一个Price的类,这个类可以根据不同类型有不同子类。Movie有一个Price类的引用,不同的类型对应不同的Price子类,调用Price类的方法来计算收费。

    参看:github ver03

一分也是爱,两分情更浓【还没有人赞赏,支持一下呗】