设计背景
图书馆的藏书成千上万,没有一个合适的图书管理系统且不是乱套了?如果没有系统的管理更显得杂乱无章,使人困扰。因此,一个能够系统管理书籍的图书管理系统就显的尤为重要了🤭
利用知识
类和对象、抽象类和接口、继承和多态、封装、数组、重写等🖊
设计思路
①区分普通用户与管理员,不同用户所展示的操作界面不同 => 通过继承和多态实现
②为了程序的安全性 => 使用封装将各种操作封装成一个个类
遵守程序规范 => 使用接口定义规范
③有几个对象设计几个类 => 书类、用户类(这两个是最容易想到的)、功能类,以此为基础再去细分类(如图)
④读者能够实现查找书籍,借阅书籍以及归还书籍 => 可利用方法的重写与多态 更加高效
了解到这里,我们基本了解了图书管理系统的主要组成部分,并且可以开始着手编译了~
具体类实现与模块展示
0.包的创建
在这个程序中,为了实现继承、多态与封装,将会创建很多个类。为了方便管理以及debug,我们可以创建三个包,分别放相关的类(严格执行一类一坑位🐕!)
这样一来,代码的逻辑性与可读性都会大大增加~更重要的是方便我们后期的增删添改👍
(进入正题)
book包——
1.Book类
书籍的属性应该有哪些?书名、作者、价格、类型、是否被借出。结合封装性,我们可以得到Book类的几个对象:
public class Book {
private String name;
private String author;
private int price;
private String type;
private boolean isBorrowed;
}
此外,我们继续提供相应的get方法和set方法,空参构造器,以及一个不包含是否被借出的构造器(布尔值的默认类型就是false,可以不进行初始化)并按需提供对应的toString()方法的重写
//Book类的完整创建
package book;
public class Book {
private String name;
private String author;
private int price;
private String type;
private boolean isBorrowed;
public Book(String name, String author, int 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 int getPrice() {
return price;
}
public void setPrice(int 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 //下文的ShowOperation类和FindOperation类
//将会使用到此重写方法!
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", type='" + type + '\'' +
", isBorrowed=" + isBorrowed +
'}';
}
}
(注意:上段代码大部分由编译器生成,只需输入书的属性,然后运用Getter and setter构造方法 以及toString方法的快捷键,就能快速生成上述代码✌✌)
2.BookList类(书架)
Book创建了之后要有地方存储,BookList类主要为我们提供了一个数组进行存储,以及对以占用空间的记录。这里我们为useSize和books提供get和set方法。注意这里的getBooks和setBooks方法需要一个int类型变量。因为后面我们需要根据这个变量来访问books数组中的元素~
booklist存在的意义约等于书架,是存放书类型的数组😐
//BookList类的完整创建
package book;
public class BookList {
private Book[] books;
private int useSize;
public BookList() {
this.books = new Book[10];
this.books[0] = new Book("三国演义","罗贯中",10,"小说");
this.books[1] = new Book("西游记","吴承恩",9,"小说");
this.books[2] = new Book("红楼梦","曹雪芹",19,"小说");
this.useSize = 3;
}
public Book getBooks(int pos) {
return books[pos];
}
public void setBooks(Book book,int pos) {
books[pos] = book;
}
public int getUseSize() {
return useSize;
}
public void setUseSize(int useSize) {
this.useSize = useSize;
}
}
我们先创建三个Book对象放到Book数组中,以便后面我们调试~
user包——
1.User类
还记得前面导图中的用户类吗?里面有两种用户,一个是管理员,一个是普通用户,从想实现的内容来看,两种用户都有各自的菜单,同时他们都有姓名等一些公共的属性:
所以这里我们可以定义一个User父类,让AdminUser和 NormalUser去继承这个类。
重点来了!为了能根据choice调用不同的操作,这个方法自然也应该在user这个类中。故我们先在User内部定义一个成员变量 => 操作接口的数组,用于存放不同的操作类
因为两者菜单不同,所以在父类User中不需要有具体的menu方法,故用abstract修饰,而菜单需要返回用户输入的值,故用int定义🖊
//User的完整实现
package user;
import book.BookList;
import operation.IOperation;
public abstract class User {
protected String name;
protected IOperation[] ioperations; // 创建操作接口的数组
public User(String name) {
this.name = name;
}
public void doOperation(int choice, BookList bookList) {
this.ioperations[choice].work(bookList); // 通过choice返回的值,调用不同的方法
}
public abstract int menu();
}
2.AdminUser类 & NormalUser类
管理员类和普通用户类继承User类,对菜单进行重写,根据两种用户各自所需的功能进行打印,最后定义一个choice来接收~
//AdminUser类的完整实现
package user;
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 ShowOperation()
};
}
public int menu() {
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("********************");
System.out.println("请输入你的操作:");
Scanner scanner = new Scanner(System.in);
int choice = scanner.nextInt();
return choice;
}
}
是不是没看懂? 猜得出来,因为我一开始也没看懂XD
没关系,下面三个Q&A应该能解决你的疑惑~
---------------------------------------------------------------------------------------------------------------------------------
Q1:为什么这里创建了一个数组?
A:因为两种用户所要实现的功能不同,导致两种类需要调用的方法不同,为了让两种用户都能正确地调用自己的方法,我们在父类 User 定义一个成员变量 IOperation数组 ,然后在 User 的子类 AdminUser 和 NormalUser上分别对他进行实例化
Q2: 为什么这个数组能根据choice返回的值来调用不同的操作?
A:我们回到父类User中,看一下这个方法:
public void doOperation(int choice, BookList bookList) {
this.ioperations[choice].work(bookList);
}
我们通过this.ioperations[choice]
来获取一个在某处定义的 IOperation
数组中的元素。IOperation
是一个接口,这里数组中的元素实现了这个接口的类的实例。 (说人话:调用ioperations
数组下标为choice
的元素,实现其work方法)
Q3:怎么保证输入的值和操作相对应?
A:这个地方比较巧妙,我们通过数组的特性来实现。在这个菜单中,用户只需输入对应的数字即可调用相关方法。
我们不难看出,菜单对应的操作是和数组元素的顺序是相同的,如果在菜单中输入1,就会调用ioperations[1]
,也就是FindOperation()。
--------------------------------------------------------------------------------------------------------------------------------
NormalUser的实现类似与AdminUser,只是创建的数组稍有不同~
//NormalUser类的完整实现
package user;
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()
};
}
public int menu() {
System.out.println("******普通用户菜单******");
System.out.println("1.查找图书");
System.out.println("2.借阅图书");
System.out.println("3.归还图书");
System.out.println("0.退出系统");
System.out.println("**********************");
System.out.println("请输入你的操作:");
Scanner scanner = new Scanner(System.in);
int choice = scanner.nextInt();
return choice;
}
}
Main类——
用户和菜单都有了,此时我们需要一个类把他们全都整合起来。
接下来我们就需要登录了,我们写一个main方法,其中写一个login方法,先写判断判定 user 指向哪个对象 (向上转型)
然后可以打印指定的菜单,通过返回的choice来调用 user 指向对象的操作方法。(动态绑定)
// Main类的完整实现
import book.BookList;
import user.AdminUser;
import user.NormalUser;
import user.User;
import java.util.Scanner;
public class Main {
public static User login() {
System.out.println("请输入你的姓名");
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
System.out.println("请输入你的身份, 1:管理员 2:普通用户 ->");
int choice = scanner.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);
}
}
}
operation包——
1.IOperation类
熟悉接口的朋友应该已经看出来了,这里要将操作类实现为操作接口🎇
因为无论是管理员还是普通用户,我们需要实现的所有功能都属于对书的操作,都是在BookList类的数组books中操作的同时为重写的方法提供书架类,便于进行操作~
//Ioperation接口的完整实现
package operation;
import book.BookList;
public interface IOperation {
void work(BookList bookList);
}
接下来就可以根据接口来实现类了⚔ ⚔
2.ExitOperation类(退出系统)
我们先从最简单的操作类入手,利用system.exit(int status),status为0时为正常退出程序,也就是结束当前正在运行中的java虚拟机。
package operation;
import book.BookList;
public class ExitOperation implements IOperation {
public void work(BookList bookList) {
//其实还应该要对 bookList 资源 手动回收
System.out.println("退出系统...");
System.exit(0);
}
}
3.ShowOperation类(显示图书)
这个也比较简单,先获取BookList类中 bookList中实际使用的元素数量getUseSize(),然后使用for循环遍历bookList数组,获取当前索引(i)处的book对象并打印✌
//ShowOperation类的完整实现
package operation;
import book.Book;
import book.BookList;
public class ShowOperation implements IOperation {
public void work(BookList bookList) {
System.out.println("显示图书!");
int currentSize = bookList.getUseSize();
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getBooks(i);
System.out.println(book);
// 因为重写了tostring方法,所以此处能打印我们想要输出的格式
// (具体见Book类 完整实现)
}
}
}
4.AddOperation类(新增图书)
这个类主要分为两个部分:①判断要新增的书是否已在书架中
②将要新增的书放入书架中
对于第一部分,我们使用for循环遍历整个bookList数组,查找是否有相同name的元素:
//判断新增书是否在bookList中
public void work(BookList bookList) {
System.out.println("新增图书!");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要新增的书名: ");
String name = scanner.nextLine();
int currentSize = bookList.getUseSize();
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getBooks(i);
if (book.getName().equals(name)) {
System.out.println("此书已存在!");
return;
}
}
若没有,则将新建一个book对象并赋值,将book放入bookList数组中,同时更新占用的空间~
//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("新增图书!");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要新增的书名: ");
String name = scanner.nextLine();
//判断新增书是否在bookList中
int currentSize = bookList.getUseSize();
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getBooks(i);
if (book.getName().equals(name)) {
System.out.println("此书已存在!");
return;
}
}
System.out.println("请输入新书的作者: ");
String author = scanner.nextLine();
System.out.println("请输入新书的价格: ");
int price = scanner.nextInt();
System.out.println("请输入新书的类型: ");
scanner.nextLine();
String type = scanner.nextLine();
//将新增书放入bookList数组中
Book book = new Book(name, author, price, type);
bookList.setBooks(book,currentSize);
bookList.setUseSize(currentSize+1);
System.out.println("设置成功!");
}
}
5.DelOperation类(删除图书)
有增加就有删除。此处我们实现的删除方法是:用要删除元素后一个的元素覆盖掉要删除的元素,以此类推(相当于向前推进了一个元素,要删除元素被“挤掉”了),因此多了一个index变量来储存要删除元素的下标,以便后续的覆盖操作精准进行~
bookList.setBooks(null,currentSize-1);
👆此处要注意,最后一个元素无法被覆盖(否则溢出),故我们将其置为null。(必须将其置为null!这里涉及到回收,不过多赘述)
//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("删除图书!");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要删除的书名: ");
String name = scanner.nextLine();
int currentSize = bookList.getUseSize();
int index = -1;
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getBooks(i);
if (book.getName().equals(name)) {
index = i;
}
}
if (index == -1) {
System.out.println("无法删除, 此书不存在!");
}
for (int j = index; j < currentSize-1; j++) {
Book book = bookList.getBooks(j+1);
bookList.setBooks(book,j);
}
bookList.setBooks(null,currentSize-1);
bookList.setUseSize(currentSize-1);
System.out.println("删除成功!");
}
}
这两个操作类搞定之后,剩下几个类的就比较简单了🙌
6.BorrowOperation类(借阅图书)
利用book对象的布尔值(isBorrowed)来判断或改变📕借阅状态~
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("借阅图书!");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要借阅的书名: ");
String name = scanner.nextLine();
int currentSize = bookList.getUseSize();
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getBooks(i);
if (book.getName().equals(name)) {
if (!book.isBorrowed()) {
book.setBorrowed(true); //设置为以借阅
System.out.println("成功借阅!");
return;
}
}
}
System.out.println("抱歉, 暂无此书: "+name);
}
}
7.ReturnOperation类(归还图书)
和借阅图书类似,改变book对象的布尔值(isBorrowed)来改变借阅状态👊
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("归还图书!");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要归还的书名: ");
String name = scanner.nextLine();
int currentSize = bookList.getUseSize();
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getBooks(i);
if (book.getName().equals(name)) {
book.setBorrowed(false);
System.out.println("成功归还!");
return;
}
}
System.out.println("抱歉, 此书不在归还范围中:"+name);
}
}
8.FindOperation(查找图书)
for遍历即可,每一次遍历先定义一个book对象,然后比较该对象的名称与输入的名称是否一样,如果找到了,提示并打印🖨
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 scanner = new Scanner(System.in);
String name = scanner.nextLine();
int currentSize = bookList.getUseSize();
for (int i = 0; i < currentSize; i++) {
Book book = bookList.getBooks(i);
if (book.getName().equals(name)) {
System.out.println("存在这本书,信息如下:");
System.out.println(book);
return;
}
}
System.out.println(name+"此书不存在!");
}
}
现在万事俱备,一运行就能达到我们想要的效果啦!💪
代码总览
如果要查看代码,点击下方链接跳转至我的gitee仓库即可浏览~
结语
笔者是一个Java初学者,很多知识点都是了解一些皮毛,也是想借着写博客的机会巩固自己学习过的知识,如果有错误请大佬们狠喷!
愿我们共同进步!🎇🎇🎇