软件构造-7 面向对象的编程(OOP)
面向对象:一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组成。
个人理解:其思想就在于将事物抽象出来,建立模型,用以描述其特征,形成一个类,然后在具体使用的时候,我们可以为其添加更多私有化的特征(通过构造器),形成一个对象,然后实际上程序中会有很多类,使用这些类和对象来进行程序的编写。
比如说我们需要编写一个学生管理程序,这个程序用java写成,是面向对象的。在这过程中需要描述学生,可以将学生抽象成一个模型(一个类),叫做Student类,这个类中描述学生的特征和一些操作这个学生对象的方法,比如里面的rep有学生的姓名,年级,专业等。其中的方法可以有获得学生的姓名等属性,或者改变学生的专业等操作(这个学生类型是高度抽象的,拥有这个类型对象应该具有的特征),在编写程序管理学生的过程中,你需要实例化每个学生,于是你在用Student类构造对象时,向其中传入学生的具体信息,比如这个学生叫张三,18岁等等。然后这个抽象类由这些信息初始化就得到了一个具体的学生对象。然后这个对象可以使用抽象类中的各个方法。当然,这个管理系统中可能还存在各种其他的类,最终主要由这些类和对象参与,你写成了这个学生管理程序,其中体现的就是OOP的思想。
OOP的不同特征
- 封装与信息隐藏
- 继承与重写
- 多态,子类型,重载
- 静态与动态分派
1. 基础概念:对象,类,属性和方法
(1)对象:两个特征——状态和行为
Java中,状态即为对象中的数据(object中的filed),方法即为这个对象支持的操作。
(2)类:每个对象都有一个类,类中定义methods和fields,类中的methods就是它的API
(3)方法:static修饰的静态方法(类方法),在别的类中可以直接使用类名.method调用,在类内部可以直接method调用,实例方法(无static)在类内部实例方法中可以直接method调用,在外部必须要先new对象,再使用对象.methos调用。
静态方法只能访问静态变量(static修饰的成员变量)(但是也可以通过new类的实例对象来访问实例变量),实例方法可以访问静态变量或实例变量;
2. 接口
(1)接口和类:定义实现ADT
(2)接口中只有方法的定义,没有实现,接口之间可以继承与扩展
(3)一个类可以实现多个接口,一个接口可以有多个实现类
(类实现接口:在类后加implements xxx,xxx……)
(在使用的时候,接口对象=new实现接口的类)
(4)接口:确定ADT规约;类:实现ADT
(5)也可以不用接口,直接使用类作为ADT,既有ADT定义也有ADT实现
(实际中更倾向于使用接口来定义变量)
(6)实例:
接口:如下例,只有对方法的定义,没有具体实现

实现:必须实现接口中的每个方法,可以增加别的方法,但该实现的不能少
实现使用重写的方法,要带@Override
(下图只是一种实现的例子)

(7)接口的问题:打破了抽象边界,接口定义中没有包含构造器,无法保证所有实现类中都包含了同样名字的构造器,因此客户端需要知道该接口的某个具体实现类的名字,这个问题由8中提到的静态工厂方法实现。
(8)使用静态工厂方法替代构造器:可以在接口中编写有实现内容的静态工厂方法作为构造器,这个方法用static修饰,返回类型是接口的名字,方法内容是返回new得到的的实现接口的类的对象。如下图例子:

(FastMyString是实现MyString接口的类)
这样在调用接口的时候,就可以直接调用这个静态工厂方法(静态方法用类名调用),如下图:

这样写相当于设定一个默认的实现接口的类,如果想要改成另一种实现接口的类,直接在静态工厂方法中更换即可。如果程序员已经知道实现接口的类叫什么名字,并明确自己想使用这个类,那么不调用静态工厂方法,直接new一个实现接口的类的对象即可(就相当于用上面第一个图框中内容new的内容替代第二个图的函数调用)。
(9)在接口中使用default方法:接口中的每个方法在所有类中都要实现,会导致某些方法重复实现,通过default方法在接口中统一实现某些功能,即接口中dafualt修饰的类要进行实现,代表这个方法在所有实现接口的类中都应该是这样实现的,故在实现接口的类中不必再重复实现default类型的方法。
3.封装和信息隐藏
(1)信息隐藏:辨别一个模块设计好坏与否的一个重要因素:它是否很好的隐藏了内部实现细节(对别的模块不可见),模块之间只使用API沟通
(2)好处:解耦组成系统的类(让它们可以独立修改,测试,优化等)
为系统开发加速(类可以被平行地开发)
减轻维护压力(类容易被更快地理解和debug,不需要害怕破坏其他模块)
实现有效的性能调优(更容易经常被用到的类可以被独立地优化)
提升软件复用(松散耦合类通常被证明在其他上下文中很有用)
(3)接口的信息隐藏:使用接口类型声明变量/客户端仅使用接口定义的方法/客户端代码无法直接访问属性/(但客户端仍然可以访问其他的非接口成员)
(4)可见性修饰符:

4. 继承和覆盖
(1)继承(inheritance)
1>继承是为了代码复用,子类可以继承父类(共性部分),自己再实现一些特性部分
(泛化:和继承意义一致,但适用于更广的范围,继承一般适用于编程语言)
2>可重写方法:可重写方法是允许被重新实现的方法(子类和父类方法一样,但是被重新实现了)
严格继承:子类只能添加新方法,无法重写超类中的方法(在父类方法前加final)
(2)覆盖/重写(Override)
1>子类修改从父类继承的方法,要求子类与父类方法签名完全相同(用@Override让编译器帮忙检查签名是否一致),执行时调用哪种方法,在运行时决定
2>一些想要子类都有但是在不同子类中实现差异很大的方法,在父类中进行空实现(类似接口)。
3>方法越通用,放到父类中,越具体,放到子类中(可以子类单独加或者实现父类空方法)
4>super:调用父类方法(当前类:this)(用super调用父类构造函数一定要位于当前构造函数的第一行)
5>建议:重写时尽量不要破坏原方法本意
6>重写方法不能降低可见性(因为子类要能够替换父类)
(3)抽象类
1> 抽象方法:只有定义没有实现的方法,使用abstract关键字
2> 抽象类:至少有一个抽象方法/不可以被实例化/继承某个抽象类的子类想要被实例化,里面的父类中的抽象方法必须都被实现
5. 多态,子类型,重载
多态:三种类型:
特殊多态:一个方法,多种同名(不是同签名,要与重写区分)实现(方法重载)
参数化多态:一个类型名字代表多个类型(泛型编程)
子类型多态:一个变量名字代表多个类的实例(比如既可以代表子类型,也代表父类型)
特殊多态
(1)重载:同名,但是不同参数列表或返回值类型,主要是为了方便client调用,eg如下:

(2)静态多态:重载是静态类型检查(编译时根据参数列表进行最佳匹配,决定具体执行某个方法,而重写是动态类型检查,由于签名相同,运行时才决定用哪个)
(3)eg:注意,这个例子中划红线的打印的是Animal!这里是重载,静态检查,匹配时检查静态类型为Animal(运行时才有new Horse)

Eg2:这里面第三个是重写,动态检查,编译器运行的时候看的是到底这个对象he是什么类型,实际上new的是Horse类型,所以调用子类的eat方法。

参数化多态:
(1)参数多态性是指方法针对多种类型时具有同样的行为,此时使用统一的类型变量表达——泛型。数据类型和函数根据指定类型编写,在需要时根据参数提供的特定类型实例化。
(2)类型变量使用实例:

(3)使用泛型变量的三种形式:泛型类/泛型接口/泛型方法
(4)1.泛型类:类中声明一个或多个泛型变量,则为泛型类。(范型变量名字随意)

2.泛型接口:

使用时可以泛型接口,非泛型实现类:

或者泛型接口,泛型实现类(上图右遍的<Character>改成<E>.
3.泛型方法:整个类是确定的,只有某个方法是泛型的。在调用方法的时候指明泛型的具体类型:

普通类中的泛型方法:

泛型类中的泛型方法:

静态方法不能使用所在泛型类定义的泛型变量。除非把静态方法自己也定义成泛型方法。
(5)一些泛型细节:通配符(对所有类型都成立,下面的extends和super代表对其子类和父类都成立)/不能使用instanceof/不能创建泛型数组:

子类型多态:
(1)一个类只有一个父类,但可以实现多个接口
(2)子类型要能够安全地赋给父类型,子类型的规约不能弱化超类型的规约
6.Object中的一些重要方法:
(1)equals():判断两个对象是否相同,不重写的话判断而这地址空间是否相同。
(2)toStrings():一个打印出来的string表示,不重写打印16进制内存空间地址。
(3)hashCode():返回hashCode,用于在hash结构容器中使用,确定把对象放在哪里,不重写还是调用内存空间地址。

浙公网安备 33010602011771号