1、引言
经过一个月的学习,我们掌握了数据类型、逻辑控制、数组、方法、类和对象、封装、继承、多态、抽象类、接口......我们已经掌握了Java的语法,在这里,我们来实现一个图书管理系统的小项目,将我们学到的知识全部用起来!以后学习了MySQL,我们还可以将这个小项目进行升级!
2、运行展示
首先,我们先来运行一下图书管理系统的代码,了解一下整体逻辑:
QQ2024528-205028-HD
以下几点值得注意:
1.管理员和普通用户所具有不同的操作权限
2.书架中原本就已经存储了三本书
3. 我们可以进行多种操作来管理或使用图书
OK,话不多说,我们进入正题~
3、图书管理系统
在项目中,我们有着多个对象:图书、书架、管理员、普通用户、删除图书操作、添加图书操作、展示图书操作、借阅图书操作、归还图书操作、排序图书操作........
为了更好的管理代码,同时使逻辑更加清晰明确,我们可以创建包来管理相关代码。
3.1 图书相关
我们可以单独创建一个包,用来存放与图书有关的相关代码。
3.1.1 public class Book(图书类)
首先,图书管理系统,那必不可少的就是图书类,我们创建图书类并定义好相关属性,我这里有:书名、作者、价格、类型、状态(是否被借出)等等(自已来定义你需要的属性)......
需要注意的是,我们最好将这些属性使用private来封装起来,提供公开的get和set接口,提高书籍的安全性。
并且,我们创建带相关参数的构造方法,以后在实例化图书对象时完成初始化,isBorrowed可以不作为参数传入,因为boolean类型的成员变量的默认值为false,即默认为未借出。(因为我们创建这本图书的时候肯定还没有被借出)
我们可以先重写一下toString方法,后续会用到。
整体代码:
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
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", type='" + type + '\'' +
(isBorrowed == true ? ", 该书被借出" : ", 该书未被借出") +
'}';
}
}
3.1.2 public class BookList(书架类)
在图书系统中,我们肯定需要有一个能够存放的图书的书架,通过这个书架来对图书完成一些增删查改的操作,我们再来定义一个BookList类。
在这个类中,我们定义一个数组,用来存放Book类的对象,即存放图书(使用了组合)。
还可以定义一个整形变量成员usedSize存放实际的图书数目。
别忘了将这些成员属性封装起来~
别忘了,图书系统中原本就有着三本书,那这三本书肯定是在构造对象时就完成了初始化,于是我们就在构造方法中生成这三本书(放入Book数组中),并将usedSize置为3 。
在后续对图书进行增删查改时肯定会涉及到具体的一本书,我们可以写一个公开接口来得到或更改某一下标的图书:
整体代码:
public class BookList {
private Book[] books = new Book[100];//存放图书
private int usedSize;//实际图书数目
public BookList() {
//书架中原本就有着三本书,在构造方法中完成初始化
books[0] = new Book("三国演义","罗贯中",50,"小说");
books[1] = new Book("朝花夕拾","鲁迅",25,"散文");
books[2] = new Book("悲惨世界","雨果",30,"小说");
this.usedSize = 3;
}
public Book[] getBooks() {//得到Book数组
return books;
}
public void setBook(int pos,Book book) {//通过下标来设置某本书
this.books[pos] = book;
}
public Book getBook(int pos) {//通过下标来得到某本书
return this.books[pos];
}
public int getUsedSize() {//得到图书的实际数目
return usedSize;
}
public void setUsedSize(int usedSize) {//设置图书的实际数目
this.usedSize = usedSize;
}
}
4.1 操作相关
我们再新创建一个包,来管理操作相关的代码
对于管理员和普通用户,他们分别有着不同的操作,而这些操作都是对书架上的图书进行的,我们可以写一个和操作相关的接口,在接口中定义一个抽象方法,不同的操作都需要通过实现这个接口来规范行为,使代码、逻辑更加清晰。
接口代码:
public interface IOperation {
void work(BookList bookList);
}
共有以下操作:
我们先来定义这些操作,先搭好整体框架后再来完成具体的操作的代码。
5.1 用户相关
同样,我们创建新包来管理用户相关的代码。
5.1.1 public class User(父类)
我们知道,用户分为管理员用户和普通用户,我们定义一个User类来抽取出他们的共性,作为他们的父类。
因为选择身份后会打印相关身份的菜单(各自菜单不同):
我们可以在父类User中定义一个不需要具体实现的菜单方法(子类中进行重写),就也是说可以将User定义为抽象类:
在子类中可以重写这个方法,各自实现各自的菜单。
菜单打印后,不同身份的用户会选择相关操作,不同用户的操作也是不相同的,我们该如何去处理呢?
因为在上文处理操作相关时,不同的操作都实现了一个IOperation接口,于是,我们可以在父类User中定义一个IOperation数组来存放相关操作(不初始化,也不给出元素数量),在各自子类用户中完成初始化。
5.1.2 public class NormalUser 、public class AdminUser(子类)
在两个子类中,我们写带参构造方法帮助父类完成初始化,并各自完成对IOperation数组的赋值,并且,重写各自的菜单方法。
AdminUser中代码:
//管理员用户
public class AdminUser extends User{
public AdminUser(String name) {
//帮助父类完成初始化
super(name);
//在iOperations数组中按照下标存放对应操作
iOperations = new IOperation[] {
new ExitOperation(),//退出系统
new FindOperation(),//查找图书
new ShowOperation(),//展示图书
new DeleteOperation(),//删除图书
new AddOperation(),//添加图书
new SortByPrice(),//排序图书(通过价格)
new SortByName(),//排序图书(通过书名)
};
}
@Override
//打印菜单(重写方法)
public int menu() {
System.out.println("========图书管理系统========");
System.out.println("====1.查找图书 2.展示图书====");
System.out.println("====3.删除图书 4.添加图书====");
System.out.println("====5.排序图书 0.退出系统====");
System.out.print("请输入您的操作:");
Scanner scanner = new Scanner(System.in);
int choice = scanner.nextInt();
return choice;
}
}
NormalUser中代码:
//普通用户
public class NormalUser extends User{
public NormalUser(String name) {
//帮助父类完成初始化
super(name);
在iOperations数组中按照下标存放对应操作
iOperations = new IOperation[] {
new ExitOperation(),//退出系统
new FindOperation(),//查找图书
new BorrowOperation(),//借阅图书
new ReturnOperation()//归还图书
};
}
@Override
//打印菜单(重写方法)
public int menu() {
System.out.println("========图书管理系统========");
System.out.println("====1.查找图书 2.借阅图书====");
System.out.println("====3.归还图书 0.退出系统====");
Scanner scanner = new Scanner(System.in);
System.out.print("请输入您的操作:");
int choice = scanner.nextInt();
return choice;
}
}
6.1 登录相关
(我们在默认包中创建Test类,在main方法中整合整个体系)
在运行图书系统时,还需要有登录界面:
我们在这里完成登录界面代码的处理,
我们可以单独抽象出一个方法来完成登录相关,需要注意的是,在选择身份后,我们都需要根据这个身份来完成后续的流程操作,这个方法可以以不同身份的对象为返回值,使用父类对象user来接收(向上转型),这样就可以处理返回值类的对象不统一的问题。
其中的代码很简单,相信大家都可以完成。
7.1 操作的处理
不同用户可执行的操作是不一样的,虽然不同用户已经在IOperation数组中初始化好了对于操作,可是,该如何去调用这些操作呢?
我们想到,IOperation数组是在父类中定义的,虽然不同用户初始化的数组不同,可是作为他们的父类,user是都可以访问这个数组的,那我们就可以在父类中写一个方法来完成对数组成员的访问,完成相关的操作。
至此,父类的代码才完成了:
public abstract class User {
protected String name;
protected IOperation[] iOperations;
public User(String name) {
this.name = name;
}
public abstract int menu();
public void doOperation(int choice, BookList bookList) {
if(choice == 5) {
System.out.println();
int sortType = -1;
System.out.println("请选择您想要的排序方式:1->按价格排序 2->按书名排序");
Scanner scanner = new Scanner(System.in);
do {
sortType = scanner.nextInt();
if(sortType != 1 && sortType != 2) {
System.out.println("输入错误,请重写输入!");
}
if(sortType == 2) {
this.iOperations[choice + 1].work(bookList);
return;
}
}while (sortType != 1 && sortType != 2);
}
this.iOperations[choice].work(bookList);
}
}
8.1 操作代码的完成
整体的框架已经搭好了,我们现在来完成操作代码的实现。
8.1.1 增添图书
增加图书的实质就是在Book数组尾添加一个Book对象,这个Book对象就是我们新增添的图书,虽然数组被封装了,但是我们提供了公开的接口可以对数组进行操作,相信这对于大家来说是不难的。(别忘了增加UsedSize的值)
public class AddOperation implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println();
System.out.println("=====增添图书=====");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您所要添加图书的书名:");
String name = scanner.nextLine();
System.out.println("请输入您所要添加图书的作者:");
String author = scanner.nextLine();
System.out.println("请输入您所要添加图书的价格:");
int price = scanner.nextInt();
scanner.nextLine();
System.out.println("请输入您所要添加图书的类型:");
String type = scanner.nextLine();
Book book = new Book(name, author, price, type);
int currentSize = bookList.getUsedSize();
bookList.setBook(currentSize,book);
bookList.setUsedSize(currentSize + 1);
System.out.println("成功添加该图书!!!");
System.out.println();
}
}
8.1.2 借阅图书
我们需要查找到要借阅的那本书,再将那本书的状态修改为true(因为该变量为布尔类型,false为未借出,true为借出)就可以了。(当然,借阅该书的前提为这本书存在并且未被借出,别的细节大家都可以自己来添加)
public class BorrowOperation implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println();
System.out.println("=====借阅图书=====");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您要借阅图书的书名:");
String name = scanner.nextLine();
for (int i = 0; i < bookList.getUsedSize(); i++) {
Book book = bookList.getBook(i);
if(book.getName().equals(name)) {
if (book.isBorrowed() == true) {
System.out.println("很抱歉,该图书已被借阅!!!");
System.out.println();
return;
}
book.setBorrowed(true);
System.out.println("借阅图书成功!!!");
System.out.println();
return;
}
}
System.out.println("没有查找到该图书!!!");
System.out.println();
}
}
8.1.3 删除图书
同样,我们需要先找到那本书,接着这本书将后面的书往前移动,覆盖掉这本书就可以了,最后要将末尾的引用置空,防止末尾引用仍有指向的数据。(别忘了减少UsedSize的值)
public class DeleteOperation implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println();
System.out.println("=====删除图书=====");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您要删除图书的书名:");
String name = scanner.nextLine();
int currentSize = bookList.getUsedSize();
if(currentSize == 0) {
System.out.println("本图书管理系统中未存储任何图书,无法进行删除操作!!!");
System.out.println();
return;
}
for (int i = 0; i < bookList.getUsedSize(); i++) {
Book book = bookList.getBook(i);
if (book.getName().equals(name)) {
for (int j = i; j < currentSize - 1; j++) {
Book book1 = bookList.getBook(j + 1);
bookList.setBook(j, book1);
}
bookList.setBook(currentSize - 1,null);
bookList.setUsedSize(currentSize - 1);
System.out.println("成功删除该图书!!!");
System.out.println();
return;
}
}
System.out.println("没有查找到该图书!!!");
System.out.println();
}
}
8.1.4 退出系统
我们直接调用系统提供的exit方法就可以完成退出。
public class ExitOperation implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println("退出系统!");
System.exit(0);
}
}
8.1.5 查找图书
输入想要查找图书的名称,循环遍历Book数组,找到后打印这本书的信息就可以了。(我们已经在Book类中重写了toString方法)
public class FindOperation implements IOperation {
@Override
public void work(BookList bookList) {
System.out.println();
System.out.println("=====查找图书=====");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您要查找图书的书名:");
String name = scanner.nextLine();
for (int i = 0; i < bookList.getUsedSize(); i++) {
Book book = bookList.getBook(i);
if (book.getName().equals(name)) {
System.out.println("成功查找到该图书!!!");
System.out.println(book);
System.out.println();
return;
}
}
System.out.println("没有查找到该图书!!!");
System.out.println();
}
}
8.1.6 归还图书
与借阅图书相同,遍历找到那本书后,将状态修改为false即可。
public class ReturnOperation implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println();
System.out.println("=====归还图书=====");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入您要归还图书的书名:");
String name = scanner.nextLine();
for (int i = 0; i < bookList.getUsedSize(); i++) {
Book book = bookList.getBook(i);
if(book.getName().equals(name)) {
if (book.isBorrowed() == false) {
System.out.println("该图书还未被借阅!!!");
System.out.println();
return;
}
book.setBorrowed(false);
System.out.println("归还图书成功!!!");
System.out.println();
return;
}
}
System.out.println("没有查找到该图书!!!");
System.out.println();
}
}
8.1.7 展示图书
我们只需要在遍历数组时,打印出当前图书的信息即可。
public class ShowOperation implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println();
System.out.println("=====展示图书=====");
for (int i = 0; i < bookList.getUsedSize(); i++) {
Book book = bookList.getBook(i);
System.out.println(book);
}
System.out.println();
}
}
8.1.8 排序图书
在完成排序图书时,由于我实现了两种不同的排序方式(通过书名和书的价格),所以利用了比较器进行排序,这样可以使比较方式更加灵活。
public class NameComparator implements Comparator<Book> {//用书名来比较
@Override
public int compare(Book o1, Book o2) {
return o1.getName().compareTo(o2.getName());
}
}
public class PriceComparator implements Comparator<Book> {//用价格来比较
@Override
public int compare(Book o1, Book o2) {
return o1.getPrice() - o2.getPrice();
}
}
以书名排序代码:
public class SortByName implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println();
NameComparator nameComparator = new NameComparator();
int currentSize = bookList.getUsedSize();
for (int i = 0; i < currentSize - 1; i++) {
boolean flg = false;
for (int j = 0; j < currentSize - 1 - i; j++) {
Book book1 = bookList.getBook(j);
Book book2 = bookList.getBook(j + 1);
if (nameComparator.compare(book1,book2) > 0) {
bookList.setBook(j,book2);
bookList.setBook(j + 1,book1);
flg = true;
}
}
if(flg == false) {
break;
}
}
System.out.println("所有图书已按书名排序成功!!!");
System.out.println();
}
}
以价格排序代码:
public class SortByPrice implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println();
PriceComparator priceComparator = new PriceComparator();
int currentSize = bookList.getUsedSize();
for (int i = 0; i < currentSize - 1; i++) {
boolean flg = false;
for (int j = 0; j < currentSize - 1 - i; j++) {
Book book1 = bookList.getBook(j);
Book book2 = bookList.getBook(j + 1);
if(priceComparator.compare(book1,book2) > 0) {
bookList.setBook(j,book2);
bookList.setBook(j+1,book1);
flg = true;
}
}
if(flg == false) {
break;
}
}
System.out.println("所有图书已按价格排序成功!!!");
System.out.println();
}
4、总结
到这里,图书管理系统就已经完成了,其实就是将我们SE语法部分的知识串了一遍,如果大家掌握的好的话,完成这个应该是没有难度的。
完整的代码在博主的码云上哦:小丁爱养花/JavaSE - Gitee.com
最后,我想对大家以及自己说几句话:
静下心来,才能越走越远,才会越来越强。
不求一步登天,相信一切都会水到渠成。
及时当勉励,岁月不待人~