引言
实现图书管理系统是对 Java 语法中的对象与类 这一块的大总结。本篇博客涉及到 Java 的对象与类、面向对象编程的的一些知识,核心点包括:【 类、包、封装、继承、多态、接口 】 等等,如果小伙伴在中途有遇到代码不理解的,可以去我的主页看看本篇博客的之前的三篇博客,当然,如果有大佬遇到我有错误的、蹩脚的代码,可以给我提出来,我一定虚心接受!
一、思路
创建三个包:
book:主要形成图书的一些信息,包中有两个类,Book 和 BookList,【Book类】 表示某一本书的信息,里面有图书名字,图书作者,图书价格等等,【BookList类】 表示电子书架,里面放了很多本书,可以对这些书架上的书进行借阅、查找的一系列操作等等。
operation:主要形成对图书的一些操作,包中有八个类、一个接口,八个类都实现一个共同接口【IOperation】,并且,一个类只对应一个操作。
user:主要形成用户功能,包中有六个类,其中三个类分别表示三种用户:【普通用户、会员、图书管理员】,而【User】类是抽象类,只用来被三种用户类继承,并且【Execute】类中放的是主函数,是我用来测试的。
二、代码实现
1. book 包
(1)Book 类
package book; public class Book { private String name; private String author; private String type; private double price; //通过标签 label 判断会员图书,label == 0 (免费) / label == 1 (会员) public int label; //通过布尔类型判断是否被借出 private boolean isBorrowed; //构造方法 public Book(String name, String author, String type, double price, int label) { this.name = name; this.author = author; this.type = type; this.price = price; this.label = label; } //针对 private,生成 Getter and Setter 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getType() { return type; } public void setType(String type) { this.type = type; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public boolean isBorrowed() { return isBorrowed; } public void setBorrowed(boolean borrowed) { isBorrowed = borrowed; } //生成 toString 方法,便于观察结构 @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", author='" + author + '\'' + ", type='" + type + '\'' + ", price=" + price + ", " + ((label == 0)?"免费":"会员图书")+ ", " + ((isBorrowed == false)?"此书未被借出":"此书已被借书") + '}'; } }
(2)BookList 类
package book; public class BookList { private Book[] books = new Book[10]; //创建一个Book类型的数组 private int usedSize; //书架所剩的图书 public BookList() { books[0] = new Book("三国演义","罗贯中","四大名著",57,1); books[1] = new Book("西游记","吴承恩","四大名著",21,0); books[2] = new Book("Java项目","林","技术丛书",31,0); books[3] = new Book("朝花夕拾","鲁迅","文学小说",22,0); books[4] = new Book("Java高阶项目","李","技术丛书",50,1); books[5] = new Book("平凡的世界","路遥","文学小说",35,0); books[6] = new Book("百年孤独","马尔克斯","外国名著",60,1); usedSize = 7; } //针对 private,生成 Getter and Setter 方法 public Book[] getBooks() { return books; } public void setBooks(Book[] books) { this.books = books; } public int getUsedSize() { return usedSize; } public void setUsedSize(int usedSize) { this.usedSize = usedSize; } public Book getPos(int pos){ return books[pos]; } //为尾插法设计 public void addBook(int pos, Book newBook){ books[pos] = newBook; } //为尾删法设计(元素覆盖) public void moveBook(int pos){ books[pos] = books[pos+1]; } }
2. operation 包
(1)IOperation 接口
package operation; import book.BookList; import java.util.Scanner; public interface IOperation { Scanner scanner = new Scanner(System.in); void work(BookList bookList); }
(3)BorrowOperation 类
package operation; import book.Book; import book.BookList; public class BorrowOperation implements IOperation{ @Override public void work(BookList bookList) { System.out.println("------------借阅图书------------"); DisplayOperation displayOperation = new DisplayOperation(); displayOperation.work(bookList); System.out.println("请输入您需要借阅的图书名字:"); String name = scanner.nextLine(); int size = bookList.getUsedSize(); for (int i = 0; i < size; i++) { Book findBook = bookList.getPos(i); if(name.equals(findBook.getName())){ if(findBook.label == 0){ findBook.setBorrowed(true); System.out.println("------------恭喜您!借阅成功------------"); System.out.println(); return; }else{ System.out.println("------------抱歉!您需要会员权限才能借阅此书------------"); System.out.println(); return; } } } System.out.println("------------抱歉!您所要借阅的图书不在图书系统中------------"); System.out.println(); } }
(4)BorrowOperation2 类
package operation; import book.Book; import book.BookList; public class BorrowOperation2 implements IOperation{ @Override public void work(BookList bookList) { System.out.println("------------借阅图书------------"); DisplayOperation displayOperation = new DisplayOperation(); displayOperation.work(bookList); System.out.println("请输入您需要借阅的图书名字:"); String name = scanner.nextLine(); int size = bookList.getUsedSize(); for (int i = 0; i < size; i++) { Book findBook = bookList.getPos(i); if(name.equals(findBook.getName())){ findBook.setBorrowed(true); System.out.println("------------恭喜您!借阅成功------------"); System.out.println(); return; } } System.out.println("------------抱歉!您所要借阅的图书不在图书系统中------------"); System.out.println(); } }
(5)DeleteOperation 类
package operation; import book.Book; import book.BookList; public class DeleteOperation implements IOperation{ @Override public void work(BookList bookList) { System.out.println("------------删除图书------------"); System.out.println("请输入您要删除的图书名:"); String name = scanner.nextLine(); int size = bookList.getUsedSize(); for (int i = 0; i < size; i++) { Book findBook = bookList.getPos(i); if(name.equals(findBook.getName())){ int pos = i; for (int j = 0; j < size-i-1; j++) { bookList.moveBook(pos); //调用函数,从后往前移数据 pos++; } bookList.setUsedSize(size - 1); System.out.println("------------删除成功!------------"); System.out.println(); return; } } System.out.println("------------抱歉!你所要删除的这本书不在图书系统中------------"); System.out.println(); } }
(6)DisplayOperation 类
package operation; import book.BookList; public class DisplayOperation implements IOperation{ @Override public void work(BookList bookList) { System.out.println("------------显示图书------------"); int size = bookList.getUsedSize(); for (int i = 0; i < size; i++) { System.out.println(bookList.getBooks()[i]); } System.out.println(); } }
(7)ExitOperation 类
package operation; import book.BookList; public class ExitOperation implements IOperation{ @Override public void work(BookList bookList) { System.out.println("------------退出系统------------"); System.exit(0); } }
(8)FindOperation 类
package operation; import book.Book; import book.BookList; public class FindOperation implements IOperation{ @Override public void work(BookList bookList) { System.out.println("------------查找图书------------"); System.out.println("请输入您要查找的图书名:"); String name = scanner.nextLine(); int size = bookList.getUsedSize(); for (int i = 0; i < size; i++) { Book findBook = bookList.getPos(i); if(name.equals(findBook.getName())){ System.out.println("为您找到了这本书,此书信息如下:"); System.out.println(findBook); return; } } System.out.println("------------抱歉!这本书不在图书系统中------------"); } }
(9)ReturnOperation 类
package operation; import book.Book; import book.BookList; public class ReturnOperation implements IOperation{ @Override public void work(BookList bookList) { System.out.println("------------归还图书------------"); System.out.println("请输入您需要归还的图书的名字:"); String name = scanner.nextLine(); int size = bookList.getUsedSize(); for (int i = 0; i < size; i++) { Book returnBook = bookList.getPos(i); if(name.equals(returnBook.getName())){ returnBook.setBorrowed(false); System.out.println("------------归还成功!------------"); System.out.println(); return; } } System.out.println("------------抱歉!您所要归还的图书不在原来的系统中------------"); System.out.println(); } }
3. user 类
(1)User 类
package user; import operation.IOperation; import book.BookList; import java.util.Scanner; public abstract class User { public String name; public User(String name) { this.name = name; } Scanner scanner = new Scanner(System.in); public abstract int menu(); protected IOperation[] iOperations; public void doWork(int choice, BookList bookList){ iOperations[choice].work(bookList); } }
(2)NormalUser 类
package user; import operation.*; public class NormalUser extends User { public NormalUser(String name) { super(name); iOperations = new IOperation[]{ new ExitOperation(), new FindOperation(), new BorrowOperation(), new ReturnOperation(), }; } @Override public int menu() { System.out.println("============用户菜单============"); System.out.println("请选择您要操作的选项:"); System.out.println("1.查找图书 2.借阅图书 3.归还图书 0.退出系统 10.返回主页面"); int choice = scanner.nextInt(); return choice; } }
(3)MemberShip 类
package user; import operation.*; public class NormalUser extends User { public NormalUser(String name) { super(name); iOperations = new IOperation[]{ new ExitOperation(), new FindOperation(), new BorrowOperation(), new ReturnOperation(), }; } @Override public int menu() { System.out.println("============用户菜单============"); System.out.println("请选择您要操作的选项:"); System.out.println("1.查找图书 2.借阅图书 3.归还图书 0.退出系统 10.返回主页面"); int choice = scanner.nextInt(); return choice; } }
(4)AdminUser 类
package user; import operation.*; public class AdminUser extends User{ public AdminUser(String name) { super(name); iOperations = new IOperation[]{ new ExitOperation(), new FindOperation(), new AddOperation(), new DisplayOperation(), new DeleteOperation() }; } @Override public int menu() { System.out.println("============管理员菜单============"); System.out.println("请选择您要操作的选项:"); System.out.println("1.查找图书 2.增添图书 3.显示图书 4.删除图书 0.退出系统 10.返回主页面"); int choice = scanner.nextInt(); return choice; } }
(5)Execute
package user; import book.BookList; import java.util.Scanner; public class Execute { public static User login(){ System.out.println("============欢迎来到主页面============"); System.out.println(); System.out.println("请输入您的名字"); Scanner scanner = new Scanner(System.in); String name = scanner.nextLine(); System.out.println("你好," + name + " 先生/女士"); System.out.println("------------请输入您的身份: 1-> 普通用户 2-> 会员 3-> 图书管理员------------"); int choice = scanner.nextInt(); if(choice == 1){ return new NormalUser(name); }else if(choice == 2){ return new MemberShip(name); }else { return new AdminUser(name); } } public static void main(String[] args) { //1. new 一个书架,开始对这个书架进行操作 BookList bookList = new BookList(); while (true){ //2. 确定用户 User user = login(); //向上转型 while(true){ //3. 选择操作功能 int choice = user.menu(); //动态绑定 if(choice == 10){ break; } //4. 通过接口开始执行操作功能 user.doWork(choice, bookList); } } } }
三、代码分析
说明
下面是对以上的代码实现进行了一些分析,主要是关于 Java 语法层面上的,实际上理解了就并不难,但就是有一点绕人。。。而关于我分析每个类、每个方法,这是本人的思想,很多人可能有自己的想法,因为业务逻辑的不同,所以写出来的代码很大可能性上不相同。
1. book 包分析
(1)Book 类
book 包中的第一个类 Book:这里创建了一本书的所有信息,包括的内容有:【名字、作者、类型、价格、是否是会员图书、是否被借出】。在用户视角来看,图书的信息基本上都是隐藏的,所以用 private 修饰图书的参数。然而在管理员视角就不同了,他可以拿到所有图书的信息。其中的构造方法、Getter and Setter 这里就不再赘述,这就是拿到封装变量的方式。而 toString 方法,可以通过IDEA 编译器自动生成,此外,我对自动生成的参数做了一些改变,这是为了更好地观察某一本书的参数。
(2)BookList 类
book 包中的第二个类 BookList:这里创建了一个 books 数组,数组类型是 Book[ ] 类型,表示电子书架,同时我也创建了数组中剩余的元素 usedSize,表示电子书架中当前拥有的图书,它们对应的 Getter and Setter 方法这里就不再赘述。此外,我创建了一个无参的构造方法,里面初始化了七本书,同时这个构造方法也为了后续方便对书架进行操作。最后,我创建了两个方法,一个是 addBook,用来增添图书,另一个是 moveBook,用来进行后续删除图书的覆盖操作。
这里我要说明两点:
① 这里的逻辑模拟的是 顺序表。
② 为什么要创建 addBook 和 moveBook 这两个方法在当前类呢?而不把这两个类直接放在 operation 包下的类呢?因为当前的 BookList 类和 Book 类属于同一个包,而这两个类下的很多成员变量都是被 private 修饰的,我们都知道:被 private 修饰的成员变量,只能在当前类中被使用,而只有 BookList 类中创建了 books 数组,等于拿到了 Book 类中的信息,在别的包中,根本就不能够做到对图书信息的直接操作,所以为了方便,我创建了 addBook 和 moveBook 这两个方法,那么后续方法只要满足于这两个操作逻辑,我直接传参调用即可。
2. operation 包分析
(1)IOperation 接口
operation 包下的接口 IOperation:这个接口的代码较为简洁,但是作用非常大,拓展能力非常强,可以看到我们创建了一个 void work( ) 方法,传参传的就是 BookList 类型,没错,我们这个接口实现的就是对电子书架操作,而这个方法的限定符,我想大家都知道:public abstract,因为它是抽象方法。只不过,限定符被隐藏了,这里不再赘述。
对于 operation 这八大类我不再一一说明,因为实现过顺序表的增删查改功能的小伙伴,都应该很熟悉这些操作。但是我必须说明几点:
① 八大类都实现了接口 IOperation,也就是说,每一个类都要重写 work( ) 方法。
② 对于增添操作,我没有考虑扩容,而且我事先增添操作的时候,只实现了尾插操作,感兴趣的小伙伴可以自己实现一下,这只是业务逻辑的方面不同,因为我不实现这些操作是有我自己的考虑。比方说:我用 boolean 类型来考虑图书有无被借出的情况,这就避免了头插和中间插入。比方说:我用变量 label 来标记是否是会员丛书等等。。。
③ 整体来说,这部分对于实现过顺序表的小伙伴们来说一点都不难,但是很繁杂,因为这里需要考虑到很多语法的问题,包括:实现接口操作、通过一个包中的某个类中的某个方法调用另一个包中的某个类中的某个方法、通过类创建对象等等。这给我们的感觉就好像是套娃一样。。。当然一个很关键的点就是因为图书的很多参数被 private 修饰了,这就造成了后续代码实现很繁杂,如果所有的限定符都是 public,那么就会简化很多很多。
3. user 包分析
user包 涉及面向对象编程的知识有很多,老样子,我先来分析代码实现了什么,再来说一下为什么要这么实现。
(1)User 类
user 包中的 User 类:User 类,通俗的来说,就是用户群体,是一个很关键的类,这代表了一个广泛的类型。User 类被定义为抽象类,其中 menu( ) 方法,只用来被继承,其中 doWork( ) 方法通过接口来调用类来操作电子书架。
(2)三个用户类
user 包中的 NormalUser 类、MemberShip 类、AdminUser 类 分别表示普通用户、会员、图书管理员这三种身份。这三个类都继承了抽象类 User,然而,不同类里面实现不同的功能,也就是实现了 operation 包下的不同的类,这些功能都是通过 ioperations 数组才得以实现的。
(3)Execute 类
user 包中的 Execute 类:在这个类中,我创建了 login( ) 方法 和 主函数。其中,login( ) 方法中,我实现了自己的业务逻辑,分别是三种身份:【普通用户、会员、图书管理员】,当你选择哪个身份的时候,就返回哪个身份。也就是说,在返回的时候,我创建了一个新的对象,比方说我返回的时候创建了一个普通用户,那么在主函数中就会实现下列代码:
User user = new NormalUser(name); //向上转型 //直接调用 NormalUser 类中的构造方法 int choice = user.menu(); <==> new NormalUser(name).menu(); //动态绑定 //直接调用 NormalUser 类中的 menu() 重写方法
系统在跑过上面两行代码后,如果 (choice != 10),就会调用这行代码:
user.doWork(choice, bookList); <==> new NormalUser(name).doWork(choice, bookList); //直接通过此对象调用 doWork 方法,再通过 doWork 方法实现接口 IOperation, //通过这个接口就能直接对电子书架进行直接操作了!
四、详细演示 查找图书操作
在演示之前我先说明一下:下面演示的只是针对其中一种情况,但是这足以表达清楚整个面向对象的逻辑,因为时间有限,感兴趣的小伙伴可以自己研究一下其他情况下的代码运行顺序。
假设你现在是一名普通用户,我们通过系统来查找一本书
IDEA 编译器的输出窗口:
上图的运行窗口看起来很简单,但是内部的代码运行顺序可不简单,它远远比表面呈现的要复杂的多!
那么接下来,我通过画图来演示代码运行的顺序,毋庸置疑,程序都是从主函数开始跑起来的,所以我从主函数开始分析:
上图是代码运行的顺序,接下来我通过文字叙述一下每一步程序是怎么走的,下面的每个序号对应上图的每个序号。
第1步
BookList bookList = new BookList();
这一步没什么好说的,我们通过 BookList 类创建了一个对象,初始化了数组,里面放了7本书。
第2步
User user = login();
这一步骤语法称为向上转型:
① 调用 login( ) 方法
② 返回的时候创建一个 【normalUser】 对象
这里需要注意一个非常关键的点:从main 函数的这行代码开始往下,所有的对象变量 user 都只具有 normalUser 属性,也就是说,从这里开始到 main 函数结束,所有的代码都只应用于一个对象:普通用户
第3步
int choice = user.menu();
这一步骤语法称为动态绑定:
① 通过刚刚 new 的 【normalUser】 对象,访问到 NormalUser 类中的 menu( ) 方法
② return choice == 1
第4步
user.doWork(choice, bookList);
这个步骤语法称为多态:
① 通过刚刚 new 的 【normalUser】 对象,调用 User 类 中的 doWork 方法
② 通过数组 iOperations[choice],访问到 NormalUser 类中的数组 iOperations,并调用 iOperation[1](choice == 1)
③ 开始调用 FindOperation
④ 通过 FindOperation 类 调用重写方法 work( )
五、一些有趣的测试
(1)情况一:
假设你是一名客人,名叫杰克,你的身份是普通用户,你需要借阅的图书名字是 ’ 三国演义 ',然而这本书却是会员图书,运行窗口如下:
(2)情况二:
假设你是一名管理员,名叫露丝,你需要添加一本书,名叫 ’ C语言 ',运行窗口如下:
我就不进行多余的测试了,感兴趣的小伙伴可以自己测试一下。
六、谈一谈为什么要用面向对象的编程来编写图书管理系统
刚开始我认为对书架进行操作可以使用 case 语句,这样确实没问题,但我思考了一个问题:当这个系统的用户不止三个,比方说:有1级会员,2级会员,有1级管理员,2级管理员。。。这些使用者要对电子书架进行不同的操作,那么这个时候使用 case 语句就不是很方便了,因为它会造成我们改变很多行代码这样的麻烦事。后来发现使用接口,并把接口表示为数组的元素,这样的逻辑可以对用户进行拓展,或者拓展新的操作,这样一来,就很方便了,这里仅代表我个人的观点。
总结
把这个图书管理系统做出来,对我这个小白,还是很不容易的,我把之前面对对象编程的内容重新总结了一下,差不多花了七天左右的时间,之后做这个小项目又花了我两天的时间。到目前为止,还是有点小迷糊,因为最近几天赶上本科论文答辩的一些事情,所以有些断断续续的。不过好在我坚持下来了,我把很多关键的点总结下来了,以后决定多看几遍,多理解一点其中的编程思想。