封装的初次见面和简单实践
(备注:以下内容仅为个人的学习见解,如有错误欢迎指正!)
大家好,我是aty!从今天起开始不定期在这里分享自己学习Java的经验。一方面总结自己所学,另一方面希望这些经验可以给到同样在学习java的同学们一些帮助。
最近几天学习了java面向对象编程中一个很重要的思想——封装。
从个人的理解来看,封装的作用主要体现在这几个方面:
- 隐藏了对象的属性,外界不知道具体的实现细节,但提供了公共的访问方式;
- 提高了代码的安全性(这一点主要体现在企业或者公司的某些项目开发中);
举个例子:
小明去食堂买早餐,点了一碗牛肉粉。他不会去关注这碗牛肉粉是如何做出来的,他的需求仅仅只是填饱肚子。店家做牛肉粉的过程小明也无法得知,比如放了多少盐、多少辣椒、牛肉来源地等等,店家最终呈现给小明的是一碗粉。
若把牛肉粉看做一个对象,牛肉粉的盐、辣椒、牛肉来源地等等可以看成是它的属性,制作的过程(烧水、下粉、捞起……)看成是它的行为(方法),那么在期间,小明是无法得知这些东西的。
也就是说,牛肉粉对象将自己的属性和方法隐藏了起来,小明不知道店家是如何做出来的,但小明可以在柜台对服务员说“要一碗牛肉粉”或者外卖下单的方式来吃到这一碗粉。
今天来给大家分享一下本次运用封装思想做出来的一个简单实例——珍爱网!
珍爱网——封装思想的简单实践
首先我们来简单描述一下珍爱网要实现的功能:
1. 珍爱网面向的是所有人,不管是帅哥还是美女;
2. 实现登录、注册、匹配、退出四个大功能;
3. 登录:查询系统中是否有和用户同样名字的人,有表示登录成功,没有无法登陆;
4. 查询:查看系统中已有的用户信息;
4. 注册:通过控制台输入信息进行注册,每注册一个都将其存入系统中;
5. 匹配:输入需要找对象的人名,在系统中为他优先寻找有缘人(两个人首字符相差520,表示有缘分),否则系统随机匹配;
6. 退出:退出系统,结束程序;
到底是如何实现简单的封装的呢?下面来通过代码来讲解一下!
代码实现
-
第一部分是创建本程序中的三个基本的类,其中People类为主类,Beauty类和Hunks类是它的子类。这里是继承的简单应用。
创建一个People类 People.java
public class People{
//三个基本属性
String sex;//性别
String name;//姓名
int age;//年龄
//自我介绍
public void info() { }
//展示用户信息
public void show() { }
}
创建一个Beauty类表示女性,作为People类的子类,继承了People类的属性和方法,并对方法进行重写
Beauty.java
public class Beauty extends People{
//有参数的构造方法
Beauty(String sex,String name,int age){
this.sex = sex;
this.name = name;
this.age = age;
}
//重写了People类的info方法
public void info() {
System.out.println("性别:" + this.sex + "\t姓名:" + this.name + "\t年龄:" + this.age);
}
//重写了People类的show方法
public void show() {
System.out.println("Hello,大家好!我是" + this.name + "今年" + this.age + "岁!");
}
}
创建一个Hunks类表示男性,作为People类的子类,继承了People类的属性和方法,并对方法进行重写
Hunks.java
public class Hunks extends People{
//有参数的构造方法
Hunks(String sex,String name,int age){
this.sex = sex;
this.name = name;
this.age = age;
}
//重写了People类的info方法
public void info() {
System.out.println("性别:" + this.sex + "\t姓名:" + this.name + "\t年龄:" + this.age);
}
//重写了People类的show方法
public void show() {
System.out.println("Hello,大家好!我是" + this.name + "今年" + this.age + "岁!");
}
}
-
第二部分我打算先给大家看看主类!
MainClass.java
public class MainClass {
public static void main(String[] args) {
//1. 定义对象型数组,为了便于测试,已经初始化了数组,长度为2
// 该数组的元素皆为上转型对象
People [] ps = {new Beauty("女","王小红",18),new Hunks("男","马小明",22)};
System.out.println("欢迎来到珍爱网——寻找你的那个他!");
System.out.println("-*-*-*-*-*-*-*请先登录*-*-*-*-*-*-*-*-");
//2. 登录界面
Login.loginUp(ps);
//3. 操作界面
System.out.println("\t请选择您想要执行的操作");
System.out.println("\t1-查询所有用户信息\n"
+ "\t2-注册\n"
+ "\t3-系统自动匹配\n"
+ "\t4-退出");
//4. 实现具体操作
Operation.operation(ps);
}
}
你没看错,MainClass类中确实只有这么寥寥几行代码,而且完完全全可以实现前面所说的各种功能。
这里为大家做一个简单的解释:
(1) 对象型数组People [ ] ps与普通数组无异,只不过是把当中的元素换成了对象而已。
在上述代码中,可以看到ps数组中的元素是一个Beauty类对象和一个Hunks类对象。单独列出来可以这么写:People ps = new Beauty( )。这里的意思是父类对象(People类)ps是子类(Beauty类)的上转型对象,只不过我们将这个对象存放在了数组中而已,本质上还是上转型对象,这是多态的一种形式。而使用数组是便于之后的一系列操作。
(2) 回到上面那个例子,小明只知道自己吃的是牛肉粉,不是羊肉粉猪肉粉,但他不知道这碗牛肉粉是如何制作的。在MainClass类中也是如此。
(3) 这里面的登录和操作功能的实现依靠的是另外两个类,即Login类和Operation类。而我们最终执行程序后发现用户既不知道执行的过程,也不知道当中包涵哪些属性。这是因为这些对用户而言被隐藏了,换言之就是将这些功能封装了起来,用户看到的只是功能执行后的结果。
-
接下来是第三部分,主要功能的实现
登录界面Login类 Login.java
import java.util.Scanner;
public class Login {
static boolean success;//记录登录状态
static Scanner sc = new Scanner(System.in);
//请记住这个静态方法!
public static void loginUp(People ps[]) {
//系统提供三次登录机会,用尽则退出程序
for(int i = 0;i < 3;i ++) {
String name = sc.next();
//由于静态方法不能直接调用非静态方法,所以我们可以先创建一个本类对象(Login类),通过对象来调用非静态方法
//可以这么写:Login login = new Login();login.signUP(ps,name);
if(new Login().signUp(ps,name)) {
System.out.println("-*-*-*-*-*-*-*登录成功-*-*-*-*-*-*-*-");
break;
}
else {
System.out.println("对不起,查无此人!请重新输入!");
}
if(i == 2) {
System.out.println("您的三次登录机会已经耗尽,即将退出程序!");
System.exit(0);
}
}
}
//若在p中查询到该用户名,返回登陆成功,否则返回失败
public boolean signUp(People p[],String name) {
for(int i = 0;i < p.length;i ++) {
success = p[i].name.equals(name) ? true : false;
if(success == true)
break;
}
return success;
}
}
这是实现登录功能的Login类。
操作界面Operation类
Operation.java
import java.util.Scanner;
public class Operation {
static Scanner sc = new Scanner(System.in);
//请记住这个静态方法!
public static void operation(People ps[]) {
boolean flag = true;
while(flag) {
//用户输入一个数字,通过switch语句来选择对应的操作
int t = sc.nextInt();
switch(t) {
case 1 :
System.out.println("您的选择是:" + t + "-查询所有用户信息");
Query.query(ps);
break;
case 2 :
System.out.println("您的选择是:" + t + "-注册");
ps = Register.register(ps);
break;
case 3 :
System.out.println("您的选择是:" + t + "-系统自动匹配");
Match.match(ps);
break;
case 4 :
System.out.println("您的选择是:" + t + "-退出");
break;
default: break;
}
if(t == 4) {
System.out.println("*********退出系统,欢迎下次使用!**********");
break;
}
}
}
}
这是实现操作界面的Operation类,可以看到在switch语句中多次通过"类名.静态方法"的方式实现了相应的功能。
查询界面Query类
Query.java
public class Query {
//输出用户信息
public static void query(People p[]) {
System.out.println("***************用户信息如下****************");
for(int i = 0;i < p.length;i ++) {
//前面提到过对象数组p中的元素都是上转型对象,自然就可以调用子类中重写的父类方法
p[i].info();
}
System.out.println("***************退出查询界面****************\n");
}
}
这是实现查询功能的Query类。遍历对象数组p,输出其信息。
注册界面Register类
Register.java
import java.util.Arrays;
import java.util.Scanner;
public class Register {
static Scanner sc = new Scanner(System.in);
//请记住这个静态方法!
public static People[] register(People p[]) {
String sex;
String name;
int age;
String flag = "是";
System.out.println("*****************注册界面******************");
while(flag.equals("是")) {
System.out.print("请输入性别:");sex = sc.next();
System.out.print("请输入姓名:");name = sc.next();
System.out.print("请输入年龄:");age = sc.nextInt();
//首先输入性别,若是男性则初始化Hunks对象,并存放在对象数组p中
if(sex.equals("男")) {
p = Arrays.copyOf(p,p.length + 1);
p[p.length-1] = new Hunks(sex,name,age);
}
//若是女性则初始化Beauty对象,并存放在对象数组p中
if(sex.equals("女")) {
p = Arrays.copyOf(p,p.length + 1);
p[p.length-1] = new Beauty(sex,name,age);
}
System.out.print("注册成功!是否继续:");
flag = sc.next();
}
System.out.println("***************退出注册界面****************\n");
return p;
}
}
这是实现注册功能的Register类。通过控制台输入基本信息,并通过性别的不同分别创建Hunks对象的上转型对象和Beauty对象的上转型对象,说得通俗点就是男女分类统计。设置一个while循环可以注册多个用户,每注册一个都将数组扩容。
匹配界面Match类
Match.java
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
public class Match {
static Scanner sc = new Scanner(System.in);
//1. 匹配界面
//请记住这个静态方法!
public static void match(People p[]) {
System.out.println("*****************匹配界面******************");
new Match().judge(p);
System.out.println("***************退出匹配界面****************\n");
}
//2.在对象数组p中找对应的名字,找到则返回
public People get(People p[],String name) {
int i;
for(i = 0;i < p.length;i ++) {
if(name.equals(p[i].name))
break;
}
return p[i];
}
//3. 判断输入对象是男性还是女性
public void judge(People p[]) {
String name;
System.out.print("请输入姓名:");
name = sc.next();
//在对象数组p中找对应的名字
People p0 = get(p,name);
//找到则输出个人信息
p0.show();
//建立两个对象数组
People wm[] = new People[0];//wm用来存放p中的女性
People m[] = new People[0];//m用来存放p中的男性
for(int i = 0;i < p.length;i ++) {
if(p[i].sex.equals("女")){
wm = Arrays.copyOf(wm,wm.length + 1);
wm[wm.length-1] = p[i];
}
if(p[i].sex.equals("男")){
m = Arrays.copyOf(m,m.length + 1);
m[m.length-1] = p[i];
}
}
//Query.query(m);Query.query(wm);
//3.1 若输入对象是男性,则在女性人员wm中寻找有缘人
//3.2 若输入对象是女性,则在男性人员m中寻找有缘人
//3.3 符合条件的输出配对成功,否则系统随机匹配
if(p0.sex.equals("男")) {
judgement(p0,wm);
}else if(p0.sex.equals("女")) {
judgement(p0,m);
}
}
//4. 若是有两人符合要求,则喜结良缘
// 否则系统随机分配
public void judgement(People p1,People p2[]) {
boolean loop = false;//记录是否配对成功,是则为true,否则为false
for(int i = 0;i < p2.length;i ++) {
if(p1.name.charAt(0) - p2[i].name.charAt(0) == 520 || p2[i].name.charAt(0) - p1.name.charAt(0) == 520) {
p2[i].show();
System.out.println("恭喜二位喜结良缘,配对成功!");
loop = true;
break;
}
}
//随机匹配
if(loop == false) {
Random r = new Random();
int ran = r.nextInt(p2.length);
System.out.println("-------------系统已为你随机匹配!------------");
p2[ran].show();
System.out.println("恭喜" + p1.name + "和" + p2[ran].name + "随机配对成功!");
}
}
}
这是实现匹配功能的Match类,比其他几个类显得复杂一点。
代码大概就是这些,接下来将一一分析上述代码是如何实现封装的。
分析封装
学习之初我有个不成熟的想法:为什么要多此一举创建另一个类,而不是直接在MainClass类中直接将登录功能的代码写出来呢?我确实这样做了,将登录、操作、查询等等功能全部写在了MainClass类中。
最初写的登录功能就是判断系统中有无这个人而已,后来我想扩展一下功能,加入了“若三次登录失败则退出系统”的限制,于是我在MainClass类中疯狂添加代码,加了一个循环和if之后我发现,woc这段代码为什么这么长?
当然我没放在心上,于是继续写。写到操作最为复杂的匹配功能时我写不下去了,因为此时的代码加上注解已经很长很长了。我习惯写一个功能运行一下,于是哪里出现错误我都要费好大得劲返回去一句一句排查,耗费了大量时间之后发现原来只是在判断时将“==”写成了“=”。。。代码量太多,第二天接着写的时候差点把自己看晕了……
之所以说一段不堪回首的糗事,是为了强调这种将大量代码挤在一个类或者一个方法中的方式十分不可取。
于是我改进了代码。将每个功能提取出来写在另一个类中,MainClass类中的代码就变得清晰又简介,而这恰恰也是用户可以看到的。如果他们看到的是一行行又臭又长的英文字符加标点肯定是没有看下去的欲望……
将功能写好后,如何用MainClass来调用它们?
在上述功能型类中有一个共同特点,都有一个static定义的方法,即静态方法。
静态方法可以直接通过“类名.静态方法名”的方式直接调用,不需要额外创建一个对象
就比如MainClass.java中的:
Login.loginUp(ps);
Operation.operation(ps);
我们只知道这里实现了呈现登录界面和操作界面的功能,却不知道它是如何实现的,而具体的实现过程被我放在了Login类和Operation类中封装了起来,对外隐藏。同样的例子还在Operation类中也有体现。
说个浅显的例子:
public class People{
String sex = "男";
}
class Test{
public static void main(String [] agrs){
People p = new People();
System.out.println(p.sex);
}
}
在主方法中我们输出People对象p的性别,在输出语句中直接将该属性暴露在外,这样一来大家就知道你是如何做到的。
但如果将代码改进一下:
public class People{
String sex ;
public void setSex(String name){
this.name = name;
}
public String getSex(){
return this.name;
}
}
class Test{
public static void main(String [] agrs){
People p = new People();
p.setSex();
System.out.println(p.getSex());
}
}
虽然代码比上面的略显复杂,但我们很好地将过程隐藏了起来,要是实现更将复杂且机密的功能肯定是使用第二种方法更为合适。
这就是一种简单的封装,跟珍爱网这个实例大同小异。
总结一下:
- 封装时会经常用到static关键字。冠以static名的被称为静态方法或者静态变量,也叫作类方法或类变量。他们可以通过类名直接访问,是实现封装的有效手段;
- 在珍爱网这个实例中体现出了封装的好处:
(1) 使隐藏了方法的细节;
(2) 用户只需要通过我们给定的访问方式即可满足需求;
(3)便于检查、修改、维护代码,不至于牵一发而动全身,对整个代码大刀阔斧地修改。
备注: 本代码中还有着些许不足,本人学习至今能力有限,只能之后慢慢完善。