专栏:JavaSE
简介:一名失败且爱整活的大学生,希望能分享给你一份我的快乐,up将会持续更新失败的经历以及相关学习博客......
wx:xszm1001Dove1110
如需.Java文件源代码可加vx或关注up发送图书管理系统即可获取
一、问题背景
设计一个图书管理系统,进入该系统时根据身份是管理员or普通用户然后返回相应的功能菜单,要求:管理员可以进行查找图书,增加图书,删除图书,显示图书,等功能。普通用户可以进行查找图书,借阅图书,归还图书等功能
二、总体设计思想
首先,要设计出一个简易图书管理系统,里面就涉及到了面向对象的思想,我们可以先创建Book类,再可以建一个BookList类模拟书架存放各类书籍,跟书有关的类我们可以放在一个book包下,然后我们还要创建使用该系统的对象,分为管理员和普通用户类,同理,将这两个类放在一个user包下,由于管理员和普通用户有相同属性,我们可以创建一个User父类,抽取子类相同属性,再让这两类继承父类User,最后我们可以根据管理员以及普通用户具有的相关操作创建不同的类去模拟该操作实现的功能,由于上述操作均是对书架进行处理,所以我们可以创建一个IOperation的接口,再将相关操作类去实现这个接口,最后根据这个框架去填写相关的业务代码即可(ps:本次就使用Java语言中的封装性、继承、多态、接口等知识点,来综合性的设计与实现出一个简易的图书管理系统)
三、图书管理系统菜单
管理员菜单
1.查找图书
2.新增图书
3.删除图书
4.显示图书
0.退出系统
----------------------------------------
普通用户菜单
1.查找图书
2.借阅图书
3.归还图书
0.退出系统
四、实现基本框架
1.预备工作
先创建一个新的项目,命名为J20240409-library-system,再在这个项目下创建模块,命名为day0409,紧接着由之前的设计思路,我们可以先创建好三个包,分别命名为book、user、operation
2.完善book包中的类
欧克,后面我们根据开始的设计思路开始先写book的相关代码,于是,我们在book包中新建一个Book类,(Book属性:书名、作者、价格、类型、是否借出)
private String name;//private 体现封装性
private String author;
private double price;
private String type;
private boolean isBorrowed;//默认为false;
接着,我们可以使用idea自带的快捷自动生成GetAndSet方法,然后再写构造方法,由于boolean类型的成员变量默认为fasle,所以新增图书的时候,即在构造方法里面不需设置最后一个参数,最后,我们可以根据实际需求重写一下toString方法,然后一个标准的Javabeen类就生成了(无参构造可创可不创建)
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 String getAuthor() {
return author;
}
public double getPrice() {
return price;
}
public String getType() {
return type;
}
public boolean isBorrowed() {
return isBorrowed;
}
public void setName(String name) {
this.name = name;
}
public void setAuthor(String author) {
this.author = author;
}
public void setPrice(double price) {
this.price = price;
}
public void setType(String type) {
this.type = type;
}
public void setBorrowed(boolean borrowed) {
isBorrowed = borrowed;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", type='" + type + '\'' +
", isBorrowed=" +
((isBorrowed==true)?"已借出":"未借出") +
'}';//三目运算符怎么写?
}
现在Book类我们已经写好了,我们开始写BookList类,分析一下BookList(书架的属性)应该要有Book[]数组存放书籍,以及该书架里面书的数量(usedSize),跟前面类似,我们使用快捷生成方式生成一个标准的JavaBeen类,并且在无参构造方法中先初始化,将三本书先放入Book[]中,并初始化usedSize大小。
private Book books[];//为什么不可以这样private Book bookList[10];可以private Book bookList[]=new Book[10]
private int usedSize;
// public BookList(Book[] books, int usedSize) {
// this.books = new Book[10];
// this.usedSize = usedSize;//??
// }
//应该先想好是有参构造还是无参构造;
public BookList() {
//关于舒适化代码的一些细节?
this.books=new Book[10];
this.books[0]= new Book("三国演义","zm",12,"小说");
this.books[1]= new Book("水浒传","zd",22,"小说");
this.books[2]= new Book("Java","zx",52,"学习");
//如何初始化书架
// this.books=new Book[3]{
// {"三国演义","zm",12,"小说",false}
// {"水浒传","zd",19,"小说",false}
// {"java","zx",52,"小说",false}
// };
this.usedSize=3;
}
public Book[] getBooks() {
return books;
}
public int getUsedSize() {
return usedSize;
}
//修改了原始setBook方法
public void setBooks(Book book,int pos) {
this.books[pos] =book;
}
public Book getBooks(int pos){
return this.books[pos];
}
public void setUsedSize(int usedSize) {
this.usedSize = usedSize;
}
注意,上述代码的getBooks和setBooks均有重写过,idea自动生成的代码为:
public Book[] getBooks() {
return books;
}
public void setBooks(Book[] books) {
this.books = books;
}
至于为何要进行重写我们后面会讲到,其实也不难想,自动生成的get和set方法是针对与Book[],而我们在进行查找图书或者删除图书时会根据下标进行查询或删除,等操作,所以进行修改后,更方便实现业务逻辑。到此,和book包相关的代码我们写完了,我们可以开始写关于user包中三个类的代码了。
3.完善user包中的类
由于普通用户和管理员均有共同属性,我们可以把这些共性抽取出来,创建一个User父类,然后这个父类的属性有(姓名),由于要实现不同的身份有不同的功能操作,我们可以考虑再父类中再增加一个接口数组IOperation[],然后在子类的构造方法中可以进行初始化,然后又由于考虑到无论是普通用户还是管理员对菜单进行功能选择然后需要调用相应的功能类去执行处理,这里我们可以运用多态的思想,不同的对象调用同一个方法会产生不一样的行为,我们可以在父类写一个doOperation方法,但这个方法不需要具体的实现,所以可以定义成抽象方法,并把类改成抽象类,由于是通过菜单选择功能,参数我们可以设置(int choice,BookList books),这样便可以实现不同的身份有不同的功能菜单,并可以通过选项进行相应的功能执行。
public abstract class User {
private String name;
//考虑初始化的一些相关问题
//由于不知道操作功能的数量,不在这初始化分配空间容量
protected IOperation[] iOperations;
public User(String name) {
this.name = name;
}
// public User user(int choice,String name){
// if(choice==1)
// return new AdminUser(name);
// else
// return new NormalUser(name);
// }
public abstract int menu();
public abstract void doOperation(int choice, BookList books);
}
接下来我们可以再去创建子类AdminUser和NormalUser,根据上述设计思路编写相应的代码:
public class AdminUser extends User {
private String name;//通过继承可以得到是否需要再子类中再写
//相同的成员变量
public AdminUser(String name) {
super(name);
this.iOperations=new IOperation[]{
new ExitOperation(),
new FindOperation(),
new AddOperation(),
new DelOperation(),
new ShowOperation()
};
}
@Override
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);
return scanner.nextInt();
}
@Override
public void doOperation(int choice,BookList books) {
this.iOperations[choice].work(books);
}
}
public class NormalUser extends User{
private String name;
public NormalUser(String name) {
super(name);//super要在第一行
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);
return scanner.nextInt();
}
public void doOperation(int choice, BookList bookList) {
this.iOperations[choice].work(bookList);
}
}
值得注意的是里面的一些小细节,我们可以在User的子类构造方法根据菜单编号来对IOperation[]进行初始化;
4. 完善operation包中的功能类
先创建一个IOperation接口,将其他类进行规范
public interface IOperation {//接口是对一些类的规范,当user调用了接口就相当于有了接口的功能特性
void work(BookList bookList);//接口中的变量默认为public final static;方法默认为public abstract,接口与抽象类的区别:
}
4.1 AddOperation(增添图书)
这个类逻辑实现较为简单,但要注意添加完图书后,应该把BookList.usedSize+1
public class AddOperation implements IOperation {
public void work(BookList bookList){
System.out.println("添加图书");
System.out.println("请输入你要添加的图书名字:");
Scanner scanner=new Scanner(System.in);
//如果已经有了该图书,那么如何扩展,存储数量
String name=scanner.next();//nextLine??
System.out.println("请输入你要添加的图书的作者:");
String author=scanner.next();
System.out.println("请输入你要添加的图书价格:");
Double price=scanner.nextDouble();
System.out.println("请输入你要添加的图书类型:");
String type=scanner.next();
int curSize=bookList.getUsedSize();
bookList.setBooks(new Book(name,author,price,type),curSize);
bookList.setUsedSize(curSize+1);
System.out.println("添加成功");
}
}
4.2BorrowOperation(借阅图书)
这个类的实现逻辑也较为简单,值得注意的是在for循环完后再判断书架中是否有要找的书,以及未找到后将重新返回最初的页面,这个逻辑可以使用递归思想完成
public class BorrowOperation implements IOperation{
public void work(BookList bookList){
System.out.println("借阅图书");
System.out.println("请输入你要借阅的图书名字:");
Scanner scanner=new Scanner(System.in);
String name=scanner.next();//nextLine()区别
int curSize= bookList.getUsedSize();
int i=0;
for (; i < curSize; i++) {
//这行代码如何编辑
if(name.equals(bookList.getBooks(i).getName())==true&&
bookList.getBooks(i).isBorrowed()==false){
bookList.getBooks(i).setBorrowed(true);
System.out.println(name+"借阅成功");
//如何进一步模拟实现书架业务逻辑
return;
} else if (name.equals(bookList.getBooks(i).getName())==true&&bookList.getBooks(i).isBorrowed()==true) {
System.out.println("该书已被借阅,将自动返回最初页面");
work(bookList);
//return;
//work(bookList);
//return;//return?
}
//如何实现重写的业务逻辑
}
System.out.println("你输入的图书名字有误,请重写,将自动返回最初页面");
work(bookList);
}
// if(i>=curSize){//如何实现重写的业务逻辑
// System.out.println("你输入的图书名字有误,请重写");
// }
// else{
// System.out.println("该书已经被借出");
// }
}
4.3DelOperation(删除图书)
这类实现的逻辑稍微要复杂一点,要分类讨论一下,如果要删除的是最后一本书的话,那么直接将最后一本书设置为null就行,否则就将后面的书把要删除的书进行覆盖,再把books[curSize-1]设置null,回收不用的空间(ps:当然也可以不用分类讨论,在for循环外面判断有是否要删除的书,并且可以在最后把书置为null)
public class DelOperation implements IOperation{
@Override
public void work(BookList bookList) {
System.out.println("请输入你要删除的图书名字:");
Scanner scanner=new Scanner(System.in);
String name=scanner.next();
int curSize=bookList.getUsedSize();
//处理特殊情况,如果删除的恰好是最后一本书
if(bookList.getBooks(curSize-1).getName().equals(name))
{
bookList.setBooks(null,curSize-1);
bookList.setUsedSize(curSize-1);
System.out.println("删除图书成功");
return;
}
int i = 0;
for (; i < curSize-1; i++) {
if(bookList.getBooks(i).getName().equals(name)){
for (int i1 = i; i1 < curSize-1; i1++) {
bookList.setBooks(bookList.getBooks(i1+1), i1);
}
bookList.setBooks(null,curSize-1);
bookList.setUsedSize(curSize-1);
System.out.println("删除图书成功");
return;
}
}
if(i==curSize-1)//for循环后i==curSize-1
System.out.println("无此书,将自动返回最初页面");
work(bookList);
}
}
4.4ExitOperation(退出系统)
public class ExitOperation implements IOperation{
public void work(BookList bookList){
System.out.println("退出图书系统");
System.exit(0);
}
}
4.5FindOperation(查询图书)
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.next();
int curSize=bookList.getUsedSize();
int i = 0;
for (; i < curSize; i++) {
if(bookList.getBooks(i).getName().equals(name)){
System.out.println(bookList.getBooks(i));
return;
}
}
if(i==curSize)
System.out.println("无此书,将自动返回最初页面");
work(bookList);
}
}
4.6ReturnOperation(归还图书)
public class ReturnOperation implements IOperation{
public void work(BookList bookList){
System.out.println("归还图书");
System.out.println("请输入你要归还的图书名字:");
Scanner scanner=new Scanner(System.in);
String name=scanner.next();
int curSize=bookList.getUsedSize();
int i = 0;
for (; i < curSize; i++) {
if(bookList.getBooks(i).getName().equals(name)){
bookList.getBooks(i).setBorrowed(false);
System.out.println("归还成功");
//break;
return;
}
}
if(i==curSize)//扩展:如果无此书的话如何再次重复此程序?
{
System.out.println("无此书");
work(bookList);//类似于递归
}
}
}
4.7ShowOperation(显示图书)
public class ShowOperation implements IOperation{
public void work(BookList bookList){
System.out.println("显示图书");
int curSize=bookList.getUsedSize();
for (int i = 0; i < curSize; i++) {
System.out.println(bookList.getBooks(i));
}
}
}
5.Main方法
再写main方法的时候,我们可以先创建一个静态方法login,(方法类型先可以使用void,但是后面我们可以用这个静态方法根据用户身份而创建一个对象并返回,所以类型最终应该是User(向上转型))该方法中模拟登入系统页面,让用户输入姓名,并且确认身份,然后再根据身份去创建相应的对象 ,但是只有在程序运行的时候才知道应该创建哪个对象,(动态绑定) ,我们可以在main方法中创建父类User对象去接收login返回的对象,再利用多态调用menu方法,从而给用户页面返回了相应身份的菜单,然后menu方法可以再返回用户的选项,再定义一个局部变量int choice 去接收menu()的返回值,最后可以调用doOperation方法实现相应功能。
在main方法中调用
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) {
//while如果全包括,那么会创建新的对象,之前新增书籍在上一个对象里面
BookList bookList=new BookList();//书架应该放在while外面
while (true){
User user= login();
//如何实现根据不同的身份返回不同的菜单
//运用多态的思想,然后父类menu方法又不需实现,可以定义为抽象方法
int choice=user.menu();
//如何根据菜单选择实现对应的业务需求
user.doOperation(choice,bookList);
}
}
}
五、实现该图书系统所遇到的一些小问题和相关知识点:
小问题
1. 在Java语言中,用双引号括起来的是字符串,而用单引号括起来的是字符。所以 '\'' 表示的是单引号字符。 在 Java 中,反斜杠(\)是转义字符,表示后面的字符具有特殊的意义。因此,'\' 表示字符串中的一个单引号字符。 Java 使用这种方式来区分字符和字符串的表示方式。
2.关于scanner.next()和scanner.nextLine()的区别,后者会把回车给吃进去(个人理解:next()只读取有效字符,也就是不能得到带有空格的字符串,而nextLine()均读取,如空格,tab,以及回车)
3.关于权限问题
这段代码定义了一个名为User的类,其中包含了一个受保护的成员变量name和一个公有的构造方法。受保护的成员变量意味着它可以在同一个包内的类以及继承User类的子类中访问,但不能在其他包中的类中直接访问。而公有的构造方法可以被任何类访问。通过这种访问权限的设置,可以控制数据的安全性和封装性。
当一个类继承另一个类时,有一些重要的注意事项需要考虑:
3.1. 访问权限:子类只能继承父类中不是私有的属性和方法。私有的属性和方法无法被继承。受保护的属性和方法可以在子类中访问,但是只有在同一包或者子类中。
3.2. 构造方法:子类会默认调用父类的无参构造方法,如果父类没有无参构造方法并且没有显式调用父类的其他构造方法,会导致编译错误。要确保父类中有适当的构造方法供子类调用。
3.3 方法重写:子类可以对父类的方法进行重写,即子类可以提供与父类同名但具有不同实现的方法。重写方法必须和父类的方法具有相同的返回类型和参数列表。
3.4. super关键字:子类可以使用super关键字调用父类的构造方法和方法,以及访问父类的属性。
继承是面向对象编程中的重要概念,正确的使用继承可以提高代码的复用性和可维护性。在设计父类时,需要考虑好哪些属性和方法应该对子类可见,以及是否应该设计为final防止被子类修改。
4.调试问题
4.1行断点,断点断点,所谓断点就是程序会在打断点的这一行停下来
4.2详细断点(在多线程的时候会用到较多)
4.3方法断点:会在方法的首行停下来以及末尾行停下来,方便查看这个方法里面有什么问题,以及看方法的返回值是否满足自己的一个预期,在接口方法上打断点,程序会直接跳转到实现类中,当实现类比较多的时候,就比较方便了
4.6异常断点:程序自动在异常的地方停止下来
4.7字段断点:在成员变量上打断点,方便查看该变量在生命周期里面的变化过程
回顾所学知识点
1.抽象类和接口的区别和一些注意事项:
抽象类可以有普通的成员变量,可以有普通的成员方法,但是接口的话(成员变量??)只能被abtract final static 修饰,只能有抽象方法,抽象类是is-a的形式,而接口是XXX具有这样的一个特点
抽象类和接口是面向对象编程中的两个重要概念。
1. 区别:
- 抽象类是一个类,可以有构造函数、成员变量和方法,其中的方法可以包含具体的实现和抽象的方法声明。而接口是一组抽象方法的集合,不能包含成员变量或具体方法的实现。
- 一个类可以实现多个接口,但只能继承一个抽象类。
- 抽象类可以包含普通的方法,而接口只能包含抽象方法。
- 在 Java 中,抽象类使用关键字 "abstract" 声明,而接口使用关键字 "interface" 声明。
2. 注意事项:
- 当一个类具有共享行为或状态,并且需要被多个类继承时,通常使用抽象类。
- 当一个类需要实现多个无关联的行为时,使用接口可以更灵活地实现多态性。
- 接口用于定义类需要遵循的契约,而抽象类用于提供子类共同的实现。
2.抽象类和父类的区别:
抽象类是一种特殊的类,它不能被实例化,只能被用作其他类的父类。抽象类中可以包含抽象方法和具体方法,而普通的父类可以被实例化,它中的方法都是具体的。另外,子类继承抽象类时必须实现所有的抽象方法,而对于普通的父类,则可以选择性地覆盖父类的方法。