精华内容
下载资源
问答
  • 代码设计说明书 填表日期:6月11日 编码对象名称 读者证编号 代码种类 层次码 代码位数 11 代码结构 效验位 无 编码对象名称 图书信息 代码种类 层次码 代码位数 8 代码结构 效验位 无 编码对象名称 借阅信息 代码...
  • 由于[GOF95]是论述软件模式著作第一本,也是OO设计理论著作中最流行一本,因此有些人常常使用设计模式(Design Pattern)一词来指所有直接处理软件架构、设计、程序实现任何种类的模式。另外一些人则强调...

        由于[GOF95]是论述软件模式的著作的第一本,也是OO设计理论著作中最流行的一本,因此有些人常常使用设计模式(Design Pattern)一词来指所有直接处理软件的架构、设计、程序实现的任何种类的模式。另外一些人则强调要划分三种不同层次的模式:架构模式(Architectural Pattern)、设计模式(Design Pattern)、成例(Idiom)。成例有时称为代码模式(Coding Pattern)。
       
     这三者之间的区别在于三种不同的模式存在于它们各自的抽象层次和具体层次上。

        架构模式是一个系统的高层次策略,涉及到大尺度的组件以及整体性质和力学。架构模式的好坏可以影响到总体布局和框架性结构。

        设计模式是中等尺度的结构策略。这些中等尺度的结构实现了一些大尺度组件的行为和它们之间的关系。模式的好坏不会影响到系统的总体布局和总体框架。设计模式定义出子系统或组件的微观结构。

        代码模式(或成例)是特定的范例和与特定语言有关的编程技巧。代码模式的好坏会影响到一个中等尺度组件的内部、外部的结构或行为的底层细节,但不会影响到一个部件或子系统的中等尺度的结构,更不会影响到系统的总体布局和大尺度框架。
      1、代码模式或成例(Coding Pattern 或 Idiom)
      代码模式(或成例)是较低层次的模式,并与编程语言密切相关。代码模式描述怎样利用一个特定的编程语言的特点来实现一个组件的某些特定的方面或关系。较为著名的代码模式的例子包括双检锁(Double-Check Locking)模式等。
      2、设计模式(Design Pattern)
      一个设计模式提供一种提炼子系统或软件系统中的组件的,或者它们之间的关系的纲要设计。设计模式描述普遍存在的在相互通讯的组件中重复出现的结构,这种结构解决在一定的背景中的具有一般性的设计问题。设计模式常常划分成不同的种类,常见的种类有:
      创建型设计模式,如工厂方法(Factory Method)模式、抽象工厂(Abstract Factory)模式、原型(Prototype)模式、单例(Singleton)模式,建造(Builder)模式等
      结构型设计模式,如合成(Composite)模式、装饰(Decorator)模式、代理(Proxy)模式、享元(Flyweight)模式、门面(Facade)模式、桥梁(Bridge)模式等
      行为型模式,如模版方法(Template Method)模式、观察者(Observer)模式、迭代子(Iterator)模式、责任链(Chain of Responsibility)模式、备忘录(Memento)模式、命令(Command)模式、状态(State)模式、访问者(Visitor)模式等等。
    以上是三种经典类型,实际上还有很多其他的类型,比如Fundamental型、Partition型,Relation型等等
      设计模式在特定的编程语言中实现的时候,常常会用到代码模式。比如单例(Singleton)模式的实现常常涉及到双检锁(Double-Check Locking)模式等。
      3、架构模式(Architectural Pattern)
      一个架构模式描述软件系统里的基本的结构组织或纲要。架构模式提供一些事先定义好的子系统,指定它们的责任,并给出把它们组织在一起的法则和指南。有些作者把这种架构模式叫做系统模式。
      一个架构模式常常可以分解成很多个设计模式的联合使用。显然,MVC模式就是属于这一种模式。MVC模式常常包括调停者(Mediator)模式、策略(Strategy)模式、合成(Composite)模式、观察者(Observer)模式等。、
      此外,常见的架构模式还有:
      ·Layers(
    分层)模式,有时也称Tiers模式
      ·Blackboard(黑板)模式
      ·Broker(
    中介)模式
      ·Distributed Process(分散过程)模式
      ·Microkernel(微核)模式
      架构模式常常划分成如下的几种:
      ·From Mud to Structure型。帮助架构师将系统合理划分,避免形成一个对象的
    海洋(A sea of objects)。包括Layers(分层)模式、Blackboard(黑板)模式、Pipes/Filters(管道/过滤器)模式等。
      ·分散系统(Distributed Systems)型。为分散式系统提供完整的架构设计,包括像Broker(中介)模式等。
      ·人机互动(Interactive Systems)型,支持包含有人机互动介面的系统的架构设计,例子包括MVC(Model-
    View-Controller)模式、PAC(Presentation-Abstraction-Control)模式等。
      ·Adaptable Systems型,支持
    应用系统适应技术的变化、软件功能需求的变化。如Reflection(反射)模式、Microkernel(微核)模式等。

    (作者:阎宏)

    展开全文
  • 本次设计用类模板求三角形的种类和面积。以实现构造函数、复制构造函数、析构函数的调用顺序以及两点距离的求解,辨别三角形的种类并求出三角形的面积。程序中使用了类模板,用于定义类及成员函数的类型,以便提高...
  • 设计模式的种类 设计模式有23种,分为三类: 创建型模式: 单例模式、抽象工厂模式 原型模式、建造者模式、工厂模式 结构型模式 适配器模式、桥接模式、装饰模式、组合模式 外观模式、享元模式、代理模式 ...

    设计模式的种类

    设计模式有23种,分为三类:

    • 创建型模式:
      • 单例模式、抽象工厂模式
      • 原型模式、建造者模式、工厂模式
    • 结构型模式
      • 适配器模式、桥接模式、装饰模式、组合模式
      • 外观模式、享元模式、代理模式
    • 行为型模式
      • 模板方法模式、命令模式、访问者模式
      • 迭代器模式、观察者模式、中介者模式
      • 备忘录模式、解释器模式、状态模式
      • 策略模式、责任链模式

    设计模式七大原则

    设计模式的核心思想

    • 找出代码中的可变化之处,并独立出来,不要和固定代码混在一起
    • 针对接口编程, 不要针对实现编程
    • 为了交互对象之间的松耦合设计而努力

    耦合度比较

    • 泛化(继承)==实现>组合>聚合>关联>依赖

    1. 单一职责

    一个类A负责多个职责时,要将A的粒度分解成对应职责的类,对应单个职责,在后期对某个职责进行更改时,不会对其他职责造成影响。降低类的复杂度

    2. 接口隔离

    一个类不应该依赖不需要的接口,即一个类对另一个类的以来应该建立在最小接口上,当要以来的接口中有不需要的部分,就应该将大的接口拆分成小接口,保证依赖的接口都是需要的

    3. 依赖倒置

    抽象不要依赖细节,细节要依赖抽象

    低层模块最好是依赖抽象,抽象中不要有具体操作,具体操作交给细节

    继承要遵守里氏替换原则

    抽象–》接口或者抽象类

    细节–》实现类

    • 依赖关系的三种传递方式

      • 接口传递

        interface IOpenAndClose{
            public void open(ITV tv);
        }
        interface ITV{
            public void play();
        }
        //实现接口
        class OpenAndClose implements IOpenAndClose{
            public void open(ITV tv){
                tv.play();
            }
        }
        
      • 构造方法传递

        interface IOpenAndClose{
            public void open();
        }
        interface ITV{
            public void play();
        }
        //实现接口
        class OpenAndClose implements IOpenAndClose{
            private ITV tv;
            public OpenAndClose(ITV tv){  //使用构造器
                this.tv = tv;
            }
            
            public void open(){
                this.tv.play();
            }
        }
        
      • setter传递

        interface IOpenAndClose{
            public void open(ITV tv);
            public void setTv(ITV tv);
        }
        interface ITV{
            public void play();
        }
        //实现接口
        class OpenAndClose implements IOpenAndClose{
            private ITV tv;
            public void setTv(ITV tv){  //调用setter方法
                this.tv = tv;
            }
            
            public void open(){
                this.tv.play();
            }
        }
        

    4.里氏替换原则

    正确使用继承:所有引用基类的方法必须能透明的使用其子类的对象。子类中不要轻易重写父类的方法,可以通过聚合,组合,依赖的方法来解决问题,让子类和父类不再继承,而是共同继承一个更加基础的base类,关于父类中子类需要的方法,可以通过依赖或者继承来获取对象并调用方法,可以降低耦合

    普通的继承

    遵守里氏替换的改进

    5.开闭原则

    提供方(被依赖)–》开放扩展

    使用方(依赖)–》修改关闭

    如图:paint为使用方,三个类型类是提供方,当我们要添加新的绘画类型时,不仅要创建新的类,还要在paint中添加新的方法,这样自违背了开闭原则,代码扩展性也很低

    以下优化遵守了开闭原则,将具体的各个类型的draw方法实现在各自的实现类中,paint依赖抽象,只需要调用Shape对象的draw方法,对应调用各自实现类的draw方法,这样就达到目的,使用方无需更改。

    6. 迪米特法则(直接朋友法则/最少知道原则)

    直接朋友:B是A的成员变量,B是A中方法的入参,B是A中方法的返回值类型

    以上三种情况称为B是A的直接朋友,其它的为陌生类

    迪米特法则:陌生类最好不要以局部变量的形式出现在类的内部;以降低类与类之间的耦合

    对于被依赖的类不管有多复杂,都封装在类的内部,对外只提供public方法,不要泄露任何信息;

    7.合成复合原则

    尽量使用聚合和合成,不要使用继承

    以下为依赖,组合,聚合的类图

    展开全文
  • 5.6.1代码的种类;1.有序码顺序码;代码的表示形式编码方式;1数字顺序编码;2数字分组顺序编码;3字符编码;4组合编码;2.区间码;1区间码之层次码;2区间码之十进制码;3区间码之特征码;3.助记码;4. 缩略码;5.校验码及其生成...
  • 【《重构 改善既有代码的设计》学习笔记】重构:第一个案例 本篇文章内容来自《重构 改善既有代码的设计》一书学习笔记整理笔记并且加上自己浅显思考总结! 一、简单例子 一个影片出租店用程序,...

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

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

    一、简单的例子

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

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

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

    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)

    展开全文
  • 由于[GOF95]是论述软件模式著作第一本,也是OO设计理论著作中最流行一本,因此有些人常常使用设计模式(Design Pattern)一词来指所有直接处理软件架构、设计、程序实现任何种类的模式。另外一些人则强调...
    由于[GOF95]是论述软件模式的著作的第一本,也是OO设计理论著作中最流行的一本,因此有些人常常使用设计模式(Design Pattern)一词来指所有直接处理软件的架构、设计、程序实现的任何种类的模式。另外一些人则强调要划分三种不同层次的模式:架构模式(Architectural Pattern)、设计模式(Design Pattern)、成例(Idiom)。成例有时称为代码模式(Coding Pattern)。 
    

     这三者之间的区别在于三种不同的模式存在于它们各自的抽象层次和具体层次上。架构模式是一个系统的高层次策略,涉及到大尺度的组件以及整体性质和力学。架构模式的好坏可以影响到总体布局和框架性结构。设计模式是中等尺度的结构策略。这些中等尺度的结构实现了一些大尺度组件的行为和它们之间的关系。模式的好坏不会影响到系统的总体布局和总体框架。设计模式定义出子系统或组件的微观结构。代码模式(或成例)是特定的范例和与特定语言有关的编程技巧。代码模式的好坏会影响到一个中等尺度组件的内部、外部的结构或行为的底层细节,但不会影响到一个部件或子系统的中等尺度的结构,更不会影响到系统的总体布局和大尺度框架。

     代码模式或成例(Coding Pattern 或 Idiom)

     代码模式(或成例)是较低层次的模式,并与编程语言密切相关。代码模式描述怎样利用一个特定的编程语言的特点来实现一个组件的某些特定的方面或关系。

     较为著名的代码模式的例子包括双检锁(Double-Check Locking)模式等。

     设计模式(Design Pattern)

     一个设计模式提供一种提炼子系统或软件系统中的组件的,或者它们之间的关系的纲要设计。设计模式描述普遍存在的在相互通讯的组件中重复出现的结构,这种结构解决在一定的背景中的具有一般性的设计问题。

     设计模式常常划分成不同的种类,常见的种类有:

     创建型设计模式,如工厂方法(Factory Method)模式、抽象工厂(Abstract Factory)模式、原型(Prototype)模式、单例(Singleton)模式,建造(Builder)模式等

     结构型设计模式,如合成(Composite)模式、装饰(Decorator)模式、代理(Proxy)模式、享元(Flyweight)模式、门面(Facade)模式、桥梁(Bridge)模式等

     行为型模式,如模版方法(Template Method)模式、观察者(Observer)模式、迭代子(Iterator)模式、责任链(Chain of Responsibility)模式、备忘录(Memento)模式、命令(Command)模式、状态(State)模式、访问者(Visitor)模式等等。
    以上是三种经典类型,实际上还有很多其他的类型,比如Fundamental型、Partition型,Relation型等等

     设计模式在特定的编程语言中实现的时候,常常会用到代码模式。比如单例(Singleton)模式的实现常常涉及到双检锁(Double-Check Locking)模式等。

     架构模式(Architectural Pattern)

     一个架构模式描述软件系统里的基本的结构组织或纲要。架构模式提供一些事先定义好的子系统,指定它们的责任,并给出把它们组织在一起的法则和指南。有些作者把这种架构模式叫做系统模式[STELTING02]。

     一个架构模式常常可以分解成很多个设计模式的联合使用。显然,MVC模式就是属于这一种模式。MVC模式常常包括调停者(Mediator)模式、策略(Strategy)模式、合成(Composite)模式、观察者(Observer)模式等。

     此外,常见的架构模式还有:

     ·Layers(分层)模式,有时也称Tiers模式

     ·Blackboard(黑板)模式

     ·Broker(中介)模式

     ·Distributed Process(分散过程)模式

     ·Microkernel(微核)模式

     架构模式常常划分成如下的几种:

     一、 From Mud to Structure型。帮助架构师将系统合理划分,避免形成一个对象的海洋(A sea of objects)。包括Layers(分层)模式、Blackboard(黑板)模式、Pipes/Filters(管道/过滤器)模式等。

     二、分散系统(Distributed Systems)型。为分散式系统提供完整的架构设计,包括像Broker(中介)模式等。

     三、人机互动(Interactive Systems)型,支持包含有人机互动介面的系统的架构设计,例子包括MVC(Model-View-Controller)模式、PAC(Presentation-Abstraction-Control)模式等。

     四、Adaptable Systems型,支持应用系统适应技术的变化、软件功能需求的变化。如Reflection(反射)模式、Microkernel(微核)模式等。

    本文转自: http://www.uml.org.cn/sjms/200605153.htm
    展开全文
  • 一个编译程序重要性体现在它使得多数计算机用户不必考虑与机器有关繁索细节,使程序员和程序设计专家独立于机器,这对于当今机器数量和种类持续不断地增长年代尤为重要。编译过程划分了词法分析、语法分析、...
  • php中客户端交互代码的结构设计

    千次阅读 2011-11-30 15:08:48
    讨论范围:php服务器端较为靠前的代码,就是直接和页面打交道这部分,数据库业务逻辑层,以及一些自定义处理模块不包含在其中。...问题:客户端ajax应用种类很多,各种请求,如果把它和别php代码放在一起,就不那
  • 测试结果: 1. 作者首先构建了一个简单示例来说明重构重要性。...除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而有不同。 2. 详细代码实现如下 Refector_1.h #ifndef REFECT...
  • 以下将以6次重构的操作来实现一个简单的案例。...除了计算费用,还要为常客计算积分,积分会根据租片的种类是否为新片而有所不同。 首先提个问题:什么时候重构? 看代码实现上面案例如下: Movie.java(影...
  • 当前数控系统种类繁多,各大数控系统厂商数控代码指令互不兼容。在开放式数控系统及仿真软件中越来越多地需要支持多家厂商数控代码体系,以满足终端用户需求。基于Lex 和 Yacc 对一个数控系统中多数控代码解释器...
  • 由于[GOF95]是论述软件模式著作第一本,也是OO设计理论著作中最流行一本,因此有些人常常使用设计模式(Design Pattern)一词来指所有直接处理软件架构、设计、程序实现任何种类的模式。另外一些人则强调...
  • 不断地将对象添加装饰的设计模式叫做 装饰者模式(Decorator)注意:装饰 边框与被装饰物一致性PHP代码实现1571209934450.jpginterface Decorator{public function display();}/*** 核心类*/class XiaoFang ...
  • 更多精彩内容,请微信搜索“FPGAer俱乐部”关注我们。... (2)、FPGA特别适合于同步电路的设计,尽可能减少使用始终信号的种类;  (3)、减少时钟摆率的一种有效方法是使用一个时钟信号生成多个时
  • 希望对做数据库课程设计的同学有所帮助!~~还有就是这些课程设计比较简单(3~10几M/个)——如果做毕业设计请绕行... 每个项目中包含:源代码、文档、使用说明... 资源分我觉得还算适合(*^__^*) 嘻嘻…… 数据库课程...
  • 希望对做数据库课程设计的同学有所帮助!~~还有就是这些课程设计比较简单(3~10几M/个)——如果做毕业设计请绕行... 每个项目中包含:源代码、文档、使用说明... 资源分我觉得还算适合(*^__^*) 嘻嘻…… 数据库课程...
  • 希望对做数据库课程设计的同学有所帮助!~~还有就是这些课程设计比较简单(3~10几M/个)——如果做毕业设计请绕行... 每个项目中包含:源代码、文档、使用说明... 资源分我觉得还算适合(*^__^*) 嘻嘻…… 数据库课程...
  • 希望对做数据库课程设计的同学有所帮助!~~还有就是这些课程设计比较简单(3~10几M/个)——如果做毕业设计请绕行... 每个项目中包含:源代码、文档、使用说明... 资源分我觉得还算适合(*^__^*) 嘻嘻…… 数据库课程...
  • 希望对做数据库课程设计的同学有所帮助!~~还有就是这些课程设计比较简单(3~10几M/个)——如果做毕业设计请绕行... 每个项目中包含:源代码、文档、使用说明... 资源分我觉得还算适合(*^__^*) 嘻嘻…… 数据库课程...
  • 希望对做数据库课程设计的同学有所帮助!~~还有就是这些课程设计比较简单(3~10几M/个)——如果做毕业设计请绕行... 每个项目中包含:源代码、文档、使用说明... 资源分我觉得还算适合(*^__^*) 嘻嘻…… 数据库课程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,750
精华内容 700
关键字:

代码设计的种类