一、 引言
本章将借用一个简易的猜字谜游戏,向读者简单介绍JAVA面向对象的部分知识以及实现思路。
二、 面向对象思想导论
为了了解什么是面向对象,我们需要解决以下几点问题(如图2.1所示):
- 面向对象是什么?
- 面向对象的特征(或者说表现形式)。
- 为什么要面向对象(它与面向过程的区别)。
- 面向对象该怎么做?
众所周知,计算机的编程语言是始于对机器的模仿,即以代码的形式控制机器完成人们希望所能达到的任务或者效果,故而所有编程语言都提供了抽象机制。
而这种抽象机制实际上可以这么认为——人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。其中,所谓的“类型”是指“我们所抽象的是什么?”。
- 汇编语言:是对底层机器的轻微抽象(他的命令,往往都是涉及数据的读写移动存储,很是直白)。
- C语言:是对于汇编语言的抽象,也就是所谓的“命令式”语言。(开始逐渐开始接近人类语法,比如if语句)。
但以上两种语言所作的主要抽象仍要求在解决问题时需要计算机结构。虽然C语言可以通过struct定义结构体达到所谓面向对象的效果。但是,其本质的抽象还是基于计算机结构,这个从底层驱动时,需要#define宏定义物理地址映射就能看出来。
那么问题来了,这种基于计算机结构的语言,由于他本身的特定性(数据定义和函数调用),要求我们的程序员必须建立起机器模型与实际待解问题之间的关联。可是往往建立这种映射是费力的,我们从行业中嵌入式底层工程师人数多少就能看出来。
正是因为这些不通用性,最终催生出了面向对象这种编程思维方法。
简而言之,就是我们处理待解问题时,主要抽象不在基于计算机结构,而是基于我们的问题本身。这种思想的实质就是程序可以通过添加新的对象来使得自身去适应某一特定问题。然则,这种OOP思想它仍然与计算机保持着联系:每个对象看起来都有点像一台计算机——它具有状态,还具有操作,用户可以要求对象执行这些操作。
为此Booch对对象提出了一个更加简洁的描述:对象具有状态、行为和标识。这就意味着每个对象都可以拥有内部数据(它们决定了该对象的状态)和方法(它们将产生什么样的行为),并且每个对象都有唯一的标识,也就是说每个对象在内存中都是唯一的。
对象是唯一的,可他们之间总有些相近的特性与行为,例如鲨鱼和草鱼它们都是属于鱼类,但他们却不是同一对象。为此,在面向对象编程的思想中,引了类似理念——在程序执行期间具有不同状态而其他方面都相似的对象都会被分组到对象类中,这就是class的由来。
Class(类)由于是描述了具有相似特性(内部数据元素)和行为(功能)的对象的集合,所以类实际上就是一个数据类型。而创建这种抽象的数据类型是面向对象程序设计的基本概念之一。你可以创建某一类型的变量(在面向对象中,称其为实例或对象),然后操作这些变量(称其为发送消息和请求)。
所以,尽管我们在面向对象程序设计中实际上进行的是创建新的数据类型,但事实上所有的面向对象程序设计语言都是使用class来表示数据类型。二者的区别在于,程序员通过定义类来适应问题,而不是再被迫只能使用现有的用来表示机器中的存储单元的数据类型。即:我们可以自由而灵活地定义我们所需的数据类型。
虽说面向对象方法如此具有自由和通用性,其应用可以将大量地问题转化为简单的解决方案。一旦类被建立,就可以随心所欲地创建类的任意个对象,然后去操作它们,就像它们就是实际待求解问题中的元素一样。但事实上,面向对象程序设计的挑战之一,就是在于问题空间中的待解元素与我们解决问题空间对对象之间的一一映射。
故此,我们会产生一个问题:怎么样才能获得有用的对象呢?解决这个问题,我们可以从对象的意义出发,对象产生的意义实际上为了使对象完成某些特定的任务,如完成路径的选定等等。确切地说每个对象都只能满足某些特定请求,而这些请求是由对象的接口所定义的,决定接口的便是类型,类是该接口的一个特定实现。举例来说:一个类型中定义了他的接口是整型,那他所能接受的请求就一定是整型的数据,而接收数据以后的处理行为,只是对于这个请求的一种实现过程,包含这些数据类型以及行为的类,就是这种面向接口的特定实现形式。
接口确定了对某一特定对象所能发出的请求,但是,在程序中必须有满足这些请求的代码,这些代码与隐藏的数据以求构成了实现。从面向过程的观点来看,并不算太难理解:向某个对象“发送消息”(产生请求),这个对象便知道此事的目的,然后执行对应的代码。
我们举一个开电视的例子来描述下这个过程,首先,我们类型/类的名称是TV,我们这时需要创建一个特定的专属名称,不然我们在多个引用同时进行时,无法指定操作。接着我们可以向TV对象发出请求:打开它、关闭它。
你将以下列方式创建了一个TV对象:定义这个对象的“引用”(It),然后调用new方法来创建该类型的新对象。为了向对象发送消息,我们需要声明对象名称,并以圆点符号连接一个消息请求。
TV It = new Light();
It.on();
最后,既然面向对象的思想是基于问题本身,那么它必然会产生以下几点思考:
1、 如果我可以将问题从表象中抽取出来,那么什么样的对象可以马上解决我的问题?(最简单的就说输入什么,输出什么,一步解决)
2、 我所需要的对象中的某些是否已经存在,如果不存在它们应该看起来像什么样子?
3、 所需对象它们能够提供什么样的服务?
4、 它们需要哪些对象才能履行它们的义务?(对象需要接收什么样的请求)
图2.1 面向思想导论
三、 构建自己的应用
3.1 需求:
构建一个简单猜字谜游戏1.0。要求:
- 猜数字范围在1~100的整数。
- 每个用户有十次机会。
3.2 面向对象如何把握
如图3.2所示,由于JAVA的编程思想是面向对象。换句话说,也就是抽象是问题本身,所以决定了思考方向——面向需求。
而在这其中我们从3.1中可以提取到几点关键需求。我们首先需要考虑为玩家提供一个交互平台(input-output,I/O),让用户可以在控制上输入数字,以及可以考虑游戏的反馈信息。接下来,我们需要模拟随机数生成器,用来产生我们需要的随机数数值。
现在假设我们已经可以得到1~100之间的随机数,那么下面就需要实现游戏规则。我们需要诸如while和if的条件语句用以描述规则。
规则:用户每个人有十次机会猜测一个1~100之间的随机整数。
最后,我们还需要一个计数器用来统计用户的操作次数。对了,还有最重要的一件事,用户的输入是否符合规则。
为此,我们就可以抽象出四个部分:
1、 一个可以读写控制台的接口。
2、 一个生成1~100之间随机数的生成器。
3、 一个比较用户输入数据是否正确且与生成器生成数字相等的比较器。
4、 一个循环计数器。
在对象定义出来以后,我们需要定义一下数据结构即在程序中定义数据的类型和存储方式。因为在这个程序中我们需要的是整数变量,所以我们都定义成int类型:
- int类型变量num:用于记录用户猜测数字的总次数。
- int类型数据temp:用于存储数字生成器生成的数字。
- int数据类型input:用于储存用户输入数据。
末了,我们开始定义功能模块即生成器模块(generate())、I/O交互模块(Scanner())和判断比较循环模块。
3.2 程序思维流程
3.3 算法流程
3.4 具体实现代码
package NumGame;
import java.util.Scanner;
public class NumGame {
/**
* 一个简易的猜数字游戏
* @param num 表示用户猜测次数。
* @param temp 存储生成器生成的随机数
* @param input 用户输入数字
*/
public static void main(String[] args) {
int num = 0;
Scanner s = new Scanner(System.in);
int temp = generate();
System.out.println("欢迎来参加猜数字游戏!");
//游戏比较循环功能主体结构
while (num<=10){
//判断用户剩余次数。
if(num == 10){
System.out.println("您已经超过了10次机会!请重新来过。");
break;
}
//接收输入并判断输入是否符合规则
System.out.println("你还有"+(10-num)+"次!请输入你的数字(1~100):");
int input = s.nextInt();
if (input<1||input>100){
System.out.println("请重新开始!输入正确的数字(1~100)!");
break;
}
//比较用户输入数字与生成的随机数
else if(input > temp){
System.out.println("您输入的数字大了!");
num=num+1;
}
else if(input < temp){
System.out.println("您输入的数字小了!");
num=num+1;
}
else if(input == temp){
System.out.println("您赢了!谢谢参与~~");
break;
}
}
s.close();
}
/**
* 生成(1~100)之间的随机数
* @param return 存储随机数字
*/
private static int generate() {
int temp = (int)(Math.random()*100+1);
return temp;
}
}