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 抽象和封装 - 图1
图8.1 轿车类
8.1 抽象和封装 - 图2
图8.2 卡车类

8.1.2 类封装

  类抽象的目的在于抽象出类,并确定属性和方法,而接下来的类封装,则要在封装的角度隐藏类的属性,提供公有的方法来访问这些属性。

  最简单的操作方法就是,把所有的属性都设置为私有属性(表示私有属性和方法时,需在类图中的属性和方法前加上“-”号),每个私有属性都提供getter和setter公有的方法(表示公有属性和方法时,需在类图中的属性和方法前加上“+”号),封装后的类图如图8.3和图8.4所示,在类图中设定了类的成员变量的初始值。

8.1 抽象和封装 - 图3
图8.3 轿车类
8.1 抽象和封装 - 图4
图8.4 卡车类

  这样的封装过于简单,没有考虑需求,接下来进一步阅读需求,可以发现以下几点。

  (1)租车时可以指定车的类型和品牌(或吨位),之后不允许修改。

  (2)油量和车损度租车时取默认值,只有通过车的加油和行驶的行为改变其油量和车损度值,不允许直接修改。

  根据需求,应对轿车类和卡车类做如下修改。

  (1)去掉所有的setter方法,保留所有的getter方法。

  (2)提供addOil()、drive()这两个公有的方法,实现车的加油和行驶的行为。

  (3)至少需要提供一个构造方法,实现对类型和品牌(或吨位)的初始化。

  调整后的类图如图8.5和图8.6所示。

8.1 抽象和封装 - 图5
图8.5 调整后的轿车类
8.1 抽象和封装 - 图6
图8.6 调整后的卡车类

  封装后的Car类代码如下所示,具体内容看注释。

  1. package com.bd.zuche;
  2. //轿车类
  3. public class Car
  4. {
  5. private String name = "飞箭"; //车名
  6. private int oil = 20; //油量
  7. private int loss = 0; //车损度
  8. private String brand = "红旗"; //品牌
  9. //构造方法,指定车名和品牌
  10. public Car(String name,String brand){
  11. this.name = name;
  12. this.brand = brand;
  13. }
  14. //显示车辆信息
  15. public void show()
  16. {
  17. System.out.println("显示车辆信息:\n车辆名称为:" + this.name + " 品牌是:" + this.brand + "油量是:" + this.oil + " 车损度为:" + this.loss);
  18. }
  19. //获取车名
  20. public String getName()
  21. {
  22. return name;
  23. }
  24. //获取油量
  25. public int getOil()
  26. {
  27. return oil;
  28. }
  29. //获取车损度
  30. public int getLoss()
  31. {
  32. return loss;
  33. }
  34. //获取品牌
  35. public String getBrand()
  36. {
  37. return brand;
  38. }
  39. //加油
  40. public void addOil()
  41. {
  42. //加油功能未实现
  43. }
  44. //行驶
  45. public void drive()
  46. {
  47. //行驶功能未实现
  48. }
  49. }

  封装后的Truck类代码如下所示。

  1. package com.bd.zuche;
  2. //卡车类
  3. public class Truck
  4. {
  5. private String name = "大力士"; //车名
  6. private int oil = 20; //油量
  7. private int loss = 0; //车损度
  8. private String load = "10吨"; //吨位
  9. //构造方法,指定车名和品牌
  10. public Truck(String name,String load){
  11. this.name = name;
  12. this.load = load;
  13. }
  14. //显示车辆信息
  15. public void show()
  16. {
  17. System.out.println("显示车辆信息:\n车辆名称为:" + this.name + " 吨位是:" + this.load + "油量是:" + this.oil + " 车损度为:" + this.loss);
  18. }
  19. //获取车名
  20. public String getName()
  21. {
  22. return name;
  23. }
  24. //获取油量
  25. public int getOil()
  26. {
  27. return oil;
  28. }
  29. //获取车损度
  30. public int getLoss()
  31. {
  32. return loss;
  33. }
  34. //获取吨位
  35. public String getLoad()
  36. {
  37. return load;
  38. }
  39. //加油
  40. public void addOil()
  41. {
  42. //加油功能未实现
  43. }
  44. //行驶
  45. public void drive()
  46. {
  47. //行驶功能未实现
  48. }
  49. }

  将之前《租车系统》的需求总结如下。

  (1)在控制台输出“请选择要租车的类型:(1代表轿车,2代表卡车)”,等待用户输入。

  (2)如果用户选择的是轿车,则在控制台输出“请选择轿车品牌:(1代表红旗,2代表长城)”,等待用户输入。

  (3)如果用户选择的是卡车,则在控制台输出“请选择卡车吨位:(1代表5吨,2代表10吨)”,等待用户输入。

  (4)在控制台输出“请给所租的车起名:”,等待用户输入车名。

  (5)所租的车油量默认值为20升,车辆损耗度为0(表示刚保养完的车,无损耗)。

  (6)具有显示所租车辆信息的功能,显示的信息包括车名、品牌/吨位、油量和车损度。

  (7)租车时指定车的类型和品牌(或吨位),之后不允许修改。

  (8)油量和车损度租车时取默认值,只有通过车的加油和行驶的行为改变其油量和车损度值,不允许直接修改。

  按需求完成代码如下,程序运行结果如图8.7所示。

  1. import java.util.Scanner;
  2. import com.bd.zuche.*;
  3. class TestZuChe
  4. {
  5. public static void main(String[] args)
  6. {
  7. String name = null; //车名
  8. int oil = 20; //油量
  9. int loss = 0; //车损度
  10. String brand = null; //品牌
  11. String load = null; //吨位
  12. Scanner input = new Scanner(System.in);
  13. System.out.println("***欢迎来到蓝桥租车系统***");
  14. System.out.print("请选择要租车的类型:(1代表轿车,2代表卡车)");
  15. int select = input.nextInt();
  16. switch(select)
  17. {
  18. case 1: //选择租轿车
  19. System.out.print("请选择轿车品牌:(1代表红旗,2代表长城)");
  20. select = input.nextInt();
  21. if(select == 1) //选择红旗品牌
  22. {
  23. brand = "红旗";
  24. }else //选择长城品牌
  25. {
  26. brand = "长城";
  27. }
  28. System.out.print("请给所租的车起名:");
  29. name = input.next(); //输入车名
  30. Car car = new Car(name,brand); //使用构造方法初始化车名和品牌
  31. car.show(); //输出车辆信息
  32. break;
  33. case 2: //选择租卡车
  34. System.out.print("请选择卡车吨位:(1代表5吨,2代表10吨)");
  35. select = input.nextInt();
  36. if(select == 1) //选择5吨卡车
  37. {
  38. load = "5吨";
  39. }else //选择10吨卡车
  40. {
  41. load = "10吨";
  42. }
  43. System.out.print("请给所租的车起名:");
  44. name = input.next(); //输入车名
  45. Truck truck = new Truck(name,load); //使用构造方法初始化车名和吨位
  46. truck.show(); //输出车辆信息
  47. break;
  48. }
  49. }
  50. }
8.1 抽象和封装 - 图7
图8.7 《租车系统》运行结果

8.1.3 方法的实现

  在Car类和Truck类的代码中,addOil()方法和drive()方法的功能还没有实现,接下来结合需求,分别完成Car类和Truck类中的这两个方法。

  《租车系统》增加了如下需求。

  (1)不论是轿车还是卡车,油箱最多可以装60升汽油,每次给车加油,增加油量20升。如果加油20升超过油箱容量时,则加到60升即可,并在控制台输出“油箱已加满!”。

  (2)汽车行驶1次,耗油5升,车损度增加10,如果油量低于10升,则不允许行驶,直接在控制台输出“油量不足10升,需要加油!”。

  具体实现代码如下所示。

  1. //加油
  2. public void addOil()
  3. {
  4. if(oil > 40) //如果加油20升则超过油箱容量,则加到60升即可
  5. {
  6. oil = 60;
  7. System.out.println("邮箱已加满!");
  8. }else{ //加油20升
  9. oil = oil + 20;
  10. }
  11. System.out.println("加油完成!");
  12. }
  13. //行驶
  14. public void drive()
  15. {
  16. if(oil < 10)
  17. {
  18. System.out.println("油量不足10升,需要加油!");
  19. }else{
  20. System.out.println("正在行驶!");
  21. oil = oil - 5;
  22. loss = loss + 10;
  23. }
  24. }

  执行下面的代码,注意观察油量和车损度的变化,程序运行结果如图8.8所示。

  1. import java.util.Scanner;
  2. import com.bd.zuche.*;
  3. class TestZuChe2
  4. {
  5. public static void main(String[] args)
  6. {
  7. Car car = new Car("战神","长城"); //初始化轿车对象car
  8. car.show(); //输出车辆信息
  9. car.drive(); //让car行驶1次,油量剩下15升,车损度为10
  10. car.show(); //输出车辆信息
  11. car.drive(); //让car再行驶1次,油量剩下10升,车损度为20
  12. car.drive(); //让car再行驶1次,油量剩下5升,车损度为30
  13. car.drive(); //让car再行驶1次,因油量不足10升,不行驶,提示需要加油
  14. car.addOil(); //让car加油1次,油量增加20升,达到25升
  15. car.show(); //输出车辆信息
  16. }
  17. }
8.1 抽象和封装 - 图8
图8.8 《租车系统》测试结果