OO第二单元作业总结
前言
又经历的三次作业,对于面向对象的理解稍稍有些加深。
本次博客主要针对5—8周java大作业进行总结,将从作业角度来对继承,封装、多态三大技术特性进行个人更深入的理解,同时分享本人对OO编程的收获与心得。
作业过程总结
第四次作业
不难看出,第四次作业第一题水文校验通过率非常低,包括我也花了很多时间也还没能把他做出来。水文数据校验这题考察的是实体类和业务类之间的关联,难度并不在于解题的技巧和方法,而在于如何对数据进行处理。比如程序要自动过滤在每一个输入数据两端均可能存在多余的空格,同时对日期和数据的格式与规范也较为苛刻。谢雨航同学在课堂上对此题的解读让我收获颇多,他使用到了java当中的日期类和异常处理,虽然这些我们可能还没学或者即将学到,但是我们可以自学加运用,自己也加深了知识的影响,这点很值得我们学习。从第四次作业这三题可以看出都是围绕着封装和继承展开,程序设计追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉。低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。把类和方法装在一个“盒子里”,这就是封装,而封装恰恰满足了这个要求。我们在编程的时候常常遇到大量的代码需要重复的情况,导致我们的代码比较臃肿,开发效率也大大降低,所以Java继承的作用就体现出来了。定义一个类做为父类,将一些共有的属性和方法定义在这个类中,当某一个类需要使用到这些方法和属性的时候,就可以直接通过extends关键字就可以直接调用父类中的成员变量和方法,方便快捷。
第五次作业
多态是第五次作业重点考察对象。多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。java实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
第六次作业
抽象类和接口是第六次作业重点考察对象。图形卡片排序要求对卡片排序时使用 Comparable 接口, 即 Card 类需要实现 Comparable 接口中的 CompareTo()方法,目的是对Arraylist 中的图形对象在 Arraylist 中进行排序而不是单纯对图形面积进行排序。抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。需注意的是,抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
总的来说:
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
作业中遇到的问题和解决方案
第五次作业图形继承与多态我使用了冒泡排序只对图形面积进行了排序而不是对图形对象进行排序。直到发布了第六次作业我才了解到comparable接口,在排序时,java本身提供的一些类,它们已经实现comparable接口,来进行比较,但是在List容器里添加自己的对象,就没法直接调用Collections.sort()方法了,编译期不知道按照什么来进行排序,会报错,这个时候就必须用到comparable接口。
冒泡排序:
comparable接口:
class Card implements Comparable<Card>{ Shape shape; Card(){ } Card(Shape shape){ this.shape=shape; } public Shape getShape() { return shape; } public void setShape(Shape shape) { this.shape = shape; } public int compareTo(Card card) { if(this.getShape().getArea()>card.getShape().getArea()) return -1; if(this.getShape().getArea()<card.getShape().getArea()) return 1; else return 0; } }
在求素数算法优化中,我第一次是单纯地遍历一遍所有范围内的数字,发现运行时长竟然有几千ms,然后我换了一种又一种方法,时长越来越短,最终确定了我认为“最优”的算法:利用了筛法,首先,2是公认最小的质数,所以,先把所有2的倍数去掉;然后剩下的那些大于2的数里面,最小的是3,所以3也是质数;然后把所有3的倍数都去掉,剩下的那些大于3的数里面,最小的是5,所以5也是质数……
上述过程不断重复,就可以把某个范围内的合数全都除去(就像被筛子筛掉一样),剩下的就是质数了。
其实呢,关于代码优化这件事,我认为没有最优,只有更优,科技永远不会停下前进的脚步。
OO设计心得
面向对象三大技术特性之间关系的理解前面已经说到,下面我们来说对面向对象设计的基本原则的理解和Junit测试的理解。
基本原则的理解:
1、单一职责原则:
定义:单一原则就是一个对象或者一个方法,只做一件事。
当一个类承担了过多的职责,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责的能力。这种耦合会导致脆弱的设计,当设计变化时,设计会遭受到意想不到的破坏。
遵循单一职责的优点:
1)可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多。
2)提高类的可读性,提高系统的可维护性。
3)变更引起的风险降低,变更时必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
2、“开-闭”原则:
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。也就是说,如果修改或者添加一个功能,应该是通过扩展原来的代码,而不是通过修改原来的代码。
遵循“开-闭”原则的优点:
1)可以提高代码的复用性
2)可以提高代码的可维护性
Junit单元测试的理解
Junit单元测试可以确保单个方法按照正确预期运行,如果修改了某个方法的代码,只需确保其对应的单元测试通过,即可认为改动正确。此外,测试代码本身就可以作为示例代码,用来演示如何调用该方法。
使用JUnit进行单元测试,我们可以使用断言(Assertion
)来测试期望结果,可以方便地组织和运行测试,并方便地查看测试结果。此外,JUnit既可以直接在IDE中运行,也可以方便地集成到Maven这些自动化工具中运行。
在编写单元测试的时候,我们要遵循一定的规范:
一是单元测试代码本身必须非常简单,能一下看明白,决不能再为测试代码编写测试;
二是每个单元测试应当互相独立,不依赖运行的顺序;
三是测试时不但要覆盖常用测试用例,还要特别注意测试边界条件,例如输入为0
,null
,空字符串""
等情况。
总结
设计方面,我总是受流程式编程思维的影响,很容易将一个类一个方法写的庞大冗杂,这样虽然仍能完成作业要求的相应功能,但这不是我们的课程的目的,因此我一定要利用好之后的每一次作业,来锻炼自己面向对象的编程思维,熟练掌握JAVA相关的知识。
bug方面,在着手编程前,一定要仔细阅读作业指导书,我的第二词作业的公测bug全部源自于对指导书的阅读不仔细。此外,在充分理解指导书的要求后,对编程思路的整理也是十分有益的。
作业方面,后面的大作业的指导书可能不会给类图了,需要自己去设计,希望自己能少踩坑,多收获!