Java 学习复盘 图书管理系统③:吃透继承与多态,实现项目运行
终于到了图书管理系统的最后一篇前两篇我们完成了实体类设计Book/BookList和核心功能实现IOPeration 接口 七大功能类吃透了封装和接口解耦的思想。这一篇我们将完成用户体系设计user 包和项目整合运行Main 类讲解抽象类的实际应用、多态的核心体现并解决 Scanner 输入的常见问题最终实现整个图书管理系统的完整运行这一篇也是面向对象思想的综合应用篇—— 通过抽象类User抽取管理员和普通用户的共性让两个子类继承并实现个性通过父类引用指向子类对象实现多态让代码摆脱繁琐的 if-else 判断通过接口数组将用户和功能联动实现完整的业务流程。学完这一篇你将彻底能学会封装、继承、接口、多态这四大 Java 面向对象核心思想一、用户体系设计基于抽象类实现权限差异化图书管理系统的核心是区分管理员和普通用户的操作权限管理员有新增、删除、显示图书的权限而普通用户只有借阅、归还图书的权限。这两个用户有很多共性也有各自的个性非常适合用继承来实现 —— 抽象出父类User抽取共性让子类AdminUser和NormalUser继承并实现个性。1. 抽象父类User—— 抽取用户共性定义抽象方法设计思路作为所有用户的父类User需要抽取管理员和普通用户的共性属性和行为同时定义抽象方法让子类实现各自的个性体现 “模板方法” 的设计思想。共性属性用户名name、操作接口数组ioPerations存放用户可执行的功能共性行为执行操作的方法doIoperation()调用功能类的work方法抽象方法菜单方法menu()管理员和普通用户的菜单不同由子类实现。完整可运行代码package user; import book.BookList; import ioperations.IOPeration; public abstract class User { // 受保护属性子类可以直接访问外部不能访问兼顾封装和继承 protected String name; // 操作接口数组存放当前用户可执行的所有功能实现类对象 protected IOPeration[] ioPerations; // 构造方法初始化用户名由子类调用 public User(String name) { this.name name; } // 抽象方法菜单方法由子类根据自身权限实现强制子类的规范性 public abstract int menu(); // 共性行为执行操作根据用户选择的编号调用对应的功能 public void doIoperation(int choice, BookList bookList) { // 核心通过接口数组调用功能类的work方法体现多态 this.ioPerations[choice].work(bookList); } }代码分析个人理解抽象类的定义用abstract修饰类表明这是一个抽象类不能实例化只能被子类继承。受保护属性protected属性用protected修饰而不是private。因为private属性子类无法直接访问public属性会破坏封装protected正好兼顾封装性和继承性这是继承中属性修饰符的最优选择。抽象方法menu()用abstract修饰无方法体强制所有子类必须实现这个方法。这样保证了所有用户都有自己的菜单体现了抽象类的模板规范性—— 父类制定模板子类实现细节。核心共性方法doIoperation()这是用户类的核心方法通过接口数组 索引调用对应的功能类work方法实现了用户和功能的联动。这里不需要关心ioPerations数组中存放的是哪个功能实现类只需调用work方法体现了多态的核心思想。接口数组ioPerations这是用户和功能联动的关键数组的元素类型是IOPeration接口存放的是各个功能实现类的对象后续子类只需在构造方法中初始化这个数组放入自身可执行的功能即可实现了用户权限和功能的解耦。2. 管理员子类AdminUser—— 继承 User实现管理员权限设计思路继承抽象父类User实现menu()方法展示管理员菜单在构造方法中初始化接口数组ioPerations放入管理员可执行的功能退出、查找、新增、删除、显示。完整代码package user; import ioperations.AddOperation; import ioperations.DelOperation; import ioperations.ExitOperation; import ioperations.FindOperation; import ioperations.IOPeration; import ioperations.ShowOperation; import java.util.Scanner; public class AdminUser extends User{ // 构造方法调用父类构造方法初始化用户名初始化管理员功能数组 public AdminUser(String name) { super(name); // 接口数组元素类型是IOPeration存放功能实现类对象体现多态 this.ioPerations new IOPeration[]{ new ExitOperation(), // 0退出系统 new FindOperation(), // 1查找图书 new AddOperation(), // 2新增图书 new DelOperation(), // 3删除图书 new ShowOperation() // 4显示图书 }; } // 实现抽象方法管理员专属菜单返回用户的操作选择 Override public int menu() { System.out.println(欢迎this.name来到图书管理系统管理员); System.out.println(*******管理员菜单*******); System.out.println(1. 查找图书); System.out.println(2. 新增图书); System.out.println(3. 删除图书); System.out.println(4. 显示图书); System.out.println(0. 退出系统); System.out.println(**********************); Scanner scanner new Scanner(System.in); System.out.print(请输入你的操作); int choice scanner.nextInt(); return choice; // 返回用户选择的编号用于调用对应功能 } }代码分析个人理解 构造方法的作用① 通过super(name)调用父类的构造方法初始化用户名这是继承中子类构造方法的强制要求子类必须先初始化父类② 初始化接口数组ioPerations放入管理员可执行的功能实现类对象数组的索引和菜单的编号一一对应0 退出1 查找2 新增等这是后续调用功能的关键。实现抽象方法menu()重写父类的抽象方法展示管理员专属菜单通过 Scanner 获取用户的操作选择并返回。菜单的编号和接口数组的索引严格对应保证了用户选择的编号能正确调用对应的功能。多态的体现接口数组ioPerations的元素类型是IOPeration但存放的是ExitOperation、FindOperation等实现类的对象这是接口的多态—— 父接口引用指向实现类对象。3. 普通用户子类NormalUser—— 继承 User实现普通用户权限设计思路和管理员类思路一致继承抽象父类User实现menu()方法展示普通用户菜单在构造方法中初始化接口数组ioPerations放入普通用户可执行的功能退出、查找、借阅、归还。完整代码package user; import ioperations.BorrowOperation; import ioperations.ExitOperation; import ioperations.FindOperation; import ioperations.IOPeration; import ioperations.ReturnOperation; import java.util.Scanner; public class NormalUser extends User{ // 构造方法调用父类构造方法初始化用户名初始化普通用户功能数组 public NormalUser(String name) { super(name); // 接口数组索引和菜单编号一一对应 this.ioPerations new IOPeration[]{ new ExitOperation(), // 0退出系统 new FindOperation(), // 1查找图书 new BorrowOperation(), // 2借阅图书 new ReturnOperation() // 3归还图书 }; } // 实现抽象方法普通用户专属菜单返回用户的操作选择 Override public int menu() { System.out.println(欢迎this.name来到图书管理系统普通用户); System.out.println(*******普通用户菜单*******); System.out.println(1. 查找图书); System.out.println(2. 借阅图书); System.out.println(3. 归还图书); System.out.println(0. 退出系统); System.out.println(**********************); Scanner scanner new Scanner(System.in); System.out.print(请输入你的操作); int choice scanner.nextInt(); return choice; } }代码分析构造方法中初始化的接口数组只包含普通用户的功能实现了权限的差异化控制后续如果需要修改用户权限只需修改这个数组即可无需修改其他代码体现了解耦的思想菜单编号和接口数组索引严格对应保证了功能调用的准确性重写的menu()方法展示普通用户的专属菜单。踩坑提醒子类构造方法必须先通过super()调用父类构造方法否则会编译报错这是 Java 继承的强制要求接口数组的索引必须和菜单的编号一一对应否则会出现 “用户选择 1却执行了 2 的功能” 的问题抽象类的抽象方法必须被子类实现否则子类也必须定义为抽象类无法实例化。二、项目整合运行Main 类Main 类是整个项目的入口类负责实现用户登录、菜单循环、功能调用将前面的实体类、功能类、用户类整合在一起实现完整的业务流程。同时我们会解决前两篇提到的Scanner 输入的常见问题nextInt()后接nextLine()出现空行让程序的输入更健壮。Main 类完整代码import book.BookList; import user.AdminUser; import user.NormalUser; import user.User; import java.util.Scanner; public class Main { // 登录方法实现用户身份选择返回User类型对象多态父类引用指向子类对象 public static User login() { Scanner scanner new Scanner(System.in); System.out.println(欢迎来到图书管理系统); System.out.print(请输入你的姓名); String name scanner.nextLine(); System.out.print(请输入你的身份1管理员 2普通用户); int choice scanner.nextInt(); // 解决Scanner输入问题消耗nextInt()后输入缓冲区的换行符 scanner.nextLine(); // 多态的核心体现父类User的引用指向子类AdminUser/NormalUser对象 if(choice 1) { return new AdminUser(name); }else { return new NormalUser(name); } } // 主方法程序入口实现菜单循环和功能调用 public static void main(String[] args) { // 1. 初始化书架创建BookList对象默认有3本测试图书 BookList bookList new BookList(); // 2. 用户登录返回User对象体现多态 User user login(); // 3. 菜单循环死循环直到用户选择0退出系统 while (true) { // 4. 显示菜单获取用户的操作选择 int choice user.menu(); // 5. 执行操作根据用户选择调用对应的功能 user.doIoperation(choice, bookList); } } }三、核心知识点解析多态的体现 Scanner 问题解决1. 多态的核心体现父类引用指向子类对象这是整个项目中多态最核心、最直观的体现也是我开发中最有收获的部分彻底理解了多态的实际应用而不是课本上的抽象定义。// 多态的核心代码 User user login();login()方法的返回值类型是父类 User但实际返回的是子类 AdminUser或NormalUser对象后续调用user.menu()和user.doIoperation()时JVM 会根据user实际指向的对象自动调用子类的实现方法这就是动态绑定无需用大量的 if-else 判断 “user 是管理员还是普通用户”代码简洁、易扩展后续如果新增一种用户如超级管理员只需新增一个子类继承 User无需修改 Main 类的代码体现了多态的可扩展性。多态的两层体现继承的多态父类 User 引用指向子类 AdminUser/NormalUser 对象接口的多态接口 IOPeration 引用指向各个功能实现类对象。整个项目就是继承多态 接口多态的综合应用这也是 Java 面向对象的精髓。2. Scanner 输入常见问题解决nextInt () 后接 nextLine () 出现空行问题原因nextInt()方法只会读取输入的数字不会读取输入缓冲区中的换行符\n当后续调用nextLine()时nextLine()会直接读取这个换行符导致获取到空行无法正常获取用户的输入。解决方法在nextInt()方法后调用一次scanner.nextLine()消耗掉输入缓冲区中的换行符后续的nextLine()就能正常读取用户的输入了核心代码int choice scanner.nextInt(); scanner.nextLine(); // 消耗换行符解决空行问题在 Main 类的login()方法中我们已经加入了这个解决代码保证了程序输入的健壮性。3. 菜单循环的实现死循环 系统退出用while (true)实现死循环让用户可以反复执行操作直到用户选择 0 退出系统 ——ExitOperation类中调用System.exit(0)终止 JVM实现程序的正常退出这是控制台程序循环的标准写法。四、第三篇复盘总结面向对象思想的综合应用抽象类的实际应用抽象类适合抽取多个类的共性制定模板规范通过抽象方法强制子类实现个性兼顾代码的复用性和规范性多态的核心价值多态的核心是父类 / 接口引用指向子类 / 实现类对象动态绑定让代码摆脱繁琐的 if-else 判断提升代码的可扩展性和可维护性这是 Java 面向对象的精髓继承的修饰符选择继承中属性用protected修饰兼顾封装性和继承性是子类访问父类属性的最优选择Scanner 输入问题解决掌握nextInt()后接nextLine()的空行问题解决方法让程序的输入更健壮项目整合的思路小项目的整合思路是 “实体类→功能类→用户类→入口类”按功能分层开发最后在入口类中整合体现了 “高内聚、低耦合” 的设计思想。五、整个项目的最终总结从新手到入门的收获做完这个图书管理系统再通过三篇博客复盘我从一个只会看课本的 Java 新手真正入门了面向对象编程收获远不止代码本身吃透了四大面向对象核心思想不再是死记封装、继承、接口、多态的定义而是能在代码中灵活应用、综合体现掌握了小项目的开发思路学会了按功能分包、按分层开发遵循 “高内聚、低耦合” 的原则让代码结构清晰、易维护解决问题的能力提升遇到数组下标越界、字符串比较失败、Scanner 输入空行等问题时能独立分析原因、找到解决方法而不是一味地死磕养成了良好的编码习惯按需提供 getter/setter、及时更新有效数量、置空无效对象避免内存泄漏、代码添加详细注释这些习惯对后续开发非常重要最后不要怕写小项目也不要觉得小项目没意义。知识点只有落地成代码才能真正理解只有在实践中不断踩坑、不断解决问题才能真正提升自己的编程能力。这个图书管理系统虽然简单但它覆盖了 Java 面向对象的所有核心知识点也体现了企业开发的基础设计思想。做完这个项目再通过博客复盘梳理我的 Java 基础变得非常扎实也为后续学习框架、做更复杂的项目打下了坚实的基础。