一、 引言
本章将第一篇中的猜字谜游戏1.0进行升级,然后向读者简单介绍JAVA中封装思想的部分知识以及其实现思路。
二、封装思想导论
在面向对象的编程思想中提出了三大基本特征:封装、继承和多态。其中,封装是继承与多态的基础。如果不能很好的理解封装的含义,那么继承和多态的学习也无从谈起。所以,为了更好的理解封装思想,我们将解决以下几个问题(如图2.1):
- 封装是什么?
- 我们为什么需要封装?
- 封装的特点?
- 封装如何实现?
封装的思想,从起源来看,已经很久了。早在亚里士多德时期,他就有关于类型的著述。而这个类型,就是封装思想的起源。也就是说把具有相似行为和属性的对象抽象划分为一组,而这种对象的归并过程,我们称之为封装。
我们只需要抽出他们的共有部分进行运用。在JAVA中称之为被封装的模块,其中的典型就是class(类)。
这种思想的好处在于,在基于根据问题描述问题的基础上,提出了可行的方法论指导思想。也就是怎么做的问题。他的思想核心就是拆分,把一个很大的问题拆分成很多细小的问题,但是这里要注意一点:这个不是无限制拆分,他的目的性只是为了让问题能更有效率的解决。所以,我们拆分的度就是在于我们可以通过短时间的研究和学习就能解决他。
既然,封装的思想只是把大问题拆成小问题,那么,基于此我们可以将程序员分为两类——封装对象的创建者以及封装对象的使用者。
从分工的目的上看,使用者的目的只是收集各种用来实现快速应用开发的类,他们所需要的是这些东西的功能而不是实现。而创建者却与之相反。
这就造成了一个矛盾,类中对象是否可以随意访问。答案是否定。
在任何相互关系中,具有关系所涉及的各方面都遵守的边界是十分重要的事情。如果所有的类中成员对任何事可用的话,那么,类的使用者就可以对类做出任何事情,而不加约束。这就是很多程序在使用时产生的bug的一个重要的原因——使用者的输入不合法,而作为程序的创建并没有做出相关的应对,即强硬的拒绝(拒绝任何的非法输入)。
为什么我们需要有强硬的拒绝机制呢?在实际中,我们会遇到种种,如果选择无视他,往往会造成灾难性后果,这方面忽视的例子是屡见不鲜的。例如地震的预兆以及重病的发展。所以,我都有种希望有个机制来解决任何所遇到的问题,这个在编程上就称之为过度优化。但是,实际上是不可能的,也很难完成。因为问题这个概念太宽泛,而且涉及到具体情境。所以,在我们不知道具体情境情况下,我们应该设定情境,画出范围。范围的确立,就代表了拒绝,什么可以输入,什么不可以。这也就是在封装思想中,另一个重要的概念——确立范围。
所以,创建者需要对代码中的某些部分做出隐藏和访问限制。
在这里面,访问控制除了隐藏使用者不该接触的部分——这部分是针对数据类型的内部操作,但并不是使用者解决特定问题所需的接口的一部分。(归而言之,就是这是对使用者来说这仅仅只是一个服务,因为他们可以很容易地看出那些东西对他来说很重要,而哪些又可以忽略。)
其还有第二个存在原因,这是在创建者的立场上的。创建者希望可以通过少量的改变而达到最大的目的。如果,允许创建者可以改变内部的工作方式而不用担心会影响到使用者。那真是个好方法。例如,我发现更改某一特定的封装对象——类(Class),可以提高这个程序对象的运行效率。这时,如果接口和实现可以清晰地分离并得以保护,那么你就可以轻而易举地完成这项工作。
因此,JAVA在界定内部边界时候,做出了public(公共)、private(私有)、protected(保护)这三个定义的划分。
这些访问指定词决定了紧随其后被定义的东西可以被谁使用。
- Public:公共部分,任何人都可以使用。
- Private:私有部分,他就好像自家私有的花园一样,除了类型创建者和类型的内部方法之外任何人都不能访问这个元素。(为了达成这个目的,所以他在结尾都是要加上return(返回值),不然调用得人根本就不会知道你的回复是什么。)
- Protected:保护类型,这个专用于继承,以后在介绍。他与Private的区别仅在于继承类是可以访问它的。
最后,还有一种默认的访问形式,叫包访问权限。如果前面没有使用以上的任何访问指定词时,他将发挥作用,类可以访问在同一个包中的其他类成员,但是在这范围以外,其成员除了类的创建者与其自身的内部方法可以访问以外,其余的都会拒绝,说白了就是放大版的Private类型。
紧接着,我们的模块已经按照访问权限设计好了,那么与之而来的会产生一些需求——我们希望我们的创建应当是个有用的代码单元,他能反复的调用,而这种调用最简单的方法就是创建它。这种方式也称之为复用。虽然,事实上有时这种复用会不尽乎人意。
由于新的模块,可以有任意数量、任意类型的其他对象以任意的方式组成。但是,组成的方式有静态和动态两个方面。其中,静态的称之为组合,动态的称之为聚合。
由于复用能带来极大的灵活性。新模块的成员对象也通常声明为private,使得使用者不能访问他们,减少了bug。这也使得你可以在不干扰主程序代码的情况下,修改他们。当时,在程序运行时,也可以被修改。从复用的特点上看,它与继承的概念很像,可是他更简单更加小巧。所以,在建立新的模块时候,优先考虑使用它,而关于继承的概念和其实现,我们以后在讨论。
图2.1 封装思想导论
三、构建自己的应用
3.1 需求:
修改猜字谜游戏1.0,令他变成制作一个具有不同等级的猜字谜游戏2.0:与用户输入字母进行匹配,全对则表示用户胜利。
3.2 封装思想具体实现
如图3.2所示,我们需要以下几个模块:
- 需要一个可以读写控制台的接口。
- 需要一个字母生成器。
- 需要一个可以匹配用户输入数据与生成器生成字母的匹配器(匹配器具有判断用户匹配正确了多少个字母的机制)。
- 需要一个循环计数器(目的是提醒用户剩余次数并统计循环次数)。
其中,我们需要改变的是1.0中随机数生产器,把它改为字母生产器。并把1.0中的比较模块,单独提了出来封装以后,更改其功能为匹配器。
程序数据结构:
1、char类型数组input:用于保存用户猜测字母数据。
2、char类型数组temp:用于保存字母生成器生成的字母。
3、int类型数组result:用于保存判断结果。(该数组含有两个元素,第一个元素用于保存完全猜对的字母个数,第二个元素用于保存用户猜对字母个数(字符正确但位置不正确))。
4、int类型变量num:存储用户进行游戏的总次数。
图3.2 猜字谜游戏2.0
3.3 算法流程
四、具体实现
package AbcGame; import java.util.Scanner; public class AbcGame { /** * 猜字谜程序2.0 * @param input 用于保存用户猜测字母数据 * @param temp 用于保存字母生成器生成的字母 * @param result 用于保存判断结果 * @param num 存储用户进行游戏的总次数 */ public static void main(String[] args) { // 主程序目录 char[] input = null; char[] temp = generate(); int[] result = new int[2]; int num = 0; Scanner s = new Scanner(System.in); System.out.println("欢迎您参加游戏!"); System.out.println("游戏开始!请输入五个不同的字母:(exit——退出,您的生命值为"+(temp.length*100)+",每错一次扣10分)"); while(true){ String inputStr = s.next().trim().toUpperCase(); if("EXIT".equals(inputStr)){ System.out.println("谢谢参与,再见!"); break; } input =inputStr.toCharArray(); for (int k = 0;k < input.length;k++){ for (int u = 0;u < input.length;u++){ if(k != u && input[k] == input[u]){ System.out.print("请重新开始且输入正确的五位不同字母!!"); System.exit(0); //程序正常结束。 } } } result = check(temp,input); if (result[0] == temp.length){ System.out.println("恭喜你猜对了!你的得分是"+(temp.length*100-num*10)); break; } else { num++; System.out.println("你猜对"+result[1]+"个字符,其中"+result[0]+"个字符正确!"); System.out.println("您的生命值还剩下"+(temp.length*100-num*10)); if((temp.length*100-num*10) == 0){ System.out.println("望下次再接再厉,谢谢参与!"); System.exit(0); } } } s.close(); } /** * 字母匹配器 * @param letters 26个字母的保存空间 * @param temp 保存随机生成的字母 * @param flags 用以储存字母位置是否被选中。 * @param index 用以保存随机生成的位置数 */ private static char[] generate() { // 字母生成器 char[] letters={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'}; char[] temp =new char[5]; boolean[] flags = new boolean[letters.length]; for (int i = 0;i<temp.length;i++){ int index; do{ index = (int)(Math.random()*(letters.length)); }while(flags[index]);//判断生成字母数是否重复 temp[i] = letters[index]; flags[index] = true; } return temp; } /** * 字母匹配器 * @param input 用户猜测字母数据 * @param temp 字母生成器生成的字母 * @param result 用于保存判断结果 */ private static int[] check(char[] temp, char[] input) { // 字母匹配器 int[] result = new int[2]; for (int i = 0;i < input.length;i++){ for (int j=0;j < temp.length;j++){ if(input[i] == temp[j]){ //判断用户输入字母是否正确 result[1]++; if(i==j){ //判断用户输入的正确字母是否位置正确 result[0]++; break; } } } } return result; } }