在前面的十三课中,笔者已经介绍了有关java的各种语法及其细节。若要融会贯通,则还需大量的练习,而非单纯的依赖于笔记中的内容。毕竟“纸上得来终觉浅,绝知此事要躬行”。在java语法的最后一节内容,我将运用之前学到的所有内容,实现一个简单的图书管理系统,并力将整个项目的实现细节讲解到位,共同学习。话不多说,进入正题。
首先,我会展示项目的最终实现效果:
一:管理员层面
二:用户层面
实现一个图书馆里系统,要包含以下三大块内容:
1.书包,要有每本书的信息,同时还要把每本书存放起来。即对于书的细节和一本书的存放情况,分别进行实现。于是就有了我们的第一个包:
2. 操作包:图书管理系统,其核心就是对书籍进行存取管理,这里同时又涉及到操作者的身份。操作者身份不同,则可以进行的操作也有所差异。这里先抽象出一个操作包,用于存放可能进行的操作。
3.正如2中所提到的,我们的第三个包,就是用于区分不同操作者的。
结合我们之前的思考,最后整体的结构如下图所示:
需要说明的是,在实际开发过程中,我们很难一次性想到所有需要实现的功能,通常会根据编写程序的进度及过程中出现的问题,不断改进、完善每个文件中的具体内容。但为使文章的思路更加流程,也为了减小书写的难度,我的行文思路是:在打好框架的基础上,尽量按照上图中从上到下的顺序,进行拆分讲解。
package book
package book;
public class Book {
public String name;
String author;
double price;
String type;
boolean isBorrowed;
public Book(String name, String author, double price, String type) {
this.name = name;
this.author = author;
this.price = price;
this.type = type;
}
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 double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isBorrowed() {
return isBorrowed;
}
public void setBorrowed(boolean borrowed) {
isBorrowed = borrowed;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", type='" + type + '\'' +
",status=" +((isBorrowed==true)?"已借出":"未借出") +
'}';
}
}
这个类非常简单,就是一个书类,包含了书名、作者、价格、类型、在借状态这5个private成员,并提供Getter And Setter方法实现访问;提供了一个带四个参数的构造方法,最后重写toString方法。唯一需要注意的是,我们并没有将status参数放在构造方法中进行初始化,因为当你添加一本书时,默认是未借出的,即status=false,而这正是status这个变量的默认值,无需进行手动传参的设置。
package book;
//BookList是书架,Book是书,实例化一书架booklist,实例化书book,booklist数组中上放的是book元素
public class BookList {
private Book books[]=new Book[10];
private int usedSize;
public BookList(){
books[0]=new Book("三国演义","罗贯中",25.8,"名著");
books[1]=new Book("百年孤独","马尔克斯",54.5,"文学");
books[2]=new Book("卡片笔记写作法","阿伦斯",23.3,"工具");
usedSize = 3;
}
public int getUsedSize() {
return usedSize;
}
public void setUsedSize(int usedSize) {
this.usedSize = usedSize;
}
//获取某个下标的书
public Book getPos(int pos){
return books[pos];
}
//给数组的pos位置放一本书
public void setBooks(int pos,Book book){
books[pos]=book;
}
}
如你所见,这是一个书架类。一开头我们就定义了一个book[]数组,用于存放书的信息,当然数组中每个成员都是Book类型的。通过构造方法,我先将三本书放入了“书架”。
usedSize用于显示当前书架中书的数目,我当然会为它提供Getter And Setter方法,以方便的获取和修改usedSize的值,及时更新数据。
如果你有一定的预见性,可能会想到我们需要做这样的事件,就是在书架的任意位置存放一本书,所以我们提供了setBooks方法,其参数为pos位置和一本书对象book。
IOPeration
import book.BookList;
public interface IOPeration {
public void work(BookList bookList);
}
这是一项独特的设计,既然我们要实现多个对书架的操作,我们索性实现一个操作接口,再让所有的操作实现这个接口,并重写其中的work()方法。参数自然是整个书架bookList。
对于每个操作的具体实现,我会在后文进行讲解。
User
package users;
import book.BookList;
import operation.IOPeration;
public abstract class User {
protected String name;
public User(String name) {
this.name = name;
}
public IOPeration[] ioPerations;//一个接口数组
public abstract int menu();
//如何得知调用哪个方法?
public void doOperation(int choice, BookList booklist){
ioPerations[choice].work(booklist);
//数组的某个下标是对象,可以调用对象的work()方法。
}
}
首先,User中的方法无需具体实现,所以我们会将其设置为abstract抽象类,其中的方法也定义为抽象方法。管理员和用户拥有共同的属性姓名,定义为name并使用构造方法进行初始化(具体到子类自然要重写父类的构造方法)。
我们先来看看最后实现出来的功能菜单:
我们期望的是从键盘上输入各个操作所对应的数字,就可以调用相对应的方法。那么如何知道调用哪个方法呢?这就要使用到接口数组,将所有的操作放在一个接口数组ioPerations中,每个操作自然会对应数组的下标,通过下标就可以找到这个操作类,再调用这个类下的work()方法。
如果对这里不理解,不妨回忆一下我们在第十一课——抽象类与接口一文中所讲过的这个例子:(理解的朋友可以直接跳过)
AdminUser
package users;
import book.BookList;
import operation.*;
import java.util.Scanner;
public class AdminUser extends User{
//重写父类构造方法
public AdminUser(String name) {
super(name);
this.ioPerations = new IOPeration[]{
new ExitOperation(),
new FindOperation(),
new AddOperation(),
new DelOperation(),
new DisplayOperation()
};
}
//重写父类抽象方法
@Override
public int menu() {
System.out.println("===========管理员菜单==========");
System.out.println("您好 "+this.name+" 欢迎来到世界联合图书馆");
System.out.println("1.查找图书");
System.out.println("2.新增图书");
System.out.println("3.删除图书");
System.out.println("4.显示图书");
System.out.println("0.退出系统");
System.out.println("请输入你的操作:");
Scanner sc = new Scanner(System.in);
int choice = sc.nextInt();
return choice;
}
}
这是对于管理者的具体实现。首先,重写父类构造方法;第二步,创建接口数组,放入管理员可以进行的操作;最后,重写父类中的抽象方法menu()。需要注意是,数组的下标与操作要相互对应,所以我们将ExitOperation()放在数组的第一个位置,确保输入0时正常退出系统。
NormalUser
package users;
import operation.*;
import java.util.Scanner;
public class NormalUser extends User {
public NormalUser(String name) {
super(name);
this.ioPerations = new IOPeration[]{
new ExitOperation(),
new FindOperation(),
new BorrowOperation(),
new ReturnOperation()
};
}
@Override
public int menu() {
System.out.println("===========用户菜单==========");
System.out.println("您好 "+this.name+" 欢迎来到世界联合图书馆");
System.out.println("1.查找图书");
System.out.println("2.借阅图书");
System.out.println("3.归还图书");
System.out.println("0.退出系统");
System.out.println("请输入你的操作:");
Scanner sc = new Scanner(System.in);
int choice = sc.nextInt();
return choice;
}
}
这是对于普通用户的具体实现。与管理员的唯一不同之处在于接口数组,因为它他们会进行不同的操作。
Main
import book.BookList;
import users.AdminUser;
import users.NormalUser;
import users.User;
import java.util.Scanner;
public class Main {
public static User login(){
System.out.println("请输入您的姓名:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
System.out.println("请输入你的身份:1->管理员 0->用户");
int choice =sc.nextInt();
if(choice == 1){
return new AdminUser(name);
}else{
return new NormalUser(name);
}
}
public static void main(String[] args) {
BookList bookList = new BookList();
User user = login();
while(true)
{
int choice = user.menu();
user.doOperation(choice,bookList);
}
}
}
在主函数的实现中,我们需要考虑一个非常重要的问题,就是如何得知当前正在使用该系统的人是管理员还是普通用户呢?所以我们设置登陆选项,根据使用者的输入确定其身份,不同的身份,会打印不同的菜单,也会实现不同的操作。父类引用引用子类对象,在这里用到了“继承”的知识,根据返回值的不同,当前用户的身份也可以得到确认了。
根据我们的思考,在main函数中实例化出书架bookList和用户User对象即可。为实现多次输入,我们操作的具体实现放在while(true)循环中,并通过退出系统的类实现循环终止。
至此,我们整体的实现逻辑已经言明。接下来,我会展示每一个操作的代码,需要注意的是,所有的操作都实现了IOPeration接口,并重写其中的work()方法。
AddOperation(新增图书)
package operation;
import book.Book;
import book.BookList;
import java.util.Scanner;
public class AddOperation implements IOPeration{
public void work(BookList bookList){
System.out.println("新增图书!");
int currentSize = bookList.getUsedSize();
System.out.println("请输入你要添加的图书的信息:");
Scanner sc = new Scanner(System.in) ;
System.out.println("请输入书名");
String name = sc.nextLine();
System.out.println("请输入作者");
String author = sc.nextLine();
System.out.println("请输入类型");
String type = sc.nextLine();
System.out.println("请输入价格");
double price = sc.nextDouble();
Book book = new Book(name,author,price,type);
bookList.setBooks(currentSize,book);
bookList.setUsedSize(currentSize+1);
System.out.println("添加成功!");
}
}
首先输入你要新增书目的所有信息,然后将这些信息作为参数实例化book,基于预见性,我们已经在BookList类中实现了在某个位置插入一本书的函数setBooks(),直接调用即可,最后别忘记通过setUsedSize()方法将书的数目+1。
BorrowOperation(借阅图书)
package operation;
import book.Book;
import book.BookList;
import java.util.Scanner;
public class BorrowOperation implements IOPeration{
public void work(BookList bookList){
System.out.println("借阅图书!");
System.out.println("请输入你要借阅的图书的名字:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
int currentSize = bookList.getUsedSize();
for (int i = 0; i <currentSize ; i++) {
Book book = bookList.getPos(i);
if(book.getName().equals(name)&&book.isBorrowed()==false){
System.out.println("借阅成功");
book.setBorrowed(true);
bookList.setUsedSize(--currentSize);
return;
}else if(book.getName().equals(name)&&book.isBorrowed()==true){
System.out.println("该书已被借出");
return;
}else{
System.out.println("该书不存在!");
return;
}
}
}
Scanner sc = new Scanner(System.in);
}
对于借书,我们的思路是输入书名,然后遍历书架数组,进行书名匹配。如果循环结束任未匹配成功,那么说明“该书不存在”;如果书名匹配成功并且借阅状态为“已借出”,那么说明该书存在但已经被借出,无法借阅;当且仅当书名匹配成功并且借阅状态为“未借出”时,才能进行借书。而借阅成功,需要将其借阅状态改为“已借出”,并将书目数减1。也有人认为此时应该直接将这本书的信息删去,不应该是只改变在借状态,这与每个人对项目的理解不同,你当然可以根据自己的想法进行业务拓展或改进。
DelOperation(删除图书)
package operation;
import book.Book;
import book.BookList;
import java.util.Scanner;
public class DelOperation implements IOPeration{
public void work(BookList bookList) {
System.out.println("删除图书!");
System.out.println("请输入你要删除的书名:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
int currentSize = bookList.getUsedSize();
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getPos(i);
if (book.getName().equals(name)) {
for (int j = i; j < currentSize; j++) {
book = bookList.getPos(j + 1);
bookList.setBooks(j, book);
}
System.out.println("删除成功!");
bookList.setUsedSize(--currentSize);
return;
}
}
System.out.println("该书不存在!");
}
}
如果要删除一本书,首先输入欲删除书的书名,然后遍历书架数组,进行书名匹配。如果匹配失败,那么自然无法进行删除;匹配成功时,可以进行删除。与在整型数组中删除元素的思路类似,删除书架数组任意位置的元素同样采用覆盖的方式。从你要删除的那本书开始进行循环,依次让后一本书覆盖前一本书(即设置前一本书的内容为后一本书,同样用到setBooks()函数)。删除成功之后,将书目数减1。
DisplayOperationy(展示图书)
package operation;
import book.Book;
import book.BookList;
public class DisplayOperation implements IOPeration{
public void work(BookList bookList){
System.out.println("显示图书!");
int currentSize = bookList.getUsedSize();
for (int i = 0; i <currentSize ; i++) {
Book book = bookList.getPos(i);
System.out.println(book);
}
}
}
显示图书,就是打印每一本书的信息。直接循环遍历书架数组,调用toString()方法打印每本书的信息即可。
ExitOperation(退出系统)
package operation;
import book.BookList;
public class ExitOperation implements IOPeration{
public void work(BookList bookList){
System.out.println("退出系统!");
int currentSize = bookList.getUsedSize();
for (int i = 0; i <currentSize ; i++) {
bookList.setBooks(i,null);
}
System.exit(0);
}
}
退出系统。当我们退出系统时,自然需要将书架中的内容情况,即循环遍历书架数组,将每一个位置上的元素置为NULL。System.exit(0)可以终止当前程序的运行,退出系统。
FindOperation(查找图书)
package operation;
import book.Book;
import book.BookList;
import java.util.Scanner;
public class FindOperation implements IOPeration{
public void work(BookList bookList){
System.out.println("查找图书!");
System.out.println("请输入你要查找的书名:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
int currentSize = bookList.getUsedSize();
for (int i = 0; i <currentSize ; i++) {
Book book = bookList.getPos(i);
if(book.getName().equals(name)){
System.out.println("找到了,信息如下:");
System.out.println(book);
return;
}
}
System.out.println("该书不存在!");
}
}
查找图书。输入你要查找的书名,并在书架数组中进行遍历匹配,如果匹配成功,说明该书存在,直接打印这本书的信息即可;如果匹配失败,则返回“该书不存在”。
ReturnOperation(归还图书)
package operation;
import book.Book;
import book.BookList;
import java.util.Scanner;
public class ReturnOperation implements IOPeration{
public void work(BookList bookList){
System.out.println("归还图书!");
System.out.println("请输入你要归还的图书的名字:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
int currentSize = bookList.getUsedSize();
for (int i = 0; i <currentSize ; i++) {
Book book = bookList.getPos(i);
if(book.getName().equals(name)&&book.isBorrowed()==true){
System.out.println("归还成功");
book.setBorrowed(false);
bookList.setUsedSize(++currentSize);
return;
}else if(book.getName().equals(name)&&book.isBorrowed()==false){
System.out.println("该书不属于在借图书!");
return;
}
}
System.out.println("非本馆图书!");
}
}
归还图书。先输入你要归还的图书的书名,然后在书架数组中进行遍历匹配。一种情况是没有匹配成功,那说明这不是本馆的图书,直接打印“非本馆图书!”;在匹配成功的情况下,如果该书的借阅状态为“已借出”,那么可以进行归还,将其借阅状态设置为“未借出”,并将usedSize加1即可。如果你已经理解了这波操作,这也是为什么在借出图书时,我仅仅修改了图书的借阅状态,而没有直接将图书信息全部删除的原因。
至此,整个图书管理系统已经实现完毕,并且笔者也已经在文章开头进行过效果的演示。
这篇博客落幕,java基础语法的内容也已更新完毕了。虽然读者很少,我仍坚信这些文章的价值,也不会去怀疑书写这些文章的意义。即使无人问津,我们还是要去写;即使没有外界的正反馈,我们依然要遵从自己内心的声音,依然要相信坚持的力量。与诸君共勉!
本课内容结束!