代码重构读书笔记(C#代码演示) 第一章

由于原书是基于java,这里勇哥改为C#版本。

先引入第一章的开篇例子。

需求如下:

需求:
影片出租,计算每一位顾客的消费金额并打印详单。
操作得告诉程序:顾客租了哪些影片、租期多长,程序便根据租赁时间和影片类型计算出费用。
影片分为三类:普通片、儿童片和新片。
除了计算费用,还要为常客计算积分,积分会根据影片种类是否新片而不同。

Movie  影片类
Rental   租赁类
Customer   顾客类

image.png

priceCode:int   表示影片的种类,普通片、儿童片和新片

daysRented:int   表示出租天数

statement()   结算清单方法



源码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    /*
      代码需要重构的理由?
    虽然编译器不区分代码的美丑,但是当我们打算修改代码的时候,就涉及到了人。
    差劲的系统很难修改的原因是:很难找到修改点。
    如果很难找到修改点,程序员就可能犯错,从而引入bug
     

    这个程序的问题:
    1.  如果希望以html格式输出消费单,则Statement()完全不支持,你得弄一个HtmlStatement()
        这样只能复制粘贴Statement()进行修改
    2.  如果计费标准发生变,则Statement()和HtmlStatement()都得一起修改。
        这里复制粘贴带来的问题就浮现出来,因为要确保两处的一致性,以后这种修改会越来越复杂。
    
  

    原则:如果你发现需要为程序添加一个特性,而代码结构使你无法很方便做到,
          那就先重构那个程序,使特征的添加比较容易进行,然后再添加特性

    */
    public partial class Form1 : Form
    {
        Customer lxy = new Customer("xaioyong");
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            lxy.AddRental(new Rental(new Movie("星球大战1", Movie.NEW_RELEASE), 3));
            lxy.AddRental(new Rental(new Movie("勇敢者的游戏2", Movie.NEW_RELEASE), 4));
            RtbMsg.Text = lxy.Statement();
        }
    }

    /// <summary>
    /// 影片类
    /// </summary>
    public class Movie
    {
        /// <summary>
        /// 儿童片
        /// </summary>
        public const int CHILDRENS = 2;
        /// <summary>
        /// 普通片
        /// </summary>
        public const int REGULAR = 0;
        /// <summary>
        /// 新片
        /// </summary>
        public const 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 void SetPriceCode(int arg)
        {
            _priceCode = arg;
        }

        public string GetTitle()
        {
            return _title;
        }

    }

    /// <summary>
    /// 租赁,表示某个顾客租了一部影片
    /// </summary>
    public class Rental
    {
        private Movie _movie;
        private int _dayRented;

        public Rental(Movie movie, int days)
        {
            _movie = movie;
            _dayRented = days;
        }

        public int GetDaysRented()
        {
            return _dayRented;
        }

        public Movie GetMovie()
        {
            return _movie;
        }

    }

    /// <summary>
    /// 顾客类
    /// </summary>
    public class Customer
    {
        private string _name;
        private List<Rental> _rentals = new List<Rental>();
        
        public Customer(string name)
        {
            _name = name;
        } 

        public void AddRental(Rental arg)
        {
            _rentals.Add(arg);
        }

        public string GetName()
        {
            return _name;
        }

        /// <summary>
        /// 生成顾客消费单
        /// </summary>
        /// <returns></returns>
        public string Statement()
        {
            double totalAmount = 0;
            int fPoints = 0;
            var result = new StringBuilder();
            result.Append($"Rental Record for {GetName()} \n");
            foreach(var m in _rentals)
            {
                double thisAmount = 0;
                switch(m.GetMovie().GetPriceCode())
                {
                    case Movie.REGULAR:
                        thisAmount += 2;
                        if (m.GetDaysRented() > 2)
                            thisAmount += (m.GetDaysRented() - 2) * 1.5;
                        break;
                    case Movie.NEW_RELEASE:
                        thisAmount += m.GetDaysRented() * 3;
                        break;
                    case Movie.CHILDRENS:
                        thisAmount += 1.5;
                        if (m.GetDaysRented() > 3)
                            thisAmount += (m.GetDaysRented() - 3)*1.5;
                        break;
                }
                fPoints++;
                if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
                         m.GetDaysRented() > 1)
                    fPoints++;

                result.Append($"\t {m.GetMovie().GetTitle()}\t{thisAmount}\n");
                totalAmount += thisAmount;
            }

            result.Append($"Amount owed is {totalAmount}\n");
            result.Append($"You earned {fPoints} frequent renter points");
            return result.ToString();

        }

    }



}


(一)拆分长函数,并把较小块的代码移至更适合的类。

这样做的目的是降低代码重复量。

首先我们对长函数Statement() 那个switch动手。


  public string Statement()
        {
            double totalAmount = 0;
            int fPoints = 0;
            var result = new StringBuilder();
            result.Append($"Rental Record for {GetName()} \n");
            foreach (var m in _rentals)
            {
                double thisAmount = amountFor(m);  //这里switch变成了函数调用。
                
                fPoints++;
                if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
                         m.GetDaysRented() > 1)
                    fPoints++;

                result.Append($"\t {m.GetMovie().GetTitle()}\t{thisAmount}\n");
                totalAmount += thisAmount;
            }

            result.Append($"Amount owed is {totalAmount}\n");
            result.Append($"You earned {fPoints} frequent renter points");
            return result.ToString();

        }

        //函数变量还进行了重命名,这是值得的。
        //任何一下傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,
        //才是优秀的程序员
        private double amountFor(Rental aRental)
        {
            double result = 0;
            switch (aRental.GetMovie().GetPriceCode())
            {
                case Movie.REGULAR:
                    result += 2;
                    if (aRental.GetDaysRented() > 2)
                        result += (aRental.GetDaysRented() - 2) * 1.5;
                    break;
                case Movie.NEW_RELEASE:
                    result += aRental.GetDaysRented() * 3;
                    break;
                case Movie.CHILDRENS:
                    result += 1.5;
                    if (aRental.GetDaysRented() > 3)
                        result += (aRental.GetDaysRented() - 3) * 1.5;
                    break;
            }
            return result;
        }


(二)函数功能放错位置

注意上面的amountFor() 函数,它没有用到顾客类Customer的信息,只是使用了Rental类的信息。

因此它放错了位置。

绝大多数情况下,函数应该放在它所用到数据的所属对象内。


        //此函数形参不变,内容变一下。
        private double amountFor(Rental aRental)
        {
            return aRental.GetCharge();
        }
        
        
        //下面函数放到Rental类中去。
        
        public double GetCharge()
        {
            double result = 0;
            switch (GetMovie().GetPriceCode())
            {
                case Movie.REGULAR:
                    result += 2;
                    if (GetDaysRented() > 2)
                        result += (GetDaysRented() - 2) * 1.5;
                    break;
                case Movie.NEW_RELEASE:
                    result += GetDaysRented() * 3;
                    break;
                case Movie.CHILDRENS:
                    result += 1.5;
                    if (GetDaysRented() > 3)
                        result += (GetDaysRented() - 3) * 1.5;
                    break;
            }
            return result;
        }


(三)更新一下新函数的调用方式

一般来讲,尽量去除临时变量,它会导致大量参数被传来传去。

为了这个目的,下面调用了2次m.GetCharge(),浪费了一点性能。

其它对于去掉临时变量这一点,勇哥有一点不一样的观点。

有时候,临时变量的存在,是为了方便调试代码。因为你可以鼠标悬停在临时变量上面查阅结果,但是你无法这样查看函数的返回结果。

所以勇哥觉得,如果是简单变量,且需要返回结果需要被关注时,还是可以用临时变量。

 public string Statement()
        {
            double totalAmount = 0;
            int fPoints = 0;
            var result = new StringBuilder();
            result.Append($"Rental Record for {GetName()} \n");
            foreach (var m in _rentals)
            {
               
                
                fPoints++;
                if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
                         m.GetDaysRented() > 1)
                    fPoints++;

                result.Append($"\t {m.GetMovie().GetTitle()}\t{m.GetCharge()}\n");
                totalAmount += m.GetCharge();
            }

            result.Append($"Amount owed is {totalAmount}\n");
            result.Append($"You earned {fPoints} frequent renter points");
            return result.ToString();

        }


(四)提炼“常客积分计算”的代码。

常客积分就是那个fPoints变量。

此积分计算的责任应该放在Rental类上面。


下面计算常客积分的函数放在Rental类中去。

   public int GetfPoints()
        {
            if (GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
                      GetDaysRented() > 1)
                return 2;
            else
                return 1;
        }

改一下Statement函数的调用。

 public string Statement()
        {
            double totalAmount = 0;
            int fPoints = 0;
            var result = new StringBuilder();
            result.Append($"Rental Record for {GetName()} \n");
            foreach (var m in _rentals)
            {
                fPoints += m.GetfPoints();
                //fPoints++;
                //if (m.GetMovie().GetPriceCode() == Movie.NEW_RELEASE &&
                //         m.GetDaysRented() > 1)
                //    fPoints++;

                result.Append($"\t {m.GetMovie().GetTitle()}\t{m.GetCharge()}\n");
                totalAmount += m.GetCharge();
            }

            result.Append($"Amount owed is {totalAmount}\n");
            result.Append($"You earned {fPoints} frequent renter points");
            return result.ToString();

        }


(五)


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2025年4月    »
123456
78910111213
14151617181920
21222324252627
282930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864