zoukankan      html  css  js  c++  java
  • 20145305《JAVA程序设计》实验二

    实验内容

    1.初步掌握单元测试和TDD
    2.理解并掌握面向对象三要素:封装、继承、多态
    3.初步掌握UML建模
    4.熟悉S.O.L.I.D原则
    5.了解设计模式

    实验要求

    1.没有Linux基础的同学建议先学习《Linux基础入门(新版)》《Vim编辑器》 课程
    2.完成实验、撰写实验报告,实验报告以博客方式发表在博客园,注意实验报告重点是运行结果,遇到的问题(工具查找,安装,使用,程序的编辑,调试,运行等)、解决办法(空洞的方法如“查网络”、“问同学”、“看书”等一律得0分)以及分析(从中可以得到什么启示,有什么收获,教训等)。报告可以参考范飞龙老师的指导
    3.严禁抄袭,有该行为者实验成绩归零,并附加其他惩罚措施。
    4.请大家先在实验楼中的~/Code目录中用自己的学号建立一个目录,代码和UML图要放到这个目录中,截图中没有学号的会要求重做,然后跟着下面的步骤练习。

    实验步骤

    (一)单元测试
    (1) 三种代码
    编程是智力活动,不是打字,编程前要把干什么、如何干想清楚才能把程序写对、写好。与目前不少同学一说编程就打开编辑器写代码不同,我希望同学们养成一个习惯,当你们想用程序解决问题时,要会写三种码:

    伪代码
    产品代码
    测试代码

    我们先写伪代码,伪代码可以用汉语写,推荐大家用英语写,伪代码与具体编程语言无关,不要写与具体编程语言语法相关的语句(如用malloc分配内存,这样只能用C语言编程了),伪代码从意图层面来解决问题,最终,伪代码是产品代码最自然的、最好的注释。想用编程来解决问题,首先要用伪代码表明自己想明白了。 有了伪代码,我们用特定编程语言翻译一下,就是可用的产品代码了。产品代码写完了,如果别人要使用这个代码,把MyUtil.java拷给他就可以了。但是作为负责任的你,肯定会担心自己的程序会有Bug。如果别人用自己的代码发现一堆Bugs,那多没面子!怎么办?写了产品代码,我们还要写测试代码,证明自己的代码没有问题。Java编程时,程序员对类实现的测试叫单元测试。类XXXX的单元测试,我们一般写建一个XXXXTest的类,针对MyUtil类我们写一个MyUtilTest.java的测试模块。

    这时测试都符合预期了,我们把MyUtil.java提供给别人使用时,心里比较有底气了。那如何保证单元测度是充分的呢?我们的一般要求是测试代码要比产品代码多。如何写测试,《单元测试之道》提出了Right-BICEP的方法,大家可以参考一下。 软件是由多人合作完成的,不同人员的工作相互有依赖关系。软件的很多错误都来源于程序员对模块功能的误解、疏忽或不了解模块的变化。如何能让自己负责的模块功能定义尽量明确,模块内部的改变不会影响其他模块,而且模块的质量能得到稳定的、量化的保证?单元测试就是一个很有效的解决方案。

    伪代码:

      百分制转五分制: 如果成绩小于60,转成“不及格”;如果成绩在60与70之间,转成“及格”; 如果成绩在70与80之间,转成“中等” ;如果成绩在80与90之间,转成“良好”; 如果成绩在90与100之间,转成“优秀” ;其他,转成“错误”。

    (2) TDD(Test Driven Devlopment, 测试驱动开发)

    我们先写产品代码,然后再写测试代码,通过测试发现了一些Bugs,提高了代码质量。这有问题吗?软件开发从建筑中吸取了很多营养

    工人是“先把墙砌好的,再用绳子测一下墙平不平,直不直,如果不平或不直拆了重砌”,还是“先用绳子给出平和直的标准,然后靠着绳子砌墙,从而保证了墙砌出来就是又平又直的”呢?答案是不言而喻的了。 拿编程做对比,我们是该“先写产品代码,然后再写测试代码,通过测试发现了一些Bugs,修改代码”,还是该“先写测试代码,然后再写产品代码,从而写出来的代码就是正确的”呢?当然先写测试代码了。这种先写测试代码,然后再写产品代码的开发方法叫“测试驱动开发”(TDD)。TDD的一般步骤如下:

    明确当前要完成的功能,记录成一个测试列表
    快速完成编写针对此功能的测试用例
    测试代码编译不通过(没产品代码呢)
    编写产品代码
    测试通过
    对代码进行重构,并保证测试通过(重构下次实验练习)
    循环完成所有功能的开发
    基于TDD,我们不会出现过度设计的情况,需求通过测试用例表达出来了,我们的产品代码只要让测试通过就可以了。

    (二)面向对象三要素

    (1)抽象

    抽象一词的本意是指人在认识思维活动中对事物表象因素的舍弃和对本质因素的抽取。抽象是人类认识复杂事物和现象时经常使用的思维工具,抽象思维能力在程序设计中非常重要,"去粗取精、化繁为简、由表及里、异中求同"的抽象能力很大程度上决定了程序员的程序设计能力。
    抽象就是抽出事物的本质特征而暂时不考虑他们的细节。对于复杂系统问题人们借助分层次抽象的方法进行问题求解;在抽象的最高层,可以使用问题环境的语言,以概括的方式叙述问题的解。在抽象的较低层,则采用过程化的方式进行描述。在描述问题解时,使用面向问题和面向实现的术语。 程序设计中,抽象包括两个方面,一是过程抽象,二是数据抽象。

    (2)封装、继承与多态

    面向对象(Object-Oriented)的三要素包括:封装、继承、多态。面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。OOA根据抽象关键的问题域来分解系统,关注是什么(what)。OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。贯穿OOA、OOD和OOP的主线正是抽象。 OOD中建模会用图形化的建模语言UML(Unified Modeling Language),UML是一种通用的建模语言,我们实验中使用umbrello进行建模,Windows中推荐大家使用 StarUML。

    过程抽象的结果是函数,数据抽象的结果是抽象数据类型(Abstract Data Type,ADT),类可以作具有继承和多态机制的ADT。数据抽象才是OOP的核心和起源。

    OO三要素的第一个要素是封装,封装就是将数据与相关行为包装在一起以实现信息就隐藏。Java中用类进行封装

    我们可以用UML中的类图来描述类Dog,首先我们在实验楼的环境中打开shell,在命令行中输入umbrello,打开UML建模软件umbrello

    测试用例

    用UML中的类图来描述类Dog,首先在环境中打开shell,在命令行中输入umbrello,打开UML建模软件umbrello。先单击工具栏上的类图标,再在class diagram(类图)中单击一下,会弹出一个对话框,输入类名Dog,把鼠标放到Dog类上,单击右键,选择Properties,在弹出的对话框中的Display中去掉Public Only选项,把鼠标放到Dog类上,单击右键,选择New->Attribute,在弹出的对话框中的填好Type,Name,并选好Visibility,把鼠标放到Dog类上,单击右键,选择New->Operation,在弹出的对话框中的填好Type,Name,并选好Visibility。在UML 里,一个类的属性能显示它的名字,类型,初始化值,属性也可以显示private,public,protected。 类的方法能显示它们的方法名,参数,返回类型,以及方法的private,public,protected属性。其中:+表示public,#表示 protected,-表示 private。

    注意UML类图中继承的表示法,是用一个带三角的直线指向父类,通过继承,我们消除了Dog类和Cat类中的重复代码,符合DRY的要求。 继承指一个类的定义可以基于另外一个已经存在的类,即子类基于父类,从而实现父类代码的重用。既存类称作基类、超类、父类(base class、super class、parent class),新类称作派生类、继承类、子类(derived class、inherited class、child class)。继承关系表达了”Is a kind of“的关系,称为“ISA”关系。继承的关键在于确认子类为父类的一个特殊类型 。继承是实现软件可重用的根基,是提高软件系统的可扩展性与可维护性的主要途径。 以封装为基础,继承可以实现代码复用,需要注意的是,继承更重要的作用是实现多态。 面向对象中允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式,我们称此现象为多态性。Java中,多态是指不同的类对象调用同一个签名的成员方法时将执行不同代码的现象。多态是面向对象程序设计的灵活性和可扩展性的基础。 我们可以进一步抽象,把Dog类中的bark()和Cat类中的meow()抽象成一个抽象方法shout(),Dog类和Cat类中覆盖这个方法

    (三)设计模式初步

    (1)S.O.L.I.D原则

    面向对象三要素是“封装、继承、多态”,任何面向对象编程语言都会在语法上支持这三要素。如何借助抽象思维用好三要素特别是多态还是非常困难的,S.O.L.I.D类设计原则是一个很好的指导:

    SRP(Single Responsibility Principle,单一职责原则)
    OCP(Open-Closed Principle,开放-封闭原则)
    LSP(Liskov Substitusion Principle,Liskov替换原则)
    ISP(Interface Segregation Principle,接口分离原则)
    DIP(Dependency Inversion Principle,依赖倒置原则)
    OCP是OOD中最重要的一个原则,OCP的内容是:

    software entities (class, modules, function, etc.) should open for extension,but closed for modification.
    软件实体(类,模块,函数等)应该对扩充开放,对修改封闭。对扩充开放(Open For Extension )要求软件模块的行为必须是可以扩充的,在应用需求改变或需要满足新的应用需求时,我们要让模块以不同的方式工作; 对修改封闭(Closed for Modification )要求模块的源代码是不可改动的,任何人都不许修改已有模块的源代码。 基于OCP,利用面向对象中的多态性(Polymorphic),更灵活地处理变更拥抱变化,OCP可以用以下手段实现:(1)抽象和继承,(2)面向接口编程。
    LSP的内容是:

    Subtypes must be substitutable for their base types
    Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it
    子类必须可以被其基类所代
    使用指向基类的指针或引用的函数,必须能够在不知道具体派生类对象类型的情况下使用它请大家想一想,Square类为何不能继承Rectangle类,在数学上好像是没有什么问题的呀。LSP告诉大家的一点是不要滥用继承,LSP原则清楚地指出,OOD中“ISA关系”是就行为功能而言。行为功能(behavior)不是内在的、私有的,而是外在、公开的,是客户程序所依赖的接口。

    比如,在一个图形系统中,已经存在三个模块Shape,Square,Circle,

    用户现大需要一个Triangle模块是一个合理的要求,由于我们使用了多态,原先的模块不需要改变,只要新增加一个模块Triangle就可以了。这个图形系统是符合OCP原则的。

    (2)模式与设计模式

    模式是某外在环境(Context) 下﹐对特定问题(Problem)的惯用解决之道(Solution)。模式必须使得问题明晰,阐明为什么用它来求解问题,以及在什么情况下有用,什么情况下不能起作用,每个模式因其重复性从而可被复用,本身有自己的名字,有可传授性,能移植到不同情景下。模式可以看作对一个问题可复用的专家级解决方法。 计算机科学中有很多模式:

    GRASP模式
    分析模式
    软件体系结构模式
    设计模式:创建型,结构型,行为型
    管理模式: The Manager Pool 实现模式
    界面设计交互模式

    这里面最重要的是设计模式,在面向对象中设计模式的地位可以和面向过程编程中的数据结构的地位相当。

    (3)设计模式实示例

    设计模式(design pattern)提供一个用于细化软件系统的子系统或组件,或它们之间的关系图,它描述通信组件的公共再现结构,通信组件可以解决特定语境中的一个设计问题。设计模式可以帮我们以最好的方式来设计系统。设计模式背后是抽象和SOLID原则。 设计模式有四个基本要素:

    Pattern name:描述模式,便于交流,存档
    Problem:描述何处应用该模式
    Solution:描述一个设计的组成元素,不针对特例
    Consequence:应用该模式的结果和权衡(trade-offs)

    (四)练习
    1使用TDD的方式设计关实现复数类Complex。
    1.伪代码:

    伪代码:

    Complex类要输出实部,输出虚部,并按照a+bi的形式输出复数。

    Complex类中有两个变量,实部RealPart和虚部ImaginePart;

    方法:

    getRealPart(int RealPart);返回实部

    getImaginePart(int ImaginePart);返回虚部

    toString(int RealPart,int ImaginePart);将复数输出成a+bi的格式。
    2.测试代码:

    import org.junit.Test;
    import junit.framework.TestCase;
    public class MyComplexTest extends TestCase {
    
    @Test
    public void testRealPart() {
    assertEquals(1, MyComplex.getRealPart(1));
    assertEquals(-1, MyComplex.getRealPart(-1));
    assertEquals(5, MyComplex.getRealPart(5));
    assertEquals(22, MyComplex.getRealPart(22));
    assertEquals(-100, MyComplex.getRealPart(-100));
    assertEquals(0, MyComplex.getRealPart(0));
    }
    @Test
    public void testImaginePart(){
    assertEquals(1, MyComplex.getImaginePart(1));
    assertEquals(-1, MyComplex.getImaginePart(-1));
    assertEquals(5, MyComplex.getImaginePart(5));
    assertEquals(22, MyComplex.getImaginePart(22));
    assertEquals(-100, MyComplex.getImaginePart(-100));
    assertEquals(0, MyComplex.getImaginePart(0));
    }
    @Test
    public void testtoString(){
    assertEquals("1+2i", MyComplex.toString(1,2));
    assertEquals("1-4i", MyComplex.toString(1,-4));
    assertEquals("19", MyComplex.toString(19,0));
    assertEquals("-3i", MyComplex.toString(0,-3));
    assertEquals("0", MyComplex.toString(0,0));
    }
    
    }
    

    3.产品代码:

    public class MyComplex {
    static int r;
    static int i;
    public static int getRealPart(int RealPart){
    r = RealPart;
    return r;
    }
    public static int getImaginePart(int ImaginePart){
    i = ImaginePart;
    return i;
    }
    public static String toString (int RealPart,int ImaginePart){
    if(RealPart==0 && ImaginePart==0)
    return "0";
    else if(RealPart == 0)
    return ImaginePart+"i";
    else if(ImaginePart == 0)
    return RealPart+"";
    else
    {
    if(ImaginePart>0)
    return RealPart+"+"+ImaginePart+"i";
    else if(ImaginePart<0)
    return RealPart+""+ImaginePart+"i";
    }
    return "Wrong!";
    }
    }
    

    2.实验报告中统计自己的PSP(Personal Software Process)时间

    步骤 耗时 百分比
    需求分析 10-15 15%
    设计 25-30 25%
    代码实现 35-40 40%
    测试 5-10 5%
    分析总结 10-15 15%

    单元测试的好处:

    1、经过单元测试的代码,质量能够得到保证。

    2、单元测试发现的问题很容易定位。

    3、修改代码犯的错,经过单元测试易发现。

    4、单元测试可以在早期就发现性能问题。

    实验体会:

      这次实验花费了较长的时间,内容虽然有些多,但是从这次实验中学到了很多课本上没有的东西,知道了写代码的正确步骤,有三种代码要写。还认识到了单元测试的重要性,并且自身使用TDD的方式设计关实现复数类Complex,巩固了大致过程,初步掌握单元测试和TDD,也提升了自身编程完善性。除此之外,还初步掌握了UML建模,熟悉了S.O.L.I.D原则,初步了解了设计模式。运用好单元测试可以在未来的程序设计中让自己负责的模块功能定义尽量明确,模块内部的改变不会影响其他模块,而且模块的质量能得到稳定的、量化的保证。

  • 相关阅读:
    基于webpack的react脚手架
    关于密码的简单加密
    移动端日期控件
    JS中如何巧妙的用事件委托
    JS中关于正则的巧妙操作
    call,apply,bind
    vue常用笔记
    高性能的js第三方库——lodash、 Underscore、async、md5及moment
    Nightwatch——自动化测试(端对端e2e)
    for循环的耗时问题
  • 原文地址:https://www.cnblogs.com/summerharper/p/5389260.html
Copyright © 2011-2022 走看看