8.1 抽象和封装
面向对象设计首先要做的就是抽象。根据用户的业务需求抽象出类,并关注这些类的属性和方法,将现实世界中的对象抽象成程序设计中的类。接下来分析一下“租车系统”的部分需求。
(1)在控制台输出“请选择要租车的类型:(1代表轿车,2代表卡车)”,等待用户输入。
(2)如果用户选择的是轿车,则在控制台输出“请选择轿车品牌:(1代表红旗,2代表长城)”,等待用户输入。
(3)如果用户选择的是卡车,则在控制台输出“请选择卡车吨位:(1代表5吨,2代表10吨)”,等待用户输入。
(4)在控制台输出“请给所租的车起名:”,等待用户输入车名。
(5)所租的车油量默认值为20升,车辆损耗度为0(表示刚保养完的车,无损耗)。
(6)具有显示所租车辆信息功能,显示的信息包括:车名、品牌/吨位、油量和车损度。
8.1.1 类抽象
程序员开发出来的软件是需要满足用户需求的,所以程序员做分析和设计的依据是用户需求,通常是软件开发前期形成的“需求规格说明书”。面向对象设计时,首先要阅读用户需求,找出需求中名词部分用来确定类和属性,找出动词部分确定方法。
首先要进行类抽象,就是发现类并定义类的属性和方法。具体的步骤如下。
(1)发现名词。
通过阅读需求,发现需求中有类型、轿车、卡车、品牌、红旗、长城、吨位、车名、油量、车损度等名词。
(2)确定类和属性。
通过分析,车名、油量、车损度、品牌这些名词依附于轿车这个名词,车名、油量、车损度、吨位依附于卡车这个名词,所以可以将轿车、卡车抽象成类,依附于这些类的名词抽象成属性。
需要补充一点,不是所有依附于类的名词都需要抽象成属性,因为在分析需求的过程中会会发现其中某些名词不需要关注,则在抽象出类的过程中放弃这些名词,不将其抽象成属性。例如红旗、长城,这是两个轿车的品牌,属于属性值,不需要抽象成类或属性。
(3)确定方法。
通过分析需求的动词,发现显示车辆信息是轿车和卡车的行为,所以可以将这个行为抽象成类的方法。同样地,不是所有依附于类名词的动词都需要抽象成类的方法,只有需要参与业务处理的动词才能确定成方法。
根据对轿车和卡车的类抽象,可以得到如图8.1和图8.2所示的结果。
8.1.2 类封装
类抽象的目的在于抽象出类,并确定属性和方法,而接下来的类封装,则要在封装的角度隐藏类的属性,提供公有的方法来访问这些属性。
最简单的操作方法就是,把所有的属性都设置为私有属性(表示私有属性和方法时,需在类图中的属性和方法前加上“-”号),每个私有属性都提供getter和setter公有的方法(表示公有属性和方法时,需在类图中的属性和方法前加上“+”号),封装后的类图如图8.3和图8.4所示,在类图中设定了类的成员变量的初始值。
这样的封装过于简单,没有考虑需求,接下来进一步阅读需求,可以发现以下几点。
(1)租车时可以指定车的类型和品牌(或吨位),之后不允许修改。
(2)油量和车损度租车时取默认值,只有通过车的加油和行驶的行为改变其油量和车损度值,不允许直接修改。
根据需求,应对轿车类和卡车类做如下修改。
(1)去掉所有的setter方法,保留所有的getter方法。
(2)提供addOil()、drive()这两个公有的方法,实现车的加油和行驶的行为。
(3)至少需要提供一个构造方法,实现对类型和品牌(或吨位)的初始化。
调整后的类图如图8.5和图8.6所示。
封装后的Car类代码如下所示,具体内容看注释。
package com.bd.zuche;
//轿车类
public class Car
{
private String name = "飞箭"; //车名
private int oil = 20; //油量
private int loss = 0; //车损度
private String brand = "红旗"; //品牌
//构造方法,指定车名和品牌
public Car(String name,String brand){
this.name = name;
this.brand = brand;
}
//显示车辆信息
public void show()
{
System.out.println("显示车辆信息:\n车辆名称为:" + this.name + " 品牌是:" + this.brand + "油量是:" + this.oil + " 车损度为:" + this.loss);
}
//获取车名
public String getName()
{
return name;
}
//获取油量
public int getOil()
{
return oil;
}
//获取车损度
public int getLoss()
{
return loss;
}
//获取品牌
public String getBrand()
{
return brand;
}
//加油
public void addOil()
{
//加油功能未实现
}
//行驶
public void drive()
{
//行驶功能未实现
}
}
封装后的Truck类代码如下所示。
package com.bd.zuche;
//卡车类
public class Truck
{
private String name = "大力士"; //车名
private int oil = 20; //油量
private int loss = 0; //车损度
private String load = "10吨"; //吨位
//构造方法,指定车名和品牌
public Truck(String name,String load){
this.name = name;
this.load = load;
}
//显示车辆信息
public void show()
{
System.out.println("显示车辆信息:\n车辆名称为:" + this.name + " 吨位是:" + this.load + "油量是:" + this.oil + " 车损度为:" + this.loss);
}
//获取车名
public String getName()
{
return name;
}
//获取油量
public int getOil()
{
return oil;
}
//获取车损度
public int getLoss()
{
return loss;
}
//获取吨位
public String getLoad()
{
return load;
}
//加油
public void addOil()
{
//加油功能未实现
}
//行驶
public void drive()
{
//行驶功能未实现
}
}
将之前《租车系统》的需求总结如下。
(1)在控制台输出“请选择要租车的类型:(1代表轿车,2代表卡车)”,等待用户输入。
(2)如果用户选择的是轿车,则在控制台输出“请选择轿车品牌:(1代表红旗,2代表长城)”,等待用户输入。
(3)如果用户选择的是卡车,则在控制台输出“请选择卡车吨位:(1代表5吨,2代表10吨)”,等待用户输入。
(4)在控制台输出“请给所租的车起名:”,等待用户输入车名。
(5)所租的车油量默认值为20升,车辆损耗度为0(表示刚保养完的车,无损耗)。
(6)具有显示所租车辆信息的功能,显示的信息包括车名、品牌/吨位、油量和车损度。
(7)租车时指定车的类型和品牌(或吨位),之后不允许修改。
(8)油量和车损度租车时取默认值,只有通过车的加油和行驶的行为改变其油量和车损度值,不允许直接修改。
按需求完成代码如下,程序运行结果如图8.7所示。
import java.util.Scanner;
import com.bd.zuche.*;
class TestZuChe
{
public static void main(String[] args)
{
String name = null; //车名
int oil = 20; //油量
int loss = 0; //车损度
String brand = null; //品牌
String load = null; //吨位
Scanner input = new Scanner(System.in);
System.out.println("***欢迎来到蓝桥租车系统***");
System.out.print("请选择要租车的类型:(1代表轿车,2代表卡车)");
int select = input.nextInt();
switch(select)
{
case 1: //选择租轿车
System.out.print("请选择轿车品牌:(1代表红旗,2代表长城)");
select = input.nextInt();
if(select == 1) //选择红旗品牌
{
brand = "红旗";
}else //选择长城品牌
{
brand = "长城";
}
System.out.print("请给所租的车起名:");
name = input.next(); //输入车名
Car car = new Car(name,brand); //使用构造方法初始化车名和品牌
car.show(); //输出车辆信息
break;
case 2: //选择租卡车
System.out.print("请选择卡车吨位:(1代表5吨,2代表10吨)");
select = input.nextInt();
if(select == 1) //选择5吨卡车
{
load = "5吨";
}else //选择10吨卡车
{
load = "10吨";
}
System.out.print("请给所租的车起名:");
name = input.next(); //输入车名
Truck truck = new Truck(name,load); //使用构造方法初始化车名和吨位
truck.show(); //输出车辆信息
break;
}
}
}
8.1.3 方法的实现
在Car类和Truck类的代码中,addOil()方法和drive()方法的功能还没有实现,接下来结合需求,分别完成Car类和Truck类中的这两个方法。
《租车系统》增加了如下需求。
(1)不论是轿车还是卡车,油箱最多可以装60升汽油,每次给车加油,增加油量20升。如果加油20升超过油箱容量时,则加到60升即可,并在控制台输出“油箱已加满!”。
(2)汽车行驶1次,耗油5升,车损度增加10,如果油量低于10升,则不允许行驶,直接在控制台输出“油量不足10升,需要加油!”。
具体实现代码如下所示。
//加油
public void addOil()
{
if(oil > 40) //如果加油20升则超过油箱容量,则加到60升即可
{
oil = 60;
System.out.println("邮箱已加满!");
}else{ //加油20升
oil = oil + 20;
}
System.out.println("加油完成!");
}
//行驶
public void drive()
{
if(oil < 10)
{
System.out.println("油量不足10升,需要加油!");
}else{
System.out.println("正在行驶!");
oil = oil - 5;
loss = loss + 10;
}
}
执行下面的代码,注意观察油量和车损度的变化,程序运行结果如图8.8所示。
import java.util.Scanner;
import com.bd.zuche.*;
class TestZuChe2
{
public static void main(String[] args)
{
Car car = new Car("战神","长城"); //初始化轿车对象car
car.show(); //输出车辆信息
car.drive(); //让car行驶1次,油量剩下15升,车损度为10
car.show(); //输出车辆信息
car.drive(); //让car再行驶1次,油量剩下10升,车损度为20
car.drive(); //让car再行驶1次,油量剩下5升,车损度为30
car.drive(); //让car再行驶1次,因油量不足10升,不行驶,提示需要加油
car.addOil(); //让car加油1次,油量增加20升,达到25升
car.show(); //输出车辆信息
}
}