精华内容
下载资源
问答
  • 【《重构 改善既有代码设计》学习笔记】重构:第一个案例 本篇文章的内容来自《重构 改善既有代码设计》一书学习笔记整理笔记并且加上自己的浅显的思考总结! 一、简单的例子 一个影片出租店用的程序,...

    【《重构 改善既有代码的设计》学习笔记】重构:第一个案例

    本篇文章的内容来自《重构 改善既有代码的设计》一书学习笔记整理并且加上自己的浅显的思考总结!

    一、简单的例子

    一个影片出租店用的程序,计算每一位顾客的消费金额,并打印详单。

    详单打印 顾客租了哪些影片、租期多长,影片类型 、单个影片费用、总费用 。 除了费用外,还要计算顾客的积分,不同种类租片积分不同。

    注:影片为为三类:普通片、儿童片、新片

    Think:如果是你做这样一个功能,用java编写,你会怎么去写代码?可以简单思考一下整个逻辑在往下面看!

    简单点设计三个表我,一个顾客表,一个影片表,还有一个是租用记录表。那么在java中顾客这个类是要有的 Customer 、然后影片这个类要有 Movie ,还有一个租期类 Rental。

    1、三个类组合实现一个打印详单的功能

    摘录原书 的代码 ,稍做字段调整 。Customer类如下,有一个 statement()打印详单!

    /**
     * 顾客
     *
     * @author:dufy
     * @version:1.0.0
     * @date 2019/1/21
     */
    public class Customer {
    
    	private String _name;
    
    	private Vector rentals = new Vector();
    
    	public Coustomer(String _name) {
    		this._name = _name;
    	}
    
    	public void addRental(Rental rental) {
    		rentals.addElement(rental);
    	}
    
    	public String getName() {
    		return _name;
    	}
    
    	public String statement() {
    		//总费用
    		double totalAmount = 0;
    		// 积分数
    		int integralNum = 0;
    		Enumeration erts = rentals.elements();
    		String result = "租用的顾客名字为: " + getName() + "\n";
    		result += "=========================================\n";
    		while (erts.hasMoreElements()) {
    			// 每个影片的费用
    			double thisAmount = 0;
    			Rental rental = (Rental) erts.nextElement();
    
    			int daysRented = rental.getDaysRented();
    			Integer type = rental.getMovie().getType();
    			switch (type) {
    				case Movie.REGULAR:
    					thisAmount += 2;
    					if (daysRented > 2) {
    						thisAmount += (daysRented - 2) * 1.5;
    					}
    					break;
    				case Movie.NEW_RELEASE:
    					thisAmount += daysRented * 3;
    					break;
    
    				case Movie.CHILDRENS:
    					thisAmount += 1.5;
    					if (daysRented > 3) {
    						thisAmount += (daysRented - 3) * 1.5;
    					}
    					break;
    			}
    
    			integralNum++;
    			// 如果是租用新片则累积多加1积分
    			if (type == Movie.NEW_RELEASE && daysRented > 1) {
    				integralNum++;
    			}
    
    			//打印租用影片详情
    			result += "影片名称:" + rental.getMovie().getTitle() + "\t 影片类型:" + rental.getMovie().getMovieTypeName(type)
    					+ "\n";
    			result += "租期:" + daysRented + "天\t 费用:" + thisAmount + "元\n";
    			result += "----------------------------------------------------\n";
    			totalAmount += thisAmount;
    		}
    
    		result += ">>>>>>>>>>>>>>>>>>总费用:" + totalAmount + "元<<<<<<<<<<<<<<<<<<<< \n";
    		result += ">>>>>>>>>>>>>>>>>>本次积分:" + integralNum + "<<<<<<<<<<<<<<<<<<<<";
    		return result;
    	}
    
    }
    
    
    

    Movie类的代码

    public class Movie {
    
    
    	/**
    	 * 普通片
    	 */
    	public static final int REGULAR = 0;
    	/**
    	 *新片
    	 */
    	public static final int NEW_RELEASE = 1;
    	/**
    	 * 儿童片
    	 */
    	public static final int CHILDRENS = 2;
    
    	/**
    	 * 影片的名称
    	 */
    	private String title;
    	/**
    	 * 影片的类型
    	 */
    	private Integer type;
    
    	public Movie() {
    	}
    
    	public Movie(String title, Integer type) {
    		this.title = title;
    		this.type = type;
    	}
    
    	public String getTitle() {
    		return title;
    	}
    
    	public void setTitle(String title) {
    		this.title = title;
    	}
    
    	public Integer getType() {
    		return type;
    	}
    
    	public void setType(Integer type) {
    		this.type = type;
    	}
    
    	public String getMovieTypeName(int type){
    		String name = "";
    		if(REGULAR == type){
    			name = "普通片";
    		}else if(NEW_RELEASE == type){
    			name = "新片";
    		}else if(CHILDRENS == type){
    			name = "儿童片";
    		}else{
    			name = "未知";
    		}
    		return name;
    	}
    }
    

    Rental 类代码:

    public class Rental {
    
    	/**
    	 * 影片
    	 */
    	private Movie movie;
    
    	/**
    	 * 租用的天数
    	 */
    	private int daysRented;
    
    	public Rental() {
    	}
    
    	public Rental(Movie movie, int daysRented) {
    		this.movie = movie;
    		this.daysRented = daysRented;
    	}
    
    	public Movie getMovie() {
    		return movie;
    	}
    
    	public void setMovie(Movie movie) {
    		this.movie = movie;
    	}
    
    	public int getDaysRented() {
    		return daysRented;
    	}
    
    	public void setDaysRented(int daysRented) {
    		this.daysRented = daysRented;
    	}
    
    }
    

    验证功能:

    public class AppMain {
    
    	public static void main(String[] args) {
    
    		Customer c = new Customer("admin");
    
    		Movie m1 = new Movie("功夫",Movie.REGULAR);
    		Movie m2 = new Movie("功夫熊猫",Movie.CHILDRENS);
    		Movie m3 = new Movie("功夫之王",Movie.NEW_RELEASE);
    
    		Rental r1 = new Rental(m1,4);
    		Rental r2 = new Rental(m2,2);
    		Rental r3 = new Rental(m3,5);
    
    		c.addRental(r1);
    		c.addRental(r2);
    		c.addRental(r3);
    
    		String statement = c.statement();
    
    		System.out.println(statement);
    
    	}
    }
    
    租用的顾客名字为: admin
    =========================================
    影片名称:功夫	 影片类型:普通片
    租期:4天	 费用:5.0----------------------------------------------------
    影片名称:功夫熊猫	 影片类型:儿童片
    租期:2天	 费用:1.5----------------------------------------------------
    影片名称:功夫之王	 影片类型:新片
    租期:5天	 费用:15.0----------------------------------------------------
    >>>>>>>>>>>>>>>>>>总费用:21.5<<<<<<<<<<<<<<<<<<<< 
    >>>>>>>>>>>>>>>>>>本次积分:4<<<<<<<<<<<<<<<<<<<<
    

    2、简单分析此功能代码

    上面的这一些代码实现了需求功能,但是你有没有觉得那个地方看起来很不舒服。【 代码重构还是需要有一定的编写代码的经验和编写过一定的代码】如果看起来没有任何感觉的话,那说明还需要在多多历练,多写一些代码多思考。

    其实对于这个简单的需求来说,上面的程序已经完全可以正常的工作,但是有没有觉得 Customerstatement 方法做的事情太多了,很多事情原本应该有其他类完成的,却都放到statement 中完成了。并且当有新需求加入的时候,修改代码也是很麻烦的,而且容易出错。

    (1)、如果我们打印详单 要换一种输出的风格?对于上面的代码要怎么进行新功能的添加?

    • 对于这个问题,我们可以快速的处理方式为: 拷贝一份 statement 进行修改就ok了。

    (2)、在有多种详单打印输出风格的前提下, 如果我们影片的计费方式发生变化,又或者是积分计算方式方式变化,又会如何?

    • 此时我们需要将所有 statement 方法同时进行修改,并确保各处修改的一致性。【这样的代码后期维护起来成本很高,并且容易出错

    对于上面的一些需求,我们都可以根据已有的代码【拷贝粘贴】进行功能的快速开发完成。但是随着需求的越来越复杂,在statement 中 能够用于 适当修改的点越来越难找,不犯错的机会也越来越少

    》》》【书籍小结】《《《

    如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便的达成目标,那就先重构那个程序,使得特性的添加比较容易进行,然后再添加特性。

    》》》【我的思考小结】《《《

    对于很多人,加入一家公司或者加入一个项目,并不是所有的功能代码都是新开发的,很大一部分时间在维护之前开发好的代码/功能,因为开发者的水平或者需求的变更,导致在原有代码上在开发新的需求变得异常的复杂和艰难,此时就一定要考虑要重构掉这一块的代码了,不要想着绕过,勇敢面对。重构,真的是可以锻炼自己思维和代码的编写能力

    二、重构的第一步

    【书籍总结】

    重构的第一步永远相同: 那就是为即将修改的代码建立一组可靠的测试环境

    这样做的目的是防止在重构的过程引入新的bug, 好的测试是重构的根本,花时间建立一个优良的测试机制完全值得。测试机制是要让程序能够自我检验,否则在重构后花大把时间进行比对,这样会降低开发速度。【程序员在修改bug的过程中又可能在创建新的bug】

    三、分解并重组 statement()

    当一个方法的长度长的离谱,看到这样的函数,就应该想着能否进行拆解。代码块越小,代码的功能越容易管理。也方便后面的维护!

    代码重构目标:希望将长长的函数切开,把较小的块移动到更合适的类中,最终能够降低代码重复和扩展

    上面的statement()方法中,switch看起来就是一个明显的逻辑泥团。把它提炼到单独的函数中似乎比较好。

    然后在分析函数内的局部变量和参数,其中statement() while循环中有两个: thisAmountdaysRentedtype, thisAmount会被修改,后面两个不会被修改。任何不会被修改的变量都可以被当成参数传入新的函数,至于被修改的变量就要格外小心

    1、第一次重构—方法抽离

    01

    第一次修改的代码:

    public String statement() {
        // 省略
        while (erts.hasMoreElements()) {
            // 每个影片的费用
            double thisAmount = 0;
            Rental rental = (Rental) erts.nextElement();
            int daysRented = rental.getDaysRented();
            int type = rental.getMovie().getType();
    
            thisAmount = amountFor(type,daysRented);
    
        }
        // 省略
        return result;
    }
    
    /**
    	 * 计算影片租费
    	 * @param type 影片类型
    	 * @param daysRented 租期
    	 * @return
    	 */
    private double amountFor(int type,int daysRented){
        double thisAmount = 0;
        switch (type) {
            case Movie.REGULAR:
                thisAmount += 2;
                if (daysRented > 2) {
                    thisAmount += (daysRented - 2) * 1.5;
                }
                break;
            case Movie.NEW_RELEASE:
                thisAmount += daysRented * 3;
                break;
    
            case Movie.CHILDRENS:
                thisAmount += 1.5;
                if (daysRented > 3) {
                    thisAmount += (daysRented - 3) * 1.5;
                }
                break;
        }
        return thisAmount;
    }
    

    看起来怎么改了这么一点点东西啊! 需要注意的是 : 重构步骤的本质,每次修改的幅度都要很小,这样任何错误都很容易发现【说白了就是步子要迈的小一点,否则容易扯着蛋】

    》》》【书籍小结】《《《

    重构技术就是以 **微小 **的步伐修改程序,如果你犯下错误,很容易发现它。

    》》》【我的思考小结】《《《

    就目前而言,上面的这个重构,相比大家在正在编写代码的时候也会操作,现在的工具IDEA,对于重构的支持也很好,使用好就是提供效率和提升代码质量。

    2、第二次重构—变量名的修改

    这次相对简单,是一种思想的转变,上面的 type 局部变量,在命名上相对比较好了,但是还是能够进一步优化! 可以改为 movieType ! 具体的修改代码不粘贴了。

    更改变量名称 是值得的行为吗? 作者说,绝对值得,我认为也绝对值得。要不你写的变量是 a 、b、c 或者 i、j、k,这样的代码真的好吗? 好的代码应该清楚的表达出自己的功能,变量名称是代码清晰的关键【代码要表现自己的目的】

    》》》【书籍小结】《《《

    任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是真正优秀的程序员。

    》》》【我的思考小结】《《《

    我遇到过一些同事吐槽说目前接受的这个项目的代码写的就是一坨,注释没有,变量定义也不知是做什么!然而 当他去开发一个功能的时候,他写的代码也是 一坨xiang! 当你看到项目中之前别人代码写的不好的,可以抱怨代码,指责之前开发这个项目的人,但是请不要继续学习/效仿他们,继续写那种像一坨xiang的代码

    3、第三次重构—代码搬家

    第一次重构的时候抽出一个方法 amountFor ,但是amountFor是用来计算 影片租期的费用的,似乎 此方法放到Customer类中有些不妥。 绝大多数情况下,函数应该放到它所使用的数据的所属对象内。这样我们就应该将amountFor 放入到 Rental类中。

    如下图所示:

    02

    但是“搬家” 之后应该去掉参数。并且在“搬家”后 修改函数名称!具体的修改代码如下:

    public class Rental {
        /**
    	 * 计算 影片租费
    	 * @return 租费金额
    	 */
        public double getCharge(){
            double resultAmount = 0;
            int rentalDays = getDaysRented();
            switch (getMovie().getType()) {
                case Movie.REGULAR:
                    resultAmount += 2;
                    if (rentalDays > 2) {
                        resultAmount += (rentalDays - 2) * 1.5;
                    }
                    break;
                case Movie.NEW_RELEASE:
                    resultAmount += rentalDays * 3;
                    break;
    
                case Movie.CHILDRENS:
                    resultAmount += 1.5;
                    if (rentalDays > 3) {
                        resultAmount += (rentalDays - 3) * 1.5;
                    }
                    break;
            }
            return resultAmount;
        }
    }
    // 其他代码省略...
    public class Customer {
        public String statement() {
            while (erts.hasMoreElements()) {
                thisAmount = amountFor(rental);
            }
        }
        
        /**
    	 * 计算影片租费
    	 * @param rental
    	 * @return
    	 */
    	private double amountFor(Rental rental){
    		return rental.getCharge();
    	}
    }
    

    改完代码后,可以使用运行 AppMain 进行验证! 改完后需要验证通过!

    提炼 积分计算代码

    由于有了上面上面的基础,积分提炼这一块的代码,我们也单独抽离成一个函数,并且将函数进行搬家到Rental类中。

    修改前后对比图:

    03

    修改后的代码如下:

    public class Rental {
        //其他代码省略
    	/**
    	 *计算积分
    	 */
    	public int getIntegralPoints(){
    		if (getMovie().getType() == Movie.NEW_RELEASE && getDaysRented() > 1) {
    			return 2;
    		}
    		return 1;
    	}
    }
    public class Customer {
        public String statement() {
            //其他省略...
            integralNum += rental.getIntegralPoints();
            //其他省略...
        }
    }
    

    还是再次强调一下:重构的时候最好小步前进, 做一次搬移,在编译、再测试。这样才能使出错的几率最小

    4、第四次重构—去除临时变量

    statement()中还有临时变量,有时候,临时变量确实能让程序的开发变的简单和快速!但是 临时变量也可能是个问题,它们只有在自己的函数中才有效,如果临时变量太多,导致函数冗长复杂

    下面就将计算 总费用 totalAmount和 积分 integralNum 临时变量 进行去除操作。

    public class Customer {
        
        public String statement() {
    		//省略...
    		while (erts.hasMoreElements()) {
    			Rental rental = (Rental) erts.nextElement();
    			int movieType = rental.getMovie().getType();
                //省略...
    		}
    
    		result += ">>>>>>>>>>>>>>>>>>总费用:" + getTotalCharge() + "元<<<<<<<<<<<<<<<<<<<< \n";
    		result += ">>>>>>>>>>>>>>>>>>本次积分:" + getIntegralNum() + "<<<<<<<<<<<<<<<<<<<<";
    		return result;
    	}
    /**
    	 * 计算影片租费
    	 * @return
    	 */
    	private double getTotalCharge(){
    		double result = 0;
    		Enumeration erts = rentals.elements();
    		while (erts.hasMoreElements()) {
    			Rental rental = (Rental) erts.nextElement();
    			result += rental.getCharge();
    		}
    		return result;
    	}
    
    	/**
    	 * 计算积分
    	 * @return
    	 */
    	private double getIntegralNum(){
    		double result = 0;
    		Enumeration erts = rentals.elements();
    		while (erts.hasMoreElements()) {
    			Rental rental = (Rental) erts.nextElement();
    			result += rental.getIntegralPoints();
    		}
    		return result;
    	}
    }
    

    看了这次的重构,会发现一下问题,那就是性能。原本只需要执行一次的while循环,现在需要执行三次了。 如果while 循环耗时很多,那就可能大大降低程序的性能。 但请注意 前面这句话的加粗词,只是如果 、可能。 除非进行评测,否则无法确定循环 的执行时间,也无法知道这个循环是否被经常使用以至于影响系统的整体性能。 重构的时候不必担心这些,优化时你才需要担心他们。到优化的时候你已处于一个比较有利的位置,可以有更多选择完成有效优化。

    5、 实现用另一种方式打印详单

    通过上面的代码重构,现在用想使用另一种方式打印租用详单,整个代码就简单很多了。此时 脱下“重构”的帽子,带上“添加功能”的帽子。实现一个打印htmlStatement() 的方法。

    public String htmlStatement() {
        Enumeration erts = rentals.elements();
        String result = "<title>租用的顾客名字为: " + getName() + "</title>\n";
        result += "<p>================================</p>\n";
        while (erts.hasMoreElements()) {
            // 每个影片的费用
            Rental rental = (Rental) erts.nextElement();
            int movieType = rental.getMovie().getType();
    
            //打印租用影片详情
            result += "影片名称:" + rental.getMovie().getTitle() + "\t 影片类型:" + rental.getMovie().getMovieTypeName(movieType)
                + "\n";
            result += "租期:" + rental.getDaysRented() + "天\t 费用:" + rental.getCharge() + "元\n";
            result += "<p>---------------------------</p>\n";
        }
    
        result += "<span>总费用:" + getTotalCharge() + "元<span>\n";
        result += "<span>本次积分:" + getIntegralNum() + "<span>";
        return result;
    }
    
    <title>租用的顾客名字为: admin</title>
    <p>================================</p>
    影片名称:功夫	 影片类型:普通片
    租期:4天	 费用:5.0<p>---------------------------</p>
    影片名称:功夫熊猫	 影片类型:儿童片
    租期:2天	 费用:1.5<p>---------------------------</p>
    影片名称:功夫之王	 影片类型:新片
    租期:5天	 费用:15.0<p>---------------------------</p>
    <span>总费用:21.5<span>
    <span>本次积分:4.0<span>
    

    通过计算逻辑的提炼,可以很快完成一个htmlStatement() 甚至更多的打印详单的方式。 并且最要的是如果租费或者积分的计算发生任何变化,我只需要在一个地方进行修改就可以了,而不需要在每一个打印详单的方法中进行修改然后复制粘贴到其他方法中。 这样的简单重构就发生在你我每天开发的项目之中,相信我,多花费的这些时间绝对是值得的

    6、第五次重构-运用多态取代与价格相关的条件逻辑

    在实际的开发过程中,用户的需求一直不断,在上面目前的重构的代码中,他们准备修改影片分类规则。我们尚不清楚他们想怎么做,但似乎新分类法很快就要引入,影片分类发生变化,则费用计算和积分计算也可能会发生变化。所以必须进入费用计算和积分计算中,把因条件而已的代码(switch语句内case的字句)替换掉,让我们重新开始重构之路。

    看一下之前我们重构之后,计算租费的方法:

    public class Rental {
        /**
    	 * 影片
    	 */
    	private Movie movie;
        // 省略...
        
        /**
    	 * 计算 影片租费
    	 * @return 租费金额
    	 */
    	public double getCharge(){
    		double resultAmount = 0;
    		int rentalDays = getDaysRented();
    		switch (getMovie().getType()) {
    			case Movie.REGULAR:
    				resultAmount += 2;
    				if (rentalDays > 2) {
    					resultAmount += (rentalDays - 2) * 1.5;
    				}
    				break;
    			case Movie.NEW_RELEASE:
    				resultAmount += rentalDays * 3;
    				break;
    
    			case Movie.CHILDRENS:
    				resultAmount += 1.5;
    				if (rentalDays > 3) {
    					resultAmount += (rentalDays - 3) * 1.5;
    				}
    				break;
    		}
    		return resultAmount;
    	}
    }
    
    

    使用switch 语句最好是在对象自己的数据上使用,而不是在别人的数据上使用,此时getCharge 应该移动到Movie类中。 移过去后,我们需要将租期作为参数传递进去,因为租期长度来自Rental对象。

    思考:为什么是选择将租期长度传给Movie对象,而不是将影片类型传递给Rental对象呢?

    因为在本系统中可能发生的变化是加入新的影片类型,这种变化带有不稳定倾向,如果影片类型有变化,我们希望尽量控制它造成的影响,所以选择在Movie对象中计算费用。

    积分的计算和租费思考类似,修改后代码:

    public class Rental {
        /**
    	 * 影片
    	 */
    	private Movie movie;
        /**
    	 * 租用的天数
    	 */
    	private int daysRented;
        // 省略...
        
       /**
    	 * 计算 影片租费
    	 * @return 租费金额
    	 */
    	public double getCharge(){
    		return movie.getCharge(daysRented);
    	}
    
    	/**
    	 *计算积分
    	 */
    	public int getIntegralPoints(){
    		return movie.getIntegralPoints(daysRented);
    	}
    }
    public class Movie {
    /**
    	 * 计算 影片租费
    	 * @return 租费金额
    	 */
    	public double getCharge(int rentalDays){
    		double resultAmount = 0;
    
    		switch (getType()) {
    			case Movie.REGULAR:
    				resultAmount += 2;
    				if (rentalDays > 2) {
    					resultAmount += (rentalDays - 2) * 1.5;
    				}
    				break;
    			case Movie.NEW_RELEASE:
    				resultAmount += rentalDays * 3;
    				break;
    
    			case Movie.CHILDRENS:
    				resultAmount += 1.5;
    				if (rentalDays > 3) {
    					resultAmount += (rentalDays - 3) * 1.5;
    				}
    				break;
    		}
    		return resultAmount;
    	}
    
    	/**
    	 *计算积分
    	 */
    	public int getIntegralPoints(int rentalDays){
    		if (getType() == Movie.NEW_RELEASE && rentalDays > 1) {
    			return 2;
    		}
    		return 1;
    	}
    
    }
    

    修改完后,一定记得测试! 测试通过在继续开干。

    如果我们有数种影片种类,不同的影片有自己计费法。

    Movie (科幻片、爱情片、岛国动作片…),这么一来,每一种类的片子有自己的一套计费方法,那么 此时我们可以使用策略模式。(积分也是一样的)

    此段稍微和书中有不同,如想看到详细细节请看书**《重构 改善既有代码的设计》。**

    整个修改:

    新增

    • Price
    public abstract class Price {
    
    	abstract int getMovieType();
    
    	abstract double getCharge(int daysRental);
    
    	/**
    	 * 如果是大型项目积分的计算建议还是不要和租费放一起
    	 * 这里因为是演示demo就放一起了
    	 * @return
    	 */
    	abstract int getIntegralPoints(int daysRental);
    }
    
    • ChildrensMoviePrice
    public class ChildrensMoviePrice extends Price {
    	@Override
    	int getMovieType() {
    		return Movie.CHILDRENS;
    	}
    
    	@Override
    	double getCharge(int daysRental) {
    		double resultAmount = 0;
    		resultAmount += 1.5;
    		if (daysRental > 3) {
    			resultAmount += (daysRental - 3) * 1.5;
    		}
    		return resultAmount;
    	}
    
    	@Override
    	int getIntegralPoints(int daysRental) {
    		return 1;
    	}
    }
    
    • NewReleaseMoviePrice
    public class NewReleaseMoviePrice extends Price {
    	@Override
    	int getMovieType() {
    		return Movie.NEW_RELEASE;
    	}
    
    	@Override
    	double getCharge(int daysRental) {
    		return daysRental * 3;
    	}
    
    	@Override
    	int getIntegralPoints(int daysRental) {
    		if (getMovieType() == Movie.NEW_RELEASE && daysRental > 1) {
    			return 2;
    		}
    		return 1;
    
    	}
    }
    
    • RegularMoviePrice
    public class RegularMoviePrice extends Price {
    
    	@Override
    	int getMovieType() {
    		return Movie.REGULAR;
    	}
    
    	@Override
    	double getCharge(int daysRental) {
    		double resultAmount = 0;
    		resultAmount += 2;
    		if (daysRental > 2) {
    			resultAmount += (daysRental - 2) * 1.5;
    		}
    		return resultAmount;
    	}
    
    	@Override
    	int getIntegralPoints(int daysRental) {
    		return 1;
    	}
    }
    

    修改

    public class Movie {
        // 省略其他....
    	public Movie(String title, Integer type) {
    		this.title = title;
    		setType(type);
    	}
    
    	public Integer getType() {
    		return price.getMovieType();
    	}
    
    	public void setType(Integer type) {
    		switch (type) {
    			case REGULAR:
    				price = new RegularMoviePrice();
    				break;
    			case NEW_RELEASE:
    				price = new NewReleaseMoviePrice();
    				break;
    
    			case CHILDRENS:
    				price = new ChildrensMoviePrice();
    				break;
    		}
    	}
    
    	/**
    	 * 计算 影片租费
    	 * @return 租费金额
    	 */
    	public double getCharge(int rentalDays){
    		return price.getCharge(rentalDays);
    	}
    
    	/**
    	 *计算积分
    	 */
    	public int getIntegralPoints(int rentalDays){
    		return price.getIntegralPoints(rentalDays);
    	}
    }
    

    修改到这里,如果你是一步步按照步骤操作,那么最后重构完的代码你要有了,我这里就不在贴一次了。再次强调一下,还是测试

    通过上面的这一次简单重构,后面在来多个种类的影片我们也可以很从容的应对,整个代码的可维护性和可扩展性得到了很大的提升。

    四、总结

    通过一个简单的例子,你有没有对“重构改怎么做”有那么一点点感觉,重构的整个过程会有很多的技巧,重构行为使得代码的责任分配更合理,代码的维护更轻松。希望我们在编写代码的开始就有这种思维,有好的代码风格,能够写出好的代码。

    重构的节奏: 测试,小修改、测试、小修改…,以这种节奏重构,能够快速安全的前进。

    》》》【我的思考小结】《《《

    看了第一章,我觉得这本书值得好好品读,作为开发人员,我们或多或少会在自己项目的开发中留下一些技术债,这里面就有 为了方便 复制重复的方法,但是一遇到需要修改,很多地方都要进行修改。当初是快了,后面却慢了! 快到最后变成了慢,最终导致要来还这个债务。 希望看文章的所有伙伴,可以提前就有这种思维方式,那就可以避免后期的很多问题了。

    PS:排除 一些 我就是写代码的,后期的维护和我没有关系,也不愿自己代码能越写越好的人。 因为我就曾遇到这样的人,他的想法就是我开发完这个功能,说不定我那天走了,反正后面也不是我维护。 对于这类型的人,我们不能改变他们,但是我希望如果你遇到了,请不要学他。不管我们在这家公司呆多久,对自己的代码负责,对自己的人生负责。


    如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

    如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
    祝你今天开心愉快!


    欢迎访问我的csdn博客和关注的个人微信公众号!

    愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人。

    不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

    博客首页 : http://blog.csdn.net/u010648555

    © 阿飞(dufyun)

    展开全文
  • 测试结果: 1. 作者首先构建了一个简单的示例来说明重构的重要性。...除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而不同。 2. 详细代码实现如下 Refector_1.h #ifndef REFECT...


    测试结果:
    1. 作者首先构建了一个简单的示例来说明重构的重要性。
    影片出租店:计算每一位顾客的消费金额并打印详单。
    操作者告诉程序:顾客租了哪些影片,租期多长,程序便根据租赁时间和影片类型算出费用。影片分为三类:普通片,儿童片和新片。除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而有不同。

    2. 详细代码实现如下

    Refector_1.h
    
    
    #ifndef REFECTOR_1_H
    #define REFECTOR_1_H
    #include <QString>
    #include <QVector>
    
    class Movie
    {
    public:
        Movie(QString title, int priceCode);
        const static int CHILDREN = 2;
        const static int REGULAR = 0;
        const static int NEW_RELEASE = 1;
        int GetPriceCode();
        void SetPriceCode(int priceCode);
        QString GetTitle();
    
    private:
        QString m_Title;
        int     m_PriceCode;
    };
    
    
    
    class Rental
    {
    public:
        Rental(Movie *movie, int daysRented);
        int GetDaysRented();
        Movie* GetMovie();
    private:
        Movie* m_Movie;
        int m_DaysRented;
    
    
    };
    
    class Customer
    {
    public:
        Customer(QString name);
        void AddRental(Rental *rental);
        QString GetName();
        QVector<Rental *> GetRentals();
        QString Statement();
    private:
        QString m_Name;
        QVector<Rental *> m_Rentals;
    
    };
    
    #endif // REFECTOR_1_H
    
    Refector_1.cpp
    
    
    #include "Refector_1.h"
    #include <QDebug>
    
    
    Movie::Movie(QString title, int priceCode)
    {
        m_Title = title;
        m_PriceCode = priceCode;
    }
    
    int Movie::GetPriceCode()
    {
        return m_PriceCode;
    }
    
    void Movie::SetPriceCode(int priceCode)
    {
        m_PriceCode = priceCode;
    }
    
    QString Movie::GetTitle()
    {
        return m_Title;
    }
    
    Rental::Rental(Movie *movie, int daysRented)
    {
        m_Movie =movie;
        m_DaysRented = daysRented;
    }
    
    int Rental::GetDaysRented()
    {
        return m_DaysRented;
    }
    
    Movie *Rental::GetMovie()
    {
        return m_Movie;
    }
    
    Customer::Customer(QString name)
    {
        m_Name = name;
    }
    
    void Customer::AddRental(Rental *rental)
    {
        m_Rentals.push_back(rental);
    }
    
    QString Customer::GetName()
    {
        return  m_Name;
    }
    
    QVector<Rental *> Customer::GetRentals()
    {
        return  m_Rentals;
    }
    
    QString Customer::Statement()
    {
        double TotalAmout = 0;
        int FrequentRenterPoints = 0;
        QString result = "Rental Record for " + GetName() + "\n";
        for (Rental *rental : GetRentals())
        {
            double ThisAmount = 0;
            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::CHILDREN:
                ThisAmount += 1.5;
                if (rental->GetDaysRented() > 3)
                {
                    ThisAmount += (rental->GetDaysRented() - 3)*1.5;
                }
                break;
            }
            FrequentRenterPoints++;
            if (Movie::NEW_RELEASE == (rental->GetMovie()->GetPriceCode()) &&
                    rental->GetDaysRented() > 1 )
            {
                FrequentRenterPoints++;
            }
            result += "\t" + rental->GetMovie()->GetTitle() + "\t" + QString("%1").arg(ThisAmount) + "\n";
            TotalAmout += ThisAmount;
        }
    
        result += "Amount owed is " + QString("%1").arg(TotalAmout) + "\n";
        result += "You earned " + QString("%1").arg(FrequentRenterPoints) + " frequent renter points";
        qDebug() << qPrintable(result) ;
        return  result;
    }
    

    main.cpp

        Movie movie1("Movie1", 0);
        Movie movie2("Movie2", 1);
        Movie movie3("Movie3", 2);
        Movie movie4("Movie4", 0);
        Movie movie5("Movie5", 1);
        Movie movie6("Movie6", 2);
        Movie movie7("Movie7", 0);
        Rental rental1(&movie1, 1);
        Rental rental2(&movie2, 2);
        Rental rental3(&movie3, 3);
        Rental rental4(&movie4, 4);
        Rental rental5(&movie5, 5);
        Rental rental6(&movie6, 6);
        Rental rental7(&movie7, 7);
        Customer ctmer("tester");
        ctmer.AddRental(&rental1);
        ctmer.AddRental(&rental2);
        ctmer.AddRental(&rental3);
        ctmer.AddRental(&rental4);
        ctmer.AddRental(&rental5);
        ctmer.AddRental(&rental6);
        ctmer.AddRental(&rental7);
        ctmer.Statement();

    执行结果:

    3. 代码中出现的问题
    1)statement() 函数过长
    2)可复用性太差。 显示格式只能为QString, 不能在html或者其他不支持该类型的平台上显示。
    4. 重构的方法
    1) 分解并重组 statement

    • Extract Method: 将switch 提炼到独立函数中
      • A: 找出局部变量和参数: rental ,  ThisAount
      • B:  将不被修改的变量当成参数出入到新函数
      • C: 如果被修改的只有1个,可以将其变为返回值
      • D: 如果不好区分,就全部传引用
      • F: 重命名函数内的参数或者变量名

    修改后的代码

    QString Customer::Statement()
    {
        double TotalAmout = 0;
        int FrequentRenterPoints = 0;
        QString result = "Rental Record for " + GetName() + "\n";
        for (Rental *rental : GetRentals())
        {
            double ThisAmount = 0;
            ThisAmount = AmountFor(rental);
            FrequentRenterPoints++;
            if (Movie::NEW_RELEASE == (rental->GetMovie()->GetPriceCode()) &&
                    rental->GetDaysRented() > 1 )
            {
                FrequentRenterPoints++;
            }
            result += "\t" + rental->GetMovie()->GetTitle() + "\t" + QString("%1").arg(ThisAmount) + "\n";
            TotalAmout += ThisAmount;
        }
    
        result += "Amount owed is " + QString("%1").arg(TotalAmout) + "\n";
        result += "You earned " + QString("%1").arg(FrequentRenterPoints) + " frequent renter points";
        qDebug() << qPrintable(result) ;
        return  result;
    }
    double Customer::AmountFor(Rental *rental)
    {
        double Result = 0;
        switch (rental->GetMovie()->GetPriceCode())
        {
        case Movie::REGULAR:
            Result += 2;
            if (rental->GetDaysRented() > 2)
            {
                Result += (rental->GetDaysRented() - 2)*1.5;
            }
            break;
        case Movie::NEW_RELEASE:
            Result += rental->GetDaysRented() * 3;
            break;
        case Movie::CHILDREN:
            Result += 1.5;
            if (rental->GetDaysRented() > 3)
            {
                Result += (rental->GetDaysRented() - 3)*1.5;
            }
            break;
        }
        return Result;
    }
    

    测试结果:

    • Move Method: 由于AmountFor只用到了rental ,而没有用到customer的任何函数。
      • A: 应该把AmountFor移动到rental类中
      • B:   修改AmountFor的函数名, GetCharge()
      • C:    如果AmountFor使开放给客户的接口,就不应该在customer类中直接调用GetCharge,应该保留AmountFor方法。如果不是,就可以在customer类中直接调用GetCharge。

    修改后的代码

    double Rental::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::CHILDREN:
            Result += 1.5;
            if (GetDaysRented() > 3)
            {
                Result += (GetDaysRented() - 3)*1.5;
            }
            break;
        }
        return Result;
    }
    double Customer::AmountFor(Rental *rental)
    {
        return rental->GetCharge();
    }
    

    测试结果:

     

    • Replace Temp with Query: 去除临时变量
      • ThisAmount是个临时变量,可以直接去除。使用 GetCharge()来代替。
      • 但如果GetCharge()计算很复杂,很耗时间,可以保留ThisAmount这个临时变量
    • Extract Method: 将积分计算抽出来。
      • 积分计算根据影片种类而有不同,可以将其单独成立一个函数
      • 将积分计算函数移动到rental类中

    修改后的代码:

    QString Customer::Statement()
    {
        double TotalAmout = 0;
        int FrequentRenterPoints = 0;
        QString result = "Rental Record for " + GetName() + "\n";
        for (Rental *rental : GetRentals())
        {
            double ThisAmount = 0;
            ThisAmount = AmountFor(rental);
            FrequentRenterPoints += rental->GetFrequentRenterPoints();
            result += "\t" + rental->GetMovie()->GetTitle() + "\t" + QString("%1").arg(ThisAmount) + "\n";
            TotalAmout += ThisAmount;
        }
    
        result += "Amount owed is " + QString("%1").arg(TotalAmout) + "\n";
        result += "You earned " + QString("%1").arg(FrequentRenterPoints) + " frequent renter points";
        qDebug() << qPrintable(result) ;
        return  result;
    }
    
    int Rental::GetFrequentRenterPoints()
    {
        if (Movie::NEW_RELEASE == (GetMovie()->GetPriceCode()) &&
                GetDaysRented() > 1 )
        {
            return 2;
        }
        return 1;
    }
    

    测试结果:

    • Replace Temp with Query: 去除临时变量
      • 用 GetTotalCharge() 替换 TotalAmount
      • 用GetTotalFrequentRenterPoints() 替换FrequentRenterPoints

    修改后的代码:

    QString Customer::Statement()
    {
        QString result = "Rental Record for " + GetName() + "\n";
        for (Rental *rental : GetRentals())
        {
            result += "\t" + rental->GetMovie()->GetTitle() + "\t" + QString("%1").arg(AmountFor(rental)) + "\n";
        }
    
        result += "Amount owed is " + QString("%1").arg(GetTotalCharge()) + "\n";
        result += "You earned " + QString("%1").arg(GetTotalFrequentRenterPoints()) + " frequent renter points";
        qDebug() << qPrintable(result) ;
        return  result;
    }
    double Customer::GetTotalCharge()
    {
        double result = 0;
        for (Rental *rental : GetRentals())
        {
            result += AmountFor(rental);
        }
        return result;
    }
    
    int Customer::GetTotalFrequentRenterPoints()
    {
        int result = 0;
        for (Rental *rental : GetRentals())
        {
            result += rental->GetFrequentRenterPoints();
        }
        return result;
    }
    

    测试结果:

    • Move Method: 由于GetCharge/GetFrequentRenterPoints主要用到Movie类中的函数,将两者的变化可以控制在movie类中,将来变化时改动要小一些。
      • 最好不要在另一个对象的属性基础上运用switch语句。如果不得不使用,也应该在对象自己的数据上使用。
      • 将GetCharge移动到Movie类中。
      • 将GetFrequentRenterPoints移动到Movie类中。

    修改后的代码:

    int Rental::GetFrequentRenterPoints()
    {
        return GetMovie()->GetFrequentRenterPoints(GetDaysRented());
    }
    double Rental::GetCharge()
    {
        return GetMovie()->GetCharge(GetDaysRented());
    }
    
    int Movie::GetFrequentRenterPoints(int daysRented)
    {
        if (NEW_RELEASE == (GetPriceCode()) &&
                daysRented > 1 )
        {
            return 2;
        }
        return 1;
    }
    
    double Movie::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::CHILDREN:
            Result += 1.5;
            if (daysRented > 3)
            {
                Result += (daysRented - 3)*1.5;
            }
            break;
        }
        return Result;
    }

    测试结果:

     

    • 运用多态取代与价格相关的条件逻辑
      • 有多种影片类型,他们的计费方式和积分方式不同。我们可以用继承机制来表现不同的影片类型
      • 用多态来取代switch语句。但是此处不可以。因为一个对象在创建后,其作为子类的特性就不能再修改了。
      • 使用状态模式/策略模式来解决这个问题
      • 先运用 Replace Type Code with State/Stategy,将类型相关的行为搬移至state/Stategy模式中。
        • 使用Self Encapsulate Field,确保任何时候都通过取值函数(getter)和设置函数(setter)来访问类型代码。
      • 然后运用MoveMethod将switch语句移到Price类。
      • 最后运用Replace Conditional with Polymorphism去掉switch 语句



    修改后的代码:

    Refector_1.h
    #ifndef REFECTOR_1_H
    #define REFECTOR_1_H
    #include <QString>
    #include <QVector>
    
    class Price
    {
    public:
        virtual int GetPriceCode() = 0;
        virtual double GetCharge(int daysRented) = 0;
    };
    
    class ChildrenPrice : public Price
    {
    public:
        int GetPriceCode();
        double GetCharge(int daysRented);
    };
    
    class NewReleasePrice : public Price
    {
    public:
        int GetPriceCode();
        double GetCharge(int daysRented);
    };
    
    class RegularPrice : public Price
    {
    public:
        int GetPriceCode();
        double GetCharge(int daysRented);
    };
    
    class ExceptionPrice : public Price
    {
    public:
        ExceptionPrice();
        int GetPriceCode();
        double GetCharge(int daysRented);
    };
    
    
    class Movie
    {
    public:
        Movie(QString title, int priceCode);
        const static int CHILDREN = 2;
        const static int REGULAR = 0;
        const static int NEW_RELEASE = 1;
        int GetPriceCode();
        void SetPriceCode(int priceCode);
        QString GetTitle();
        double GetCharge(int daysRented);
        int  GetFrequentRenterPoints(int daysRented);
    
    private:
        QString m_Title;
        int     m_PriceCode;
        Price *m_pPrice;
    };
    
    
    
    class Rental
    {
    public:
        Rental(Movie *movie, int daysRented);
        int GetDaysRented();
        double GetCharge();
        Movie* GetMovie();
        int GetFrequentRenterPoints();
    
    private:
        Movie* m_Movie;
        int m_DaysRented;
    
    
    };
    
    class Customer
    {
    public:
        Customer(QString name);
        void AddRental(Rental *rental);
        QString GetName();
        QVector<Rental *> GetRentals();
        QString Statement();
        double AmountFor(Rental *rental);
        double GetTotalCharge();
        int GetTotalFrequentRenterPoints();
    private:
        QString m_Name;
        QVector<Rental *> m_Rentals;
    
    };
    
    #endif // REFECTOR_1_H
    
    Refector_1.cpp
    
    
    #include "Refector_1.h"
    #include <QDebug>
    
    
    Movie::Movie(QString title, int priceCode)
    {
        m_Title = title;
        SetPriceCode(priceCode);
    }
    
    int Movie::GetPriceCode()
    {
        return m_pPrice->GetPriceCode();
    }
    
    void Movie::SetPriceCode(int priceCode)
    {
        switch (priceCode) {
        case REGULAR:
            m_pPrice = new RegularPrice();
            break;
        case CHILDREN:
            m_pPrice = new ChildrenPrice();
            break;
        case NEW_RELEASE:
            m_pPrice = new NewReleasePrice();
            break;
        default:
            m_pPrice = new ExceptionPrice();
        }
    }
    
    QString Movie::GetTitle()
    {
        return m_Title;
    }
    
    double Movie::GetCharge(int daysRented)
    {
        return m_pPrice->GetCharge(daysRented);
    }
    
    int Movie::GetFrequentRenterPoints(int daysRented)
    {
        if (NEW_RELEASE == (GetPriceCode()) &&
                daysRented > 1 )
        {
            return 2;
        }
        return 1;
    }
    
    Rental::Rental(Movie *movie, int daysRented)
    {
        m_Movie =movie;
        m_DaysRented = daysRented;
    }
    
    int Rental::GetDaysRented()
    {
        return m_DaysRented;
    }
    
    double Rental::GetCharge()
    {
        return GetMovie()->GetCharge(GetDaysRented());
    }
    
    Movie *Rental::GetMovie()
    {
        return m_Movie;
    }
    
    int Rental::GetFrequentRenterPoints()
    {
        return GetMovie()->GetFrequentRenterPoints(GetDaysRented());
    }
    
    Customer::Customer(QString name)
    {
        m_Name = name;
    }
    
    void Customer::AddRental(Rental *rental)
    {
        m_Rentals.push_back(rental);
    }
    
    QString Customer::GetName()
    {
        return  m_Name;
    }
    
    QVector<Rental *> Customer::GetRentals()
    {
        return  m_Rentals;
    }
    
    QString Customer::Statement()
    {
        QString result = "Rental Record for " + GetName() + "\n";
        for (Rental *rental : GetRentals())
        {
            result += "\t" + rental->GetMovie()->GetTitle() + "\t" + QString("%1").arg(AmountFor(rental)) + "\n";
        }
    
        result += "Amount owed is " + QString("%1").arg(GetTotalCharge()) + "\n";
        result += "You earned " + QString("%1").arg(GetTotalFrequentRenterPoints()) + " frequent renter points";
        qDebug() << qPrintable(result) ;
        return  result;
    }
    
    double Customer::AmountFor(Rental *rental)
    {
        return rental->GetCharge();
    }
    
    double Customer::GetTotalCharge()
    {
        double result = 0;
        for (Rental *rental : GetRentals())
        {
            result += AmountFor(rental);
        }
        return result;
    }
    
    int Customer::GetTotalFrequentRenterPoints()
    {
        int result = 0;
        for (Rental *rental : GetRentals())
        {
            result += rental->GetFrequentRenterPoints();
        }
        return result;
    }
    
    
    int ChildrenPrice::GetPriceCode()
    {
        return Movie::CHILDREN;
    }
    
    double ChildrenPrice::GetCharge(int daysRented)
    {
        double Result = 0;
        Result += 1.5;
        if (daysRented > 3)
        {
            Result += (daysRented - 3)*1.5;
        }
        return Result;
    }
    
    int NewReleasePrice::GetPriceCode()
    {
        return Movie::NEW_RELEASE;
    }
    
    double NewReleasePrice::GetCharge(int daysRented)
    {
        return daysRented * 3;
    }
    
    int RegularPrice::GetPriceCode()
    {
        return Movie::REGULAR;
    }
    
    double RegularPrice::GetCharge(int daysRented)
    {
        double Result = 0;
        Result += 2;
        if (daysRented > 2)
        {
            Result += (daysRented - 2)*1.5;
        }
        return Result;
    }
    
    ExceptionPrice::ExceptionPrice()
    {
        //trigger except handler
        qDebug() << "Except occur";
    }
    
    int ExceptionPrice::GetPriceCode()
    {
        return -1;
    }
    
    double ExceptionPrice::GetCharge(int daysRented)
    {
        return 0;
    }
    
    

    5. 参考文献
    1) 重构:改善既有代码的设计, Martin Fowler ,熊节译, 中国工信出版集团,人民邮电出版社。

    展开全文
  • 操作者告诉程序:顾客租了哪些影片,租期多长,程序便根据租赁时间和影片的类型算出费用。影片分为三类:普通片,儿童片和新片。除了计算费用,还要为常客计算积分,积分会根据租片的种类是否为新片而有所不同。 ...

    以下将以6次重构的操作来实现一个简单的案例。

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


    • 首先提个问题:什么时候重构?
    • 看代码实现上面案例如下:

    Movie.java(影片实体类)

    public class Movie {
    
        public static final int CHILEDRENS = 2;
        public static final int REGULAR = 0;
        public static final int NEW_RELEASE = 1;
    
    
        public Movie(String title, int priceCode) {
            this.title = title;
            this.priceCode = priceCode;
        }
    
        private String title;
        private int 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.java(租赁类)

    public class Rental {
    
        private Movie movie;
        private int daysRented;
    
        public Rental(Movie movie, int daysRented) {
            this.movie = movie;
            this.daysRented = daysRented;
        }
    
        public int getDaysRented() {
            return daysRented;
        }
    
        public Movie getMovie() {
            return movie;
        }
    }
    

    Customer.java(顾客租赁类)

    public class Customer {
    
        private String name;
        private Vector rentals = new Vector();
    
        public void addRental(Rental arg) {
            rentals.addElement(arg);
        }
    
        public String getName() {
            return name;
        }
    
        public String statement() {
            double totalAmount = 0;
            int frequentRenterPoints = 0;
            Enumeration elements = rentals.elements();
            StringBuilder result = new StringBuilder("Rental Record for " + getName() + "\n");
            while (elements.hasMoreElements()) {
                double thisAmount = 0;
                Rental each = (Rental) elements.nextElement();
    
                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.CHILEDRENS:
                        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.append("\t").append(each.getMovie().getTitle()).append("\t").append(String.valueOf(thisAmount)).append("\n");
                totalAmount += thisAmount;
            }
            //add footer lines
            result.append("Amount owed is ").append(String.valueOf(totalAmount)).append("\n");
            result.append("You earned ").append(String.valueOf(frequentRenterPoints)).append(" frequent renter points");
            return result.toString();
        }
    }
    
    以上代码实现完全没有问题,但是能如果遇到以下这两个问题,就会突出臃肿,可扩展性差,难以维护等缺点了。
    • 如果计费标准变化了?
      • 就需要找到每条case中的计费方式,整个switch语句会十分庞大。
    • 如果想输出statement()中某个数据显示到表单或者被其他地方引用?
      • 没有任何方法独立提出来,导致重复引用的计算多处出现。

    如果你发现自己需要为程序添加一个特性,而代码的结构使你无法很方便地达到目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。


    重构(一)

    分解并重组statement()
     public String statement() {
            double totalAmount = 0;
            int frequentRenterPoints = 0;
            Enumeration elements = rentals.elements();
            StringBuilder result = new StringBuilder("Rental Record for " + getName() + "\n");
            while (elements.hasMoreElements()) {
                double thisAmount = 0;
                Rental each = (Rental) elements.nextElement();
    
                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.append("\t").append(each.getMovie().getTitle()).append("\t").append(String.valueOf(thisAmount)).append("\n");
                totalAmount += thisAmount;
            }
            //add footer lines
            result.append("Amount owed is ").append(String.valueOf(totalAmount)).append("\n");
            result.append("You earned ").append(String.valueOf(frequentRenterPoints)).append(" frequent renter points");
            return result.toString();
        }
    
        private int amountFor(Rental each){
            int thisAmount = 0;
            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.CHILEDRENS:
                    thisAmount += 1.5;
                    if (each.getDaysRented() > 3) {
                        thisAmount += (each.getDaysRented() - 3) * 1.5;
                    }
                    break;
            }
            return thisAmount;
        }
    

    每次做完一次这样的修改都要编译并测试。这次测试发现错误了吧。故意把返回的double改成了int类型。
    就是为了告诉我们每次修改都要非常小心,并且编译测试,不但不会浪费时间,反而会节省大量的调试时间。

    重构(二)

    修改函数名称/参数名称
     private double amountFor(Rental rental){
            double result = 0;
            switch (rental.getMovie().getPriceCode()) {
                case Movie.REGULAR:
                    result += 2;
                    if (rental.getDaysRented() > 2) {
                        result += (rental.getDaysRented() - 2) * 1.5;
                    }
                    break;
                case Movie.NEW_RELEASE:
                    result += rental.getDaysRented() * 3;
                    break;
                case Movie.CHILEDRENS:
                    result += 1.5;
                    if (rental.getDaysRented() > 3) {
                        result += (rental.getDaysRented() - 3) * 1.5;
                    }
                    break;
            }
            return result;
        }
    

    任何一个傻瓜都可以写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。

    重构(三)

    搬移函数(移动到它该到的地方去)
    public class Customer {
    
        private String name;
        private Vector rentals = new Vector();
    
        public void addRental(Rental arg) {
            rentals.addElement(arg);
        }
    
        public String getName() {
            return name;
        }
    
        public String statement() {
            double totalAmount = 0;
            int frequentRenterPoints = 0;
            Enumeration elements = rentals.elements();
            StringBuilder result = new StringBuilder("Rental Record for " + getName() + "\n");
            while (elements.hasMoreElements()) {
                double thisAmount = 0;
                Rental each = (Rental) elements.nextElement();
    
                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.append("\t").append(each.getMovie().getTitle()).append("\t").append(String.valueOf(thisAmount)).append("\n");
                totalAmount += thisAmount;
            }
            //add footer lines
            result.append("Amount owed is ").append(String.valueOf(totalAmount)).append("\n");
            result.append("You earned ").append(String.valueOf(frequentRenterPoints)).append(" frequent renter points");
            return result.toString();
        }
    
        private double amountFor(Rental rental){
           return rental.getCharge();
        }
    }
    
    

    Rental.java类

    public class Rental {
    
        private Movie movie;
        private int daysRented;
    
        public Rental(Movie movie, int daysRented) {
            this.movie = movie;
            this.daysRented = daysRented;
        }
    
        public int getDaysRented() {
            return daysRented;
        }
    
        public Movie getMovie() {
            return movie;
        }
    
        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.CHILEDRENS:
                    result += 1.5;
                    if (getDaysRented() > 3) {
                        result += (getDaysRented() - 3) * 1.5;
                    }
                    break;
            }
            return result;
        }
    }
    

    重构(四)

    提炼函数(越细小的函数功能越明确)/去除临时变量(使结构更清晰)
     public String statement() {
    
            Enumeration elements = rentals.elements();
            StringBuilder result = new StringBuilder("Rental Record for " + getName() + "\n");
            while (elements.hasMoreElements()) {
                Rental each = (Rental) elements.nextElement();
    
                //show figures for this rental
                result.append("\t").append(each.getMovie().getTitle()).append("\t").append(String.valueOf(each.getCharge())).append("\n");
            }
            //add footer lines
            result.append("Amount owed is ").append(String.valueOf(getTotalCharge())).append("\n");
            result.append("You earned ").append(String.valueOf(getTotalFrequentRenterPoints())).append(" frequent renter points");
            return result.toString();
        }
    
        private double getTotalCharge(){
            double result = 0;
            Enumeration enumeration = rentals.elements();
            while (enumeration.hasMoreElements()){
                Rental each = (Rental) enumeration.nextElement();
                result += each.getCharge();
            }
            return result;
        }
    
        private int getTotalFrequentRenterPoints(){
            int result = 0;
            Enumeration enumeration = rentals.elements();
            while (enumeration.hasMoreElements()){
                Rental each = (Rental) enumeration.nextElement();
                result += each.getFrequentRenterPoints();
            }
            return result;
        }
    

    Rental.java类

    public class Rental {
    
        private Movie movie;
        private int daysRented;
    
        public Rental(Movie movie, int daysRented) {
            this.movie = movie;
            this.daysRented = daysRented;
        }
    
        public int getDaysRented() {
            return daysRented;
        }
    
        public Movie getMovie() {
            return movie;
        }
    
        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.CHILEDRENS:
                    result += 1.5;
                    if (getDaysRented() > 3) {
                        result += (getDaysRented() - 3) * 1.5;
                    }
                    break;
            }
            return result;
        }
    
        int getFrequentRenterPoints(){
            if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1){
                return 2;
            }else {
                return 1;
            }
        }
    }
    

    重构(五)

    多态来提炼switch()语句
    • 这里不直接抽象Movie类,是因为Movie类有自己的属性和生命周期。所以State模式的应用可以很好的解决这个问题。

    在这里插入图片描述

    Movie.java类

    public class Movie {
    
        public static final int CHILEDRENS = 2;
        public static final int REGULAR = 0;
        public static final int NEW_RELEASE = 1;
    
        public Movie(String title, int priceCode) {
            this.title = title;
            setPriceCode(priceCode);
        }
    
        public void setPriceCode(int arg) {
            switch (arg) {
                case REGULAR:
                    price = new RegularPrice();
                    break;
                case CHILEDRENS:
                    price = new ChildrensPrice();
                    break;
                case NEW_RELEASE:
                    price = new NewReleasePrice();
                    break;
                default:
                    throw new IllegalArgumentException("incorrect price code");
            }
        }
    
        private String title;
        private Price price;
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public Price getPrice() {
            return price;
        }
    
        public void setPrice(Price price) {
            this.price = price;
        }
    
        double getCharge(int daysRented) {
            return price.getCharge(daysRented);
        }
    
        int getFrequentRenterPoints(int daysRented) {
            return price.getFrequentRenterPoints(daysRented);
        }
    }
    
    

    Price.java类

    abstract class Price {
    
        abstract int getPriceCode();
    
        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.CHILEDRENS:
                    result += 1.5;
                    if (daysRented > 3) {
                        result += (daysRented - 3) * 1.5;
                    }
                    break;
            }
            return result;
        }
    
        int getFrequentRenterPoints(int daysRented) {
            if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) {
                return 2;
            } else {
                return 1;
            }
        }
    }
    

    NewReleasePrice.java类

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

    其他两个实现类同NewReleasePrice类,就不列出来了。

    重构(六)

    继承
    abstract class Price {
    
        abstract int getPriceCode();
    
        abstract double getCharge(int daysRented);
    
        int getFrequentRenterPoints(int daysRented) {
            return 1;
        }
    }
    

    NewReleasePrice.java类

    public class NewReleasePrice extends Price {
        @Override
        int getPriceCode() {
            return Movie.NEW_RELEASE;
        }
    
        @Override
        double getCharge(int daysRented) {
            return daysRented * 3;
        }
    
        @Override
        int getFrequentRenterPoints(int daysRented) {
            return (daysRented > 1) ? 2 : 1;
        }
    }
    

    前面列出的技术点仅仅只是一个起点,使你登堂入室之前的大门。如果没有这些技术,你根本无法对运行的程序进行任何设计上的改动。有了这些技术,你仍然做不到,但起码可以开始尝试了。

    展开全文
  • 操作者告诉程序:顾客租了哪些影片,租期多长。程序根据租赁时间和影片类型计算费用。影片分为3类:普通片,儿童片,新片。除了计算费用还要为常客计算积分,积分根据组片种类是否为新片而不同。Movie(movieKind, ...
    影片出租店的程序:计算每位顾客的消费金额并打印详单。
    操作者告诉程序:顾客租了哪些影片,租期多长。程序根据租赁时间和影片类型计算费用。
    影片分为3类:普通片,儿童片,新片。除了计算费用还要为常客计算积分,积分根据组片种类是否为新片而不同。

    Movie(movieKind, name)
    Rent(影片名称,租赁时间)
    RentTotal(Rent列表)

    阶段1:从statement()到htmlStatement()
    使用的重构方法:
    1.extract method(抽取逻辑,可能在多处使用的相同逻辑,只需要保留一份逻辑代码,而在使用时对该逻辑进行调用。
    而不是写重复的逻辑代码。)

    确保逻辑代码唯一,而不是逻辑代码重复。
    即确保逻辑实体唯一,而不是逻辑实体重复。
    确保只有一个该逻辑的实体,而不是多个该逻辑的实体。因为如果某个逻辑产生多个实体,必须维护它们的一致性。
    唯一的逻辑实体 + 逻辑实体的多次调用。
    原因:逻辑代码重复,当需要修改该逻辑代码时,必须确保该逻辑代码的所有实体都是一致性。
    这个一致性维护工作难度根据逻辑实体分散程度增加,逻辑实体的个数增加,出错的概率也不断提升。

    2.move method(函数搬家,函数应该放在它使用的数据的对象内)
    调整修改代码,让其使用新函数,旧函数如果是public则可以保留,如果其他类还引用了旧函数,不需要修改它们。
    3.replace temp with query。(使用函数而不是临时变量)

    阶段2:结合变化量,影片的类型,限制变化的影响范围。本例中将影片类型的变化影响限制到Movie类中。
    如果getCharge()和getRentPoints()方法在Movie类中,我们只需要在Movie类中修改类型,getCharge() getRentPoints()方法。
    如果getCharge()和getRentPoints()方法在Rental类中,当影片类型发生变化,我们需要在Movie类中需改类型,并在Rental类中修改这2个方法。
    尽管修改的东西都是一样的,但是后者在2个类中修改,前者在1个类中修改。显然前者更好。
    所以把Rental中的getCharge()方法和getRentPoints()方法搬到Movie类中
    方式:将Rental中的getCharge()方法和getRentPoints()方法搬到Movie类中

    阶段3:使用多态替换类型代码
    1.replace type code with state/strategy
    1.self encapsulate field
    2.move method
    3.replace conditional with polymorphism

    重构的节奏:测试小修改,测试小修改。。。

    重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
    重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
    重构是对软件的小修改,但重构之中还可以包含另一个重构。

    重构的目的:在不改变软件可观察行为的前提下,调整内部结构,使其更容易被理解和修改。
    性能优化:在不改变软件可观察行为的前提下,调整内部结构,提高性能,但往往代码会变得更难理解。

    使用重构技术开发软件:
    时间分配给2种截然不同的行为:添加新功能,以及重构。
    添加新功能时不应修改代码,只管添加功能。
    重构时不应添加新功能,只管调整结构。
    经常变换帽子,但无论何时都应该清楚自己戴的是哪顶帽子。

    重构把我带到更高的理解层次。

    重构使程序拥有良好的设计,而良好的设计使快速开发的根本。
    如果没有良好的设计,某段时间你可能进展迅速,但恶劣的设计很快就让你的速度慢下来。你会花时间在调试上面,修改的时间
    越来越长,因为你必须花更多的时间取理解系统、寻找重复的代码,随着你给程序打上一个有一个补丁,新特性需要更多的代码来实现。
    真是个恶性循环。

    何时重构?
    重构本来就不是一件应该特别拨出时间做的事情,随时随地都可以。
    如果你想做某件事情,而重构可以帮你把它做得更好,就用重构。
    如果你想理解代码,而重构可以帮助你理解,那就重构。
    如果你想添加新功能,重构可以帮助你理解代码,并更容易的添加新功能。
     1 package shop;
     2 
     3 public class Movie {
     4     public static final int NEW_RELEASE = 0;
     5     public static final int REGULAR = 1;
     6     public static final int CHILDREN = 2;
     7 
     8 //    private int priceCode;
     9     private String title;
    10     private Price price;
    11 
    12     public Movie() {
    13     }
    14 
    15     public Movie(String title, int priceCode) {
    16         setPriceCode(priceCode);
    17         this.title = title;
    18     }
    19 
    20     public int getPriceCode() {
    21         return price.getPriceCode();
    22     }
    23 
    24     public void setPriceCode(int priceCode) {
    25         switch (priceCode){
    26             case Movie.NEW_RELEASE:
    27                 price = new NewPrice();
    28                 break;
    29             case Movie.REGULAR:
    30                 price = new RegularPrice();
    31                 break;
    32             case Movie.CHILDREN:
    33                 price = new ChildrenPrice();
    34                 break;
    35             default:
    36                 throw new IllegalArgumentException("非法的影片类型:" + priceCode);
    37         }
    38     }
    39 
    40     public String getTitle() {
    41         return title;
    42     }
    43 
    44     public void setTitle(String title) {
    45         this.title = title;
    46     }
    47 
    48     public double getCharge(int daysRent){
    49         return price.getCharge(daysRent);
    50 //        double result = 0;
    51 //        switch(getPriceCode()){
    52 //            case Movie.REGULAR:
    53 //                result += 2;
    54 //                if(daysRent > 2){
    55 //                    result += (daysRent - 2) * 1.5;
    56 //                }
    57 //                break;
    58 //            case Movie.NEW_RELEASE:
    59 //                result += daysRent * 3;
    60 //                break;
    61 //            case Movie.CHILDREN:
    62 //                result += 1.5;
    63 //                if(daysRent > 3){
    64 //                    result += (daysRent - 3) * 1.5;
    65 //                }
    66 //                break;
    67 //        }
    68 //        return result;
    69     }
    70 
    71     public int getRentPoints(int daysRent){
    72         return price.getRentPoints(daysRent);
    73 //        int rentPoints = 1;
    74 //        if(getPriceCode() == Movie.NEW_RELEASE
    75 //                && daysRent > 1){
    76 //            rentPoints++;
    77 //        }
    78 //        return rentPoints;
    79     }
    80 }
    Movie
     1 package shop;
     2 
     3 public class Rental {
     4     private Movie movie;
     5     private int daysRent;
     6 
     7     public Rental(){}
     8 
     9     public Rental(Movie movie, int daysRent){
    10         this.movie = movie;
    11         this.daysRent = daysRent;
    12     }
    13 
    14     public Movie getMovie() {
    15         return movie;
    16     }
    17 
    18     public void setMovie(Movie movie) {
    19         this.movie = movie;
    20     }
    21 
    22     public int getDaysRent() {
    23         return daysRent;
    24     }
    25 
    26     public void setDaysRent(int daysRent) {
    27         this.daysRent = daysRent;
    28     }
    29 
    30     public int getRentPoints(){
    31         return getMovie().getRentPoints(getDaysRent());
    32 //        int rentPoints = 1;
    33 //        if(getMovie().getPriceCode() == Movie.NEW_RELEASE
    34 //                && getDaysRent() > 1){
    35 //            rentPoints++;
    36 //        }
    37 //        return rentPoints;
    38     }
    39 
    40     public double getCharge(){
    41         return getMovie().getCharge(getDaysRent());
    42 //        double result = 0;
    43 //        switch(this.getMovie().getPriceCode()){
    44 //            case Movie.REGULAR:
    45 //                result += 2;
    46 //                if(this.getDaysRent() > 2){
    47 //                    result += (this.getDaysRent() - 2) * 1.5;
    48 //                }
    49 //                break;
    50 //            case Movie.NEW_RELEASE:
    51 //                result += this.getDaysRent() * 3;
    52 //                break;
    53 //            case Movie.CHILDREN:
    54 //                result += 1.5;
    55 //                if(this.getDaysRent() > 3){
    56 //                    result += (this.getDaysRent() - 3) * 1.5;
    57 //                }
    58 //                break;
    59 //        }
    60 //        return result;
    61     }
    62 }
    Rental
     1 package shop;
     2 
     3 import java.util.ArrayList;
     4 
     5 public class CustomerOrder01 {
     6     private String name;
     7     private ArrayList<Rental> rentals = new ArrayList<>();
     8 
     9     public CustomerOrder01(String name){
    10         this.name = name;
    11     }
    12 
    13     public void addRental(Rental rental){
    14         rentals.add(rental);
    15     }
    16 
    17     public String statement(){
    18         String result ="Rental Record for " + getName() + "\n";
    19         for (Rental rental : rentals) {
    20 //            double thisAmount = 0;
    21 //            switch(rental.getMovie().getPriceCode()){
    22 //                case Movie.REGULAR:
    23 //                    thisAmount += 2;
    24 //                    if(rental.getDaysRent() > 2){
    25 //                        thisAmount += (rental.getDaysRent() - 2) * 1.5;
    26 //                    }
    27 //                    break;
    28 //                case Movie.NEW_RELEASE:
    29 //                    thisAmount += rental.getDaysRent() * 3;
    30 //                case Movie.CHILDREN:
    31 //                    thisAmount += 1.5;
    32 //                    if(rental.getDaysRent() > 3){
    33 //                        thisAmount += (rental.getDaysRent() - 3) * 1.5;
    34 //                    }
    35 //                    break;
    36 //            }
    37 //            thisAmount = amountFor(rental);
    38 //            thisAmount = rental.getCharge();
    39 //            rentPoints++;
    40 //            if(rental.getMovie().getPriceCode() == Movie.NEW_RELEASE
    41 //                && rental.getDaysRent() > 1){
    42 //                rentPoints++;
    43 //            }
    44             result += "\t" + rental.getMovie().getTitle() + "\t" + String.valueOf(rental.getCharge()) + "\n";
    45         }
    46         result += "Amount owed is " + String.valueOf(getTotalAmount()) + "\n";
    47         result += "You earned " + String.valueOf(getTotalRentPoints()) + " frequent renter points";
    48         return result;
    49     }
    50 
    51     public double amountFor(Rental rental){
    52 //        double result = 0;
    53 //        switch(rental.getMovie().getPriceCode()){
    54 //            case Movie.REGULAR:
    55 //                result += 2;
    56 //                if(rental.getDaysRent() > 2){
    57 //                    result += (rental.getDaysRent() - 2) * 1.5;
    58 //                }
    59 //                break;
    60 //            case Movie.NEW_RELEASE:
    61 //                result += rental.getDaysRent() * 3;
    62 //                break;
    63 //            case Movie.CHILDREN:
    64 //                result += 1.5;
    65 //                if(rental.getDaysRent() > 3){
    66 //                    result += (rental.getDaysRent() - 3) * 1.5;
    67 //                }
    68 //                break;
    69 //        }
    70 //        return result;
    71         return rental.getCharge();
    72     }
    73 
    74     public double getTotalAmount(){
    75         double totalAmount = 0;
    76         for (Rental rental : rentals) {
    77             totalAmount += rental.getCharge();
    78         }
    79         return totalAmount;
    80     }
    81 
    82     public int getTotalRentPoints(){
    83         int totalRentPoints = 0;
    84         for (Rental rental : rentals) {
    85             totalRentPoints += rental.getRentPoints();
    86         }
    87         return totalRentPoints;
    88     }
    89     public String getName(){
    90         return name;
    91     }
    92 }
    CustomerOrder
     1 package shop;
     2 
     3 public class CustomerTest01 {
     4     public static void main(String[] args) {
     5         Movie movie1 = new Movie("阿凡达", Movie.NEW_RELEASE);
     6         Movie movie2 = new Movie("僵尸世界大战", Movie.REGULAR);
     7         Movie movie3 = new Movie("熔炉", Movie.CHILDREN);
     8         Movie movie4 = new Movie("星际穿越", Movie.REGULAR);
     9 
    10         Rental rental1 = new Rental(movie1,3);
    11         Rental rental2 = new Rental(movie2,5);
    12         Rental rental3 = new Rental(movie3,5);
    13 
    14         CustomerOrder01 customerOrder = new CustomerOrder01("liubei");
    15         customerOrder.addRental(rental1);
    16         customerOrder.addRental(rental2);
    17         customerOrder.addRental(rental3);
    18 
    19         System.out.println(customerOrder.statement());
    20     }
    21 }
    CustomerTest
     1 package shop;
     2 
     3 public abstract class Price {
     4     public abstract int getPriceCode();
     5 
     6     public abstract double getCharge(int daysRent);
     7 
     8     public int getRentPoints(int daysRent){
     9         return 1;
    10 //        int rentPoints = 1;
    11 //        if(getPriceCode() == Movie.NEW_RELEASE
    12 //                && daysRent > 1){
    13 //            rentPoints++;
    14 //        }
    15 //        return rentPoints;
    16     }
    17 }
    Price
     1 package shop;
     2 
     3 public class NewPrice extends Price{
     4     @Override
     5     public int getPriceCode() {
     6         return Movie.NEW_RELEASE;
     7     }
     8 
     9     @Override
    10     public double getCharge(int daysRent) {
    11         return daysRent * 3;
    12     }
    13 
    14     @Override
    15     public int getRentPoints(int daysRent) {
    16         return daysRent > 1 ? 2 : 1;
    17     }
    18 }
    NewPrice
     1 package shop;
     2 
     3 public class ChildrenPrice extends Price {
     4     @Override
     5     public int getPriceCode(){
     6         return Movie.CHILDREN;
     7     }
     8 
     9     @Override
    10     public double getCharge(int daysRent) {
    11         double result = 1.5;
    12         if(daysRent > 3){
    13             result += (daysRent - 3) * 1.5;
    14         }
    15         return result;
    16     }
    17 }
    ChildrenPrice
     1 package shop;
     2 
     3 public class RegularPrice extends Price {
     4     @Override
     5     public int getPriceCode() {
     6         return Movie.REGULAR;
     7     }
     8 
     9     @Override
    10     public double getCharge(int daysRent) {
    11         double result = 2;
    12         if(daysRent > 2){
    13             result += (daysRent - 2) * 1.5;
    14         }
    15         return result;
    16     }
    17 }
    RegularPrice

     

    转载于:https://www.cnblogs.com/mozq/p/10804886.html

    展开全文
  • 在Java中锁的种类主要这些: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 可中断锁 上面是很多锁的名词,这些分类并不是全是指锁的...
  • 1. 在设计原则上,STL和Boost大体统一因为STL和Boost基本上都是标准委员会那批人在策划、审核和维护,所以口味上是相对接近的。但是因为Boost并不在标准中,或者说是下一代标准的试验场,所以库的种类要更多一些,...
  • Code Compl 代码大全

    2011-05-26 12:09:22
     哪些类包含最多的错误?  错误的分类  不完善的构造过程引发错误所占的比例  你期望能发现多少错误  测试本身的错误  22.5 测试支持工具  为测试各个类构造脚手架  Diff工具  测试数据生成器  覆盖率监视...
  • 微软资深软件设计工程师Eric Lippert...最近采访了Coding.net全栈工程师杜万,听他讲讲Coding即将发布的在线阅读代码工具CodeInsight有哪些功能。\\InfoQ:请介绍一下自己,包括之前的工作经历以及现在所从事的主要...
  • 设计模式入门

    2017-06-12 23:40:11
    设计原则:找出应用中可能需要变化之处,把他们独立出来,不要和哪些不需要变化的代码混在一起。尽量使用面向接口编程而不是针对具体实现类编程。在鸭子类的设计过程中,因为鸭子种类的变化,fly()和quack()会不同...
  • 设计模式之-单例模式

    2020-05-21 01:44:00
    单例模式应该是我们日常工作中用的或者见过最多的设计模式之一了,尤其是在设计各种框架的管理器时,基本都会针对不同种类的管理单元设计一个单例供业务代码使用。那么单例模式多少种写法呢?每种方法适合用在什么...
  • 生活中的场景有哪些? 支付时的银行种类; 购物车支付提供商招商银行建设银行XX银行 举例实现 场景:设计一款包含所有鸭子的产品,而且鸭子的种类与日俱增。 代码 /** * 超类,所有鸭子都要继承此类 * ...
  • 设计模式之工厂模式

    2018-07-05 10:04:37
    注解:设计模式只是思想,思想,思想,没有代码,工厂模式概念:实例化对象,用工厂方法代替new操作。工厂模式的思想:定义一个接口来创建对象,但是让子类决定哪些类需要被实例化,工厂方法把实例化的工作推迟到...
  • 2.1.2变量有哪些类型 2.2如何使用变量 2.2.1如何使用整型变量 2.2.2如何使用浮点型变量 2.2.3如何使用字符型变量 2.2.4如何使用布尔型变量 2.2.5基本数据类型之间的类型转换 2.2.6基本数据类型和字符串之间的...
  • iPhone开发秘籍(第2版)--源代码

    热门讨论 2012-12-11 13:51:22
    CruiseYoung提供的带详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 该资料是《iPhone开发秘籍:第2版》的源代码 对应的书籍资料见: iPhone开发秘籍:第2版(iphone开发必备佳作,在...
  • 设计模式学习总结(5) 原型模式

    千次阅读 2014-03-25 10:35:47
    本系列主要记录设计模式...这个模式的优缺点是什么,其有哪些使用场景,在使用时要注意什么。 尊重作者,转载请注明晔阳的Bloghttp://blog.csdn.net/ 5.原型模式 意图:用原型实例指定创建对象的种类,并且通过
  • 16.1.4 string还提供了哪些功能 16.1.5 字符串种类 16.2 智能指针模板类 16.2.1 使用智能指针 16.2.2 有关智能指针的注意事项 16.2.3 unique_ptr为何优于auto_ptr 16.2.4 选择智能指针 16.3 标准模板库 ...
  • 5.3 问题的种类 117 5.4 关于问题的问题 119 5.5 关于数据的问题 121 5.6 建立逻辑表达式 126 5.7 小结 136 第6章 SQL执行计划 137 6.1 解释计划 137 6.1.1 使用解释计划 137 6.1.2 理解解释计划可能达不到...
  • JSP设计-第14章(1)

    2007-09-27 11:58:00
    java.util 软件包的核心主要种类:Locale 和 ResourceBundle。Locale 对象只是用来确定地区设置的:它们结合了 ISO 639 语言代码(例如,ja 代表日文)和 ISO 3166 国家代码(例如,IT 代表
  • 数据库课程设计(基于B/S)

    热门讨论 2008-12-28 15:28:06
    本系统在设计过程中,为了克服这些困难,需要使程序代码标准化,软件统一化,确保软件的可维护性和实用性;删除不必要的管理冗余,实现管理规范化、科学化;界面友好、简单化,做到实用、方便,尽量满足报刊订阅中员工的...
  • 6.软件测试都那些种类? 黑盒:针对系统功能的测试白合:测试函数功能,各函数接口 7.确定模块的功能和模块的接口是在软件设计的那个队段完成的? 概要设计阶段 8.enum string { x1, x2, x3=10, x4, x5, }x; 问x= 0x...
  • WPF--XMAL概述性知识

    2020-07-16 15:07:34
    这还不算有些种类的应用程序需要在源代码中进行相应的配置(如需要哪些文件和文件夹、代码的基本格式是什么样)。 如果每次写程序都让程序员手动配置这些参数和初始设置,那开销就太大了。 所以,当你选择了哪个模板...
  • Java 中异常分为哪些种类: 按照异常需要处理的时机分为编译时异常(也叫强制性异常)也叫 CheckedException 和运行时异常 (也叫非强制性异常)也叫 RuntimeException。如果程序没有处理 Checked 异常,该程序在编译...
  • 1.java中异常分为哪些种类 1)按照异常需要处理的时机分为编译时异常(CheckedException)和运行时异常(RuntimeException)。 只有Java语言提供了Checked异常,Java认为Checked异常都是可以被处理的异常,所以...

空空如也

空空如也

1 2 3 4 5
收藏数 100
精华内容 40
关键字:

代码设计有哪些种类