面向对象程序设计(Object Oriented Programming)作为一种程序设计方法,其本质是以建立模型体现出来的抽象思维过程。模型是用来反映现实世界中事物特征的,任何一个模型都不可能反映客观事物的一切具体特征,只能是对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。本场 Chat 中,将通过讲解和具体程序实例介绍程序设计的方法、面向对象程序设计的编程机制,以及我们如何利用 Java 这门语言,来实现面向对象的编程,提升面向对象程序设计开发的能力。目前,主流程序设计的方法主要分为两种,一种是面对过程的程序设计方法,另一种是面向对象的程序设计方法,下面将分别进行讲解。
面向过程的程序设计方法 面向过程的程序设计方法的编程机制及得失面向过程的程序设计方法,它的编程机制是我们首先要考虑程序要实现什么样的功能。然后,设计实现这个功能所需要采取的步骤,这个步骤就是一个过程。面向过程的程序设计方法,它的编程机制关注的是如何实现过程。当我们采用面向过程的程序设计方法,编写程序的时候,得到的程序结构是“数据 + 算法”,其中的算法就是解决问题的步骤。过程化程序设计支持逐步求精的程序开发方法,这种方法对复杂算法的实现有很重要的启发性,针对复杂算法,我们不是一次就详细写出它的所有实现细节,而是从一个基本算法框架入手,逐步的进行细节实现。
“数据 + 算法”的程序结构,适用于中小型程序,一般代码长度在 1 万行以内,如果我们的程序,想要有更多的功能,那么就得定义新的函数,增加新的代码。当代码量超过 1 万行,大到一定程度的时候,面向过程的程序设计方法,它的缺点就暴露出来了。
- 第一个缺点,程序的代码不易维护,如果程序的数据结构发生了改变,操作这个数据结构的函数也需要做相应的修改,如果一个数据结构被很多函数访问,那么这些函数都需要进行修改,这样工作量就很大,所以代码不易维护。
第二个缺点,代码复用的效率不高,只有函数的代码得到了复用,保存数据的变量的代码并没有得到复用,所以代码的复用效率不高。 第三个缺点,数据的安全得不到保证,开发团队中的一名成员定义的变量,可能被其成员编写的方法访问修改,这样当程序运行的时候,这个变量的值就可能被意外的修改,所以数据的安全得不到保证。在这些问题当中,最严重的是数据安全问题,因为数据的安全得不到保证,可能直接导致程序可能无法正确执行。
采用面向过程的程序设计方法模拟 ATM 自动取款机的工作流程下列代码包括一个主过程 main() 和 8 个子过程,在主过程中对子过程进行调用,程序没有模拟用户账户的对象,所以取钱后,程序并没有从账号中扣除取钱的金额。
import java.util.Scanner;public class SimulateATM { public static void main(String[] args) { int total = 5000; ATM atm = new ATM(); int select; //用户输入的服务 atm.welcome(); //显示欢迎信息 atm.pass(); //进行密码验证 do { select = atm.service(); atm.selectService(select,total); }while(true); } void welcome() { System.out.println("------------------ATM 自动取款系统--------------------"); System.out.println("请输入您的磁卡(按任意键完成)"); } void pass() { int n,password; Scanner in = new Scanner(System.in); for(n=1;n<=3;n++) { if(n==1) { System.out.println("请输入密码(最多可输入 3 次)!"); }else { System.out.println("密码错误,请重新输入!"); } password = in.nextInt(); if(password==123) { break; } if(n>3) { System.out.println("遇到问题,请与银行管理员联系"); System.exit(1); } } } int service() { int select; Scanner in = new Scanner(System.in); System.out.println("n*******************欢迎进入银行自动取款系统******************n"); System.out.println("n*******************您选择的服务是******************n"); System.out.println(" 查询余额——1n"); System.out.println(" 取款——2n"); System.out.println(" 快速取款——3n"); System.out.println(" 取卡——0n"); System.out.println("-------------------------------------------------------n"); System.out.println("请输入选择:"); select = in.nextInt(); return select; } void selectMoney(int a){ System.out.println("nn 您账户上的余额为"+a+"元nn"); } void getMoney(int total) { int number; int flag; Scanner in = new Scanner(System.in); System.out.println("请输入取款金额:"); number = in.nextInt(); if(total>=number) { total = total - number; System.out.println("请取走您的现金"+number+"元n"); System.out.println("是否需要打印凭证(1/0)?"); flag = in.nextInt(); if(flag == 1) { System.out.println("您取款"+number+"元"); } }else { System.out.println("您的余额不足!"); } } void quickGetMoney(int total) { Scanner in = new Scanner(System.in); int select,number=0; String flag; System.out.println("tt-------------请选择取款金额n"); System.out.println("tt100(1)tt200(2)ntt500(3)tt1000(4)n"); select = in.nextInt(); switch(select) { case 1:number=100;break; case 2:number=200;break; case 3:number=500;break; case 4:number=1000;break; } if(total >= number) { System.out.println("请取走您的现金"+number+"元n"); total = total - number; System.out.println("是否需要打印凭证(Y/N)?"); flag = in.next(); if("Y".equals(flag)) { System.out.println("您取款"+number+"元"); } }else { System.out.println("您的余额不足!"); } } void exitATM() { System.out.println("请取走您的磁卡,谢谢,欢迎下次光临!n"); System.exit(1); } void selectService(int select,int total) { switch(select) { case 1:selectMoney(total);break; case 2:getMoney(total);break; case 3:quickGetMoney(total);break; case 0:exitATM(); default:System.out.println("非法操作!"); } }}
面向对象的程序设计方法 为了解决面向过程的程序设计方法暴露的问题,人们就提出了面向对象的程序设计方法,我们说一门编程语言是面向对象的程序设计语言,那就意味着这门语言是支持面向对象程序设计方法的。所以,学习这门语言关键就是理解面向对象的程序设计方法,掌握它的编程机制。
在讲解之前,我们先了解一下什么是面向对象?面向对象是一种解决问题的方法和观点,它认为现实世界是由一组彼此相关,并且能够相互通信的实体所组成。比如教室里的课桌,电脑都是实体。一个事物在现实世界中,我们称它为实体,在由计算机软硬件所组成的信息系统中,我们称它为对象。现实世界中的实体都有属性和行为,我们程序中的对象,相应的也有属性和行为。比如一块手表,它有属性时、分、秒,用来代表当前时间,它有行为显示时间、设置时间,代表它做具有的功能。
面向对象的程序设计方法的编程机制及得失了解了面向对象的概念之后,我们再进一步学习面向对象的编程机制。面向对象的程序设计,它的编程机制是,我们首先考虑要实现什么样的功能,然后设计实现这个功能所需要的对象和对象之间是如何发送消息相互驱动的。面向对象的程序设计它的编程机制关注的是对象和对象之间的关系。
当我们采用面向对象的程序设计方法,编写程序的时候,得到的程序结构是“对象+消息”。简单的说,编程就是用计算所需的指令来构成一种运算装置。对象在操作上是一种能响应消息的抽象的机器。这种方法的优点是,它更符合人的思维方式,面向对象的程序设计语言更容易被学习者掌握,编写的程序更容易维护。更重要的是“对象+消息”的程序结构可以有效的保证数据的安全。
面向对象系统中的计算是指对象间相互发送消息,这可能导致非常复杂的指令序列,为了控制这种复杂性我们需要考虑如何组织对象,我们需要一个定义和协调对象间交互的软件体系结构。面向对象方法使程序开发有了很大的灵活性,但他仍然存在一些固有的本身机制难以解决的问题,面向对象库和框架的设计经验表明用对象构建的模块结构有时是不稳定的。因此,面向对象的真正长处可能要打一些折扣。另外面向对象的程序设计还需要克服以下两个缺点。
第一个缺点,虽然继承性提高了代码的可复用性,但由于基类的信息隐藏,只阅读子类的代码,往往不足以很好的理解它。
第二个缺点,如果把开发的重点放在软件设计阶段,而不是编程实现阶段,就比较容易建立起稳定的模块化结构,采用面向对象程序设计,程序员可以自然而直接的,直接地对应用系统建模,但这要以提高设计阶段的投资为代价。
面向对象程序设计的四个基本机制:抽象、封装、继承、多态采用面向对象程序设计方法之所以有这么多的好处,是通过面向对象的编程机制来实现的,下面我们来进一步讲解面向对象的编程机制。面向对象的程序设计,包括四个基本机制:抽象、封装、继承、多态。
抽象抽象就是找共性,现实世界中的实体都有属性和行为,我们把具有相同属性名和行为名的实体归为一类,就得到了类型,类型和类是同一个概念,类型的定义就是类的定义。我们根据类型创建对象,类就相当于是对象的设计图纸,对象是类的实例。我们编写程序首先要定义类,然后根据类的定义创建对象,使用对象。
自然语言有语法规则,计算机编程语言也有语法规则。我们必须按照规定的语法格式来编写程序,这样的程序才能够在计算机上执行,定义一个类的语法格式,类的定义包含两部分:类头、类体。
类头由关键字 class 以及后面的类名构成。类体是一个代码块。类体的里面定义了类的成员,类的成员包括数据成员和成员函数,类的数据成员代表对象具有的属性,成员函数代表对象具有的行为,我们在类体中对数据成员和成员函数进行声明,数据成员的声明方式和变量的声明方式相同,成员函数的声明方式和函数的声明方式相同。
下面我们来看一个例子,我们在这里定义了一个学生类,学生类是对学生实体的抽象,因此需要了解学生对象有哪些特征,可知每个学生都有身份证号、学号、姓名、年龄以及性别等信息,因此在学生类中就要对学生这些共同特征进行抽象。
public class Student//类名{ String id;//身份证号码 String name; //姓名 int age;//年龄 String sex;//性别 String sno;//学号}public class TestStudent{ public static void main(String [] args){ //创建学生对象,对象名小明 Student xiaoming = new Student (); }}public class StudentWithMethod { String id;//身份证号码 String name; //姓名 int age;//年龄 String sex;//性别 String sno;//学号 public void study( ){ System.out.println("我是"+name+",我在好好学习"); }}
封装 封装是对象支持的信息隐藏,打包和隐藏两者的结合就称为封装。对象在结构上可以看成是数据和方法的集合,原则上数据对外是不可见的,只能通过调用过程本身对其进行操作调用,这样的过程的唯一途径就是向对象发送消息数据隐藏。和消息接口抽象结合在一起被称为封装。事实上,在面向对象语言中,对象都是数据和过程的结合,只是对数据隐藏和抽象的程度和方式有所不同,方法提供服务,消息是执行方法的请求。
使用对象时,客户程序只需要知道对象能做什么,而不需要知道对象是如何做的,然而要体现出对象数据隐藏的优势对象的开发者必须提供一个能够以充分抽象的方式表现对象行为的接口,这种设计思想把对象看成是一个能响应高层请求的服务器,同时还要决定应用程序需要对象提供何种服务。
对象实现封装后,对象成员的可见范围,可以通过类定义中成员声明前的访问控制修饰确定,访问控制修饰符及其被修饰成员的可见范围如下所示:
示例代码如下:
package cn.edu.lyu.info.test1;//理解访问修饰符public class AccessModifier { private int a;// 私有整型变量 a int b;// 缺省的整型变量 protected int c;//受保护变量 c public int d;//公有的变量 d public AccessModifier() { super(); a = 1; b = 2; c = 3; d = 4; }}package cn.edu.lyu.info.test1;public class AccessModifierTest { //测试同包中访问修饰符类各种访问修饰符的权限 public static void main(String[] args) { AccessModifier modifier = new AccessModifier(); //modifier.a = 4;//error 私有成员变量 a 的类外无法访问 modifier.b = 6;//正确 缺省的成员变量 b 在同包内可以访问 modifier.c = 8;//正确 受保护的成员变量 c 在同包内可以访问 modifier.d = 10;//正确,公有的成员变量 d 在同包内可以访问 }}package cn.edu.lyu.info.test2;//导入包中的类import cn.edu.lyu.info.test1.AccessModifier;public class AccessModifierTest1 { //测试其他包中访问修饰符类各种访问修饰符的权限 public static void main(String[] args) { AccessModifier modifier = new AccessModifier(); //modifier.a =4;//error 私有成员变量 a 的类外无法访问 //modifier.b = 6;//error 缺省成员变量 b 在其他包内不能访问 //modifier.c = 8;//error 受保护成员变量 c 在其他包内不能访问 modifier.d = 10;//正确,公有的成员变量 d 在其他包内可以访问 }}
继承 继承是提供可复用性的主要机制,因为复用的代码往往不能满足全部需求,这时继承机制,使程序员可以修改对象类的行为,而无需访问源代码。对象按类进行划分,类定义了对象的行为,也可以把类看成创建对象的模板,具有继承性,从设计的角度看,继承机制主要用于提高类的可复用度。类的层次结构是表示类的继承关系的树结构,继承的语法格式如下所示:
访问控制修饰符 子类名 extends 父类名{ //子类类体}
Java 中的继承关系是单继承关系,子类只能有一个父类,即 extends 后面的父类只能有一个。
例如:动物类定义属性动物的名称,体重,同时所有的动物都有吃、运动的行为,因此定义 eat 方法和 run 方法。老虎类继承动物类,因此继承相关的属性和方法,同时老虎有自己的新特性,狩猎,因此在老虎类有新方法 hunt。
public class Animal {//定义动物类 protected String name;//定义变量 name public void run(){ System.out.println("动物可以运动..."); } public void eat(){ System.out.println("动物需要吃东西..."); } public Animal(String name) { super(); this.name = name; } public Animal() { name = "未知"; } }//extends 继承动物类,生成老虎类public class Tiger extends Animal{ public void hunt(){// 添加新方法 狩猎 System.out.println("老虎可以凶猛的狩猎"); }}package cn.edu.lyu.info.inherit;public class TigerTest { public static void main(String[] args) { //测试老虎类拥有的方法 Tiger tiger = new Tiger(); tiger.setName("老虎");//调用父类方法,设置 name 属性 tiger.eat();//调用父类方法 tiger.run();//调用父类方法 tiger.hunt();//调用子类新加方法 }}
多态 多态的定义:多态是指对象在调用同一个函数时所呈现的多种不同的行为。
补充说明:同一个函数意味着函数名相同,不同的行为意味着函数体不同。
多态的实现:
编译时多态:函数的重载 运行时多态:函数的重写函数的重载
问题的提出:函数名数量巨大,管理困难。 解决方案:功能相近的函数进行合并。例如下列代码中,将类 Examle05 中的函数 add01、add02、add03 合并为类 Examle06 中的 add 函数。
public class Example01{ public int add01(int x, int y){ return x + y; } public int add02(int x, int y,int z){ return x + y + z; } public double add03(double x, double y){ return x + y; } public static void main(String[] args ){ Example01 example01 = new Example01(); int sum1 = example01.add01(1,2); int sum2 = example01.add02(3,4,7); double sum3 = example01.add03(0.2,5.3); System.out.println("sum1="+sum1); System.out.println("sum2="+sum2); System.out.println("sum3="+sum3); }}//当定义函数时,存在多个具有相同函数名的函数,但具有不同函数列表的函数,则意味着该函数被重载。public class Example02{ public int add(int x, int y){ return x + y; } public int add(int x, int y,int z){ return x + y + z; } public double add(double x, double y){ return x + y; } public static void main(String[] args ){ Example02 example02 = new Example02(); int sum1 = example02.add(1,2); int sum2 = example02.add(3,4,7); double sum3 = example02.add(0.2,5.3); System.out.println("sum1="+sum1); System.out.println("sum2="+sum2); System.out.println("sum3="+sum3); }}
使用重载时需要注意的规则如下所示:
被重载的函数必须改变参数列表; 被重载的函数可以改变返回类型; 被重载的函数可以改变访问控制修饰符; 被重载的函数可以声明新的或更广的检查异常; 函数能够在同一个类中或者再一个子类中被重载。注意:第 2 条和第 3 条规则也可以表述为,函数的返回类型、修饰符与函数重载无关,函数的返回类型、修饰符可以相同,也可不同。
函数的重写
子类重写从父类继承的函数,重写表示重写继承函数的函数体的语句,而不是改变函数首部,这与函数的重载不同。子类重写的函数的访问权限不能低于父类函数的访问权限,例如父类函数使用 public 修饰,那么子类重写函数只能使用 public 修饰,当子类对象调用重写的函数时,调用子类重写后的函数。
例如:Tiger 类继承 Animal 的类的 walk 和 eat 函数,但 Animal 类的 walk 和 eat 函数不适合 Tiger 类,因此要重写两个函数的方法体。
package cn.edu.lyu.info.inherit;public class Tiger extends Animal { //Tiger 继承 Animal 并函数重写 public void run() {// 重写父类函数 run,重写函数体 System.out.println("老虎用四肢跑步,跑的飞快"); } // 注解表示该函数为重写父类函数 public void eat() {// 重写父类函数 eat System.out.println("老虎是肉食动物,凶猛的吃肉"); } public void hunt() { System.out.println("老虎可以凶猛的狩猎"); }}package cn.edu.lyu.info.inherit;public class TestTiger { public static void main(String[] args) {// TigerOverride tiger = new TigerOverride(); tiger.setName("老虎"); //调用父类函数,设置 name 属性 tiger.eat(); //调用子类重写父类的函数 tiger.run(); //调用子类重写父类的函数 tiger.hunt();//调用子类新加函数 }}
总而言之,多态性是隐藏公用接口的不同实现,对象灵活的分配行为使对象具有多态性,这得益于方法和消息的动态绑定。类的继承导致了类的层次结构,从操作的角度看,这种结构决定了对象的分配行为,即对于某一消息选用哪个方法来处理。
采用面向对象的程序设计方法模拟 ATM 自动取款机的工作流程程序源代码包括 ATM.java 和 TestATM.java 两个文件。
源代码文件 ATM.java 的内容如下:
import java.util.Scanner;public class ATM { Scanner in = new Scanner(System.in); void welcome() { System.out.println("------------------ATM 自动取款系统--------------------"); System.out.println("请输入您的磁卡(按任意键完成)"); } void pass() { int n,password; for(n=1;n<=3;n++) { if(n==1) { System.out.println("请输入密码(最多可输入 3 次)!"); }else { System.out.println("密码错误,请重新输入!"); } password = in.nextInt(); if(password==123) { break; } if(n>3) { System.out.println("遇到问题,请与银行管理员联系"); System.exit(1); } } } int service() { int select; System.out.println("n*******************欢迎进入银行自动取款系统******************n"); System.out.println("n*******************您选择的服务是******************n"); System.out.println(" 查询余额——1n"); System.out.println(" 取款——2n"); System.out.println(" 快速取款——3n"); System.out.println(" 取卡——0n"); System.out.println("-------------------------------------------------------n"); System.out.println("请输入选择:"); select = in.nextInt(); return select; } void selectMoney(int a){ System.out.println("nn 您账户上的余额为"+a+"元nn"); } void getMoney(int total) { int number; int flag; System.out.println("请输入取款金额:"); number = in.nextInt(); if(total>=number) { total = total - number; System.out.println("请取走您的现金"+number+"元n"); System.out.println("是否需要打印凭证(1/0)?"); flag = in.nextInt(); if(flag == 1) { System.out.println("您取款"+number+"元"); } }else { System.out.println("您的余额不足!"); } } void quickGetMoney(int total) { int select,number=0; String flag; System.out.println("tt-------------请选择取款金额n"); System.out.println("tt100(1)tt200(2)ntt500(3)tt1000(4)n"); select = in.nextInt(); switch(select) { case 1:number=100;break; case 2:number=200;break; case 3:number=500;break; case 4:number=1000;break; } if(total >= number) { System.out.println("请取走您的现金"+number+"元n"); total = total - number; System.out.println("是否需要打印凭证(Y/N)?"); flag = in.next(); if("Y".equals(flag)) { System.out.println("您取款"+number+"元"); } }else { System.out.println("您的余额不足!"); } } void exitATM() { System.out.println("请取走您的磁卡,谢谢,欢迎下次光临!n"); System.exit(1); } void selectService(int select,int total) { switch(select) { case 1:selectMoney(total);break; case 2:getMoney(total);break; case 3:quickGetMoney(total);break; case 0:exitATM(); default:System.out.println("非法操作!"); } }}//源代码文件 TestATM.java 的内容如下:public class TestATM { public static void main(String[] args) { int total = 5000; ATM atm = new ATM(); int select; //用户输入的服务 atm.welcome(); //显示欢迎信息 atm.pass(); //进行密码验证 do { select = atm.service(); atm.selectService(select,total); }while(true); }}
总结 面向对象的程序设计方法,其本质是强调从客观世界固有的事物出发来构造系统,用人类习惯的思维方式来认识理解和描述客观事物,强调最终建立的软件系统能够映射问题域软件系统中的对象和对象之间的关系,能够如实的反映问题域中的事物及其关系,面向对象对于复杂软件系统的分析设计实现与测试向软件开发人员提供了正确的抽象。
把对象作为软件分析、设计和实现的基本单元,使我们可以以一种自然的方式定义整个软件系统的结构和行为。面向对象编程并没有扩大可计算问题的类型,也不能降低我们能够处理的问题的计算复杂性。我们不能期望解决更多的问题,也不能期望减少运算的复杂性,但是面向对象方法能提供更好的方式来编程更好的方式来减少错误以及更好的方式来控制编程任务的复杂性,不是计算本身的复杂性,换句话说。通过更多的面向人而更少的面向机器的抽象机制,面向对象使我们在实际软件开发中能处理更多的问题。