硅基计划2.0 学习总结 玖 图书管理系统 初版

发布于:2025-07-17 ⋅ 阅读:(17) ⋅ 点赞:(0)

1752388689513



一、单例模式

定义:控制类只能有一个实例,并且能够提供全局访问点,用于严格资源控制

1. 饿汉式

定义:在程序启动的时候就让这单个例子初始化,好处是安全和不用额外开销,坏处就是如果你不去使用这个实例,就会造成资源的浪费
那如何去控制实例的个数呢,很简单,且看演示

//创建一个User类
public class User {
    private static User user = new User();
    //static说明静态成员只有一份,不管new了多少对象都只有一份

    private User(){

    }

    public static User get(){//提供外部调用的唯一接口
        return user;
    }
}
//创建Test类以实例化类
public class Test {
    public static void main(String[] args) {
        User user1 = User.get();
        User user2 = User.get();
        System.out.println(user1 == user2);//true
    }
}

我们可以看到我们提供类一个对外接口,这个接口就是为构造方法服务的,这是外部实例化类的唯一方式
在User类中我们令其构造方法为private修饰,就是保证了不能通过User user = new User();来创建对象
只能User user1 = User.get();通过类去调用get()方法,从而去实例化类
通过打印结果我们看到我们不论创建了多少实例它们本质上都是一样的(因static修饰了)

2. 懒汉式

定义:顾名思义就是你要用的时候才去实例化,不用就放在那,首次访问才初始化
其优点就是不占用资源,缺点就是多线程不安全,待会演示

//创建一个Person类
public class Person {
    private static Person person;

    private Person(){

    }

    public static Person get(){
        if(person == null){//首次调用实例化
            person = new Person();
        }//如果是第二次及以上就不用实例化了
        return person;
    }
}
//在Test类中调用
public static void main(String[] args) {
        Person person = Person.get();
        Person person1 = Person.get();
        System.out.println(person1 == person);//true
    }

现在来回答为什么多线程不安全,注意看get()方法
假设我A用户进来了(此时还没有实例化),实例化好了正准备出去,B用户又进来了(因A用户还未完全实例化好)
明明只能实例化一个,但是现在B用于也实例化了一个,就会造成混乱,但是这个后续我们可以优化

3. 懒汉式例子——公司CEO

//创建一个CEO类
public class CEO {
    private static CEO ceo;
    private String name;

    private CEO(String name){
        this.name = name;
    }

    public static CEO get(String name){
        if(ceo == null){
            ceo = new CEO(name);
        }
        return ceo;
    }

    public void manage(){
        System.out.println(name+"CEO正在管理公司事务");
    }

    public void development(){
        System.out.println(name+"CEO正在统筹公司战略");
    }
}
//创建一个Company类供调用
public class Company {
    public static void main(String[] args) {
        CEO ceo = CEO.get("张三");
        ceo.development();
        ceo.manage();
    }
}

张三CEO正在统筹公司战略 张三CEO正在管理公司事务

二、工厂模式

定义:创建对象过程与使用对象过程进行分离,达到解耦目的
用一个专门的类对创建对象进行封装
分为简单工厂、工厂方法、抽象工厂三大模式
在非工厂模式中,加入你的类内部变量发生了变动,很多地方都受到牵连,都要改

我们先演示非工厂模式弊端,我们创建一个抽象类User,定义一些变量
再创建两个类ManagePersonOrdinaryPerson去继承User类,再定义个Test去实例化对象

public abstract class User {
    protected String name;
    protected int id;

    public User(String name, int id) {
        this.name = name;
        this.id = id;
    }
}
public class ManagePerson extends User{
    public ManagePerson(String name, int id) {
        super(name, id);
    }
}
public class OrdinaryPerson extends User{
    public OrdinaryPerson(String name, int id) {
        super(name, id);
    }
}
public class Test {
    public static void main(String[] args) {
        ManagePerson managePerson = new ManagePerson("张三",10010);
        OrdinaryPerson ordinaryPerson = new OrdinaryPerson("李四",10086);
    }
}

当我们在抽象类User中去添加新的用户类型的时候,就要重新写构造方法,传参之类的,很麻烦
因此,如果我们使用工厂模式,我们先想,工厂是怎么运作的,是不是要做出产品,而这个产品正好对于我们的对象
因此我们可以知道:每个用户类型(管理员ManagePerson和普通用户OrdinaryPerson)都分别可以做成一个工厂,想要添加新的用户类型,只需要添加新的工厂而无需修改原本代码

我们创建一个抽象工厂类接口PersonFactory,只写User用户类的对象构造User user(String name,int id);
我们再创建两个工厂类ManagePersonFactory类和OrdinaryPersonFactory类中实现PersonFactory接口
return返回值就返回创建当前类的对象,即return new 类(参数)
并且工厂类中不能出现构造方法,这个是在对应的用户类中才有的

//User用户抽象类
public abstract class User {
    protected String name;
    protected int id;

    public User(String name, int id) {
        this.name = name;
        this.id = id;
    }
}
//管理员类ManagePerson
public class ManagePerson extends User{
    public ManagePerson(String name, int id) {
        super(name, id);
    }
}
//普通用户类OrdinaryPerson
public class OrdinaryPerson extends User {
    public OrdinaryPerson(String name, int id) {
        super(name, id);
    }
}
//用户工厂类接口PersonFactory,声明`User`方法
public interface PersonFactory {
    User user(String name,int id);
}
//管理员工厂类ManagePersonFactory,实现用户类接口,重写方法
public class ManagePersonFactory implements PersonFactory {
    @Override
    public User user(String name, int id) {
        return new ManagePerson(name,id);
    }
}
//普通用户工厂类OrdinaryPersonFactory,实现用户类接口,重写方法
public class OrdinaryPersonFactory implements PersonFactory {
    @Override
    public User user(String name, int id) {
        return new OrdinaryPerson(name,id);
    }
}
//定义测试类用于实例化对象
public class Test {
    public static void main(String[] args) {
        PersonFactory person1 = new ManagePersonFactory();
        person1.user("张三",10010);
        PersonFactory person2 = new OrdinaryPersonFactory();
        person2.user("李四",10086);
        PersonFactory person3 = new OrdinaryPersonFactory();
        person3.user("王五",10000);
    }
}

此时,如果还有其他用户类型,直接写新的用户类和用户类工厂就好了,无需修改原代码,是不是很方便?

三、代理模式

定义:通俗理解成中介,这个类可以去代理另一个类的功能,分为静态、动态以及CGLIB
我们本次以静态为例

想想我们刚刚写的代码,我们不同类型的用户是不是有不同的菜单呢
因此我们在用户类中加入打印菜单方法public abstract int display();,注意返回类型是int

再在普通用户类和管理员类中分别重写方法和定义一个专属方法(比如普通用户借书,管理员上架书等等)
此时我们就达成了普通用户和管理员之间的差异化
再定义个代理类Broker,定义一个私有变量realName用于表示用户真实姓名,再写个构造方法,用于向上转型指向相对应的用户类型
再在代理类中定义所有用户类型的所有可能方法,每个方法再判断用户类型权限
在测试类的main方法中注意我们还要利用向上转型和通过工厂对象调用user方法完成对象创建
此时,我们再创建代理类对象,传参传对应的用户类型的对象,完整代码如下
仅展示改动的代码,其他代码与之前代码一致

//新建一个代理类Broker
public class Broker {
    private User realName;//用来表示真正的用户名字

    public Broker(User user){
        realName = user;
    }//运用到了向上转型,因为User是所有用户类的父类

    //再判断对象实例化过程中用户的类型,即检查对应权限
    //要把所有用户类型的所有可能的方法都写这
    public void borrowBook(){
        if(realName instanceof OrdinaryPerson) {
            ((OrdinaryPerson)realName).borrowBook();
            //可以强转,因为类型以及判断,就是把realName转成对应类型
        }else if(realName instanceof ManagePerson){
            System.out.println("管理员不用借书");
        }else{
            System.out.println("错误");
        }
    }

    public void helvesBook(){
        if(realName instanceof ManagePerson) {
            ((ManagePerson)realName).shelvesBook();
            //可以强转,因为类型以及判断,就是把realName转成对应类型
        }else if(realName instanceof OrdinaryPerson){
            System.out.println("普通用户不能上架图书");
        }else{
            System.out.println("错误");
        }
    }
}
//在User类中加入展示菜单`dispaly()`方法
public abstract class User {
    protected String name;
    protected int id;

    public User(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public abstract int display();
}
//其他两个用户类再重写
public class ManagePerson extends User{
    public ManagePerson(String name, int id) {
        super(name, id);
    }

    @Override
    public int display() {
        return 0;
    }

    public void shelvesBook(){
        System.out.println(name+"正在上架图书");
    }
}
public class OrdinaryPerson extends User {
    public OrdinaryPerson(String name, int id) {
        super(name, id);
    }

    @Override
    public int display() {
        return 0;
    }

    public void borrowBook(){
        System.out.println(name+"在借书");
    }
}
//再在测试类中的main方法中调用
public class Test {
    public static void main(String[] args) {
        PersonFactory personFactory = new ManagePersonFactory();
        User managePerson = personFactory.user ("张三",10010);
        PersonFactory person2 = new OrdinaryPersonFactory();
        User ordinaryPerson = person2.user("李四",10086);
        System.out.println("===以下是代理模式===");
        Broker brokerOrdinary = new Broker(ordinaryPerson);
        brokerOrdinary.borrowBook();
        brokerOrdinary.helvesBook();//权限测试
        System.out.println("------------");
        Broker brokerManage = new Broker(managePerson);
        brokerManage.borrowBook();//权限测试
        brokerManage.helvesBook();
    }
}

运行结果:
=以下是代理模式=
李四在借书
普通用户不能上架图书
------------
管理员不用借书
张三正在上架图书

四、jar包的使用

先打开需要导入jar包的项目,先创建一个名为lib的文件
image-20250714184711717
再找到想导入的jar包,放在lib目录下
导入成功后再点击上方文件-项目结构
image-20250714185402927
再点击库,加号导入,从Java,导入刚刚jar包,确定->应用再确定即可
image-20250714185614006
虽然jar内部是字节码文件,但是Idea会反编译成Java文件,从此我们直接调用这个jar包即可(类实例化对象)

五、项目模块

本项目有四大类:用户——user,书籍——book,utils——很多工具,数值常量——constant

1. 用户模块优化

但是在用户模块,还是要优化下,添加指示用户类型的变量,提供对应的get方法
而set方法不用提供,因为这个是抽象类,子类我们会在对应的用户类型的类的构造方法中明确用户类型

//在User类中
public abstract class User {
    protected String name;
    protected int id;
    protected String role;

    public User(String name, int id, String role) {
        this.name = name;
        this.userID = userID;
        this.role = role;
    }
    
    //提供对外get方法
    public String getName() {
        return name;
    }

    public int getUserID() {
        return userID;
    }

    public String getRole() {
        return role;
    }

    //定义菜单⽅法,该⽅法打印菜单同时能够去输出菜单的选择
    public abstract int display();//在子类的用户类型中再重写
}

同时把管理员的特有方法完善下

//在ManagePerson(管理员类)类构造方法中
public class ManagePerson extends User{
    public ManagePerson(String name, int id) {
        super(name, id,"管理员");//直接写用户类型,因此不需要在User类中传参
    }
    
    @Override
    public int display() {
        System.out.println("管理员 " + name + " 的操作菜单:");
        System.out.println("1. 查找图书");
        System.out.println("2. 打印所有的图书");
        System.out.println("3. 退出系统");
        System.out.println("4. 上架图书");
        System.out.println("5. 修改图书");
        System.out.println("6. 下架图书");
        System.out.println("7. 统计借阅次数");
        System.out.println("8. 查看最后欢迎的前K本书");
        System.out.println("9. 查看库存状态");
        System.out.println("10. 按类别统计图书 ");
        System.out.println("11. 按作者统计图书 ");
        System.out.println("12. 检查超过⼀年未下架的图书");
        System.out.println("请选择你的操作:");
        return scanner.nextInt();
    }
    
    //接下来是一些管理员的特有方法
    public void addBook() {//上架书
    }
    
    public void updateBook() {//图书修改——书名作者还有类别
    }
    
    public void removeBook() {//移除书籍
    }
    
    public void borrowCount() {//统计每本书借了几次
    }
    
    public void generateBook() {//检查最受大众喜爱的几本书
    }
    
    public void checkInventoryStatus() {//查看库存
    }
    
    public void checkAndRemoveOldBooks() {//移除上架了一年的旧书
    }
    
    public void exit() {//程序退出方法
    }

同时再把普通用户特有方法再完善下

//同理在OrdinaryPerson(普通用户类)类构造方法中
public class OrdinaryPerson extends User {
    public OrdinaryPerson(String name, int id) {
        super(name, id,"普通用户");//直接写用户类型,因此不需要在User类中传参
    }
    
    private void loadBorrowedBook() {//加载借阅的书籍
    }
    
    private void storeBorrowedBook() {//存储借阅的书籍
    }
    
    @Override
    public int display() {
        System.out.println("普通⽤⼾ " + name + " 的操作菜单:");
        System.out.println("1. 查找图书");
        System.out.println("2. 打印所有的图书");
        System.out.println("3. 退出系统");
        System.out.println("4. 借阅图书");
        System.out.println("5. 归还图书");
        System.out.println("6. 查看当前个⼈借阅情况");
        System.out.println("请选择你的操作:");
        return scanner.nextInt();//返回值取决于你选择了什么
    }
    
    //再在定义一些特有方法
    public void borrowBook() {//借书
    }
    
    public void returnBook() {//还书
    }
    
    public void viewBorrowBooks() {//查看个人目前的借阅情况
    }
}

2. 用户类型创建模块工厂模式化(工厂包底下)

对于用户类型创建的工厂模式,我们之前已经给出了代码,这里不再赘述,只给出代码

定义一个factory包存放,首先先创建一个总的用户类型模块的接口

public interface PersonFactory {
    User user(String name,int id);
    //为了保证以后新增的用户类型,因此只有两个参数
    //第三个参数已经在对应的用户类型的类内定好了
}

我们再定义两个具体的用户类型的工厂类ManagePersonFactory(管理员类工厂)OrdinaryPersonFactory(普通用户类工厂)

//ManagePersonFactory(管理员类工厂)
public class ManagePersonFactory implements PersonFactory {
    @Override
    public User user(String name, int id) {
        return new ManagePerson(name,id);
    }
}
//OrdinaryPersonFactory(普通用户类工厂)
public class OrdinaryPersonFactory implements PersonFactory {
    @Override
    public User user(String name, int id) {
        return new OrdinaryPerson(name,id);
    }
}

我们把之前的Test(测试类)改名成LibrarySystem(图书系统)
这个类在src目录下,不属于任何一个模块的包,因为说白了我们后续启动程序就在这启动

但是这样你想想,我们每次都要去new,然后呢如果代码改动大的话盖起来很麻烦
因此为了程序易读性和简洁性,我们使用代理模式去创建对象

因此我们在factory包下面新建一个代理类Broker
给定一个成员变量realName来说明这个代理对象真实指向的是哪个用户类型
在构造方法中再使用向上转型来自动确定代理的用户类型,再提供get方法
我们再在代理类中继承普通用户和管理员的所有可能的方法,为后续调用代理类打下基础

public class Broker {//代理类
    private User realName;//用来表示真正的用户名字
    
    public Broker(User user) {
        realName = user;
    }//运用到了向上转型,因为User是所有用户类的父类
    
    public User getRealUser() {//获取用户真实的信息
        return realName;
    }
    
    public int display() {//选择菜单
    }
    
    public void addBook() {//添加书籍
    }
    
    public void updateBook() {//更新书籍
    } 
    
    public void removeBook() {//移除图书
    } 
    
    public void borrowCount( ) {//查看书借阅次数
    } 
    
    public void generateBook() {//查看最受欢迎的前K本书
    } 
    
    public void checkInventoryStatus() {//查看库存状态
    } 
    
    public void checkAndRemoveOldBooks() {//移除上架超1年的旧书籍
    }
    
    //以下是普通用户方法
    public void borrowBook() {//借书
    }
    
    public void returnBook() {//归还图书
    } 
    
    public void viewBorrowHistory() {//查看个⼈借阅情况
    }

因此我们在LibrarySystem类中main方法中改变如下代码

public class LibrarySystem {
    public static void main(String[] args) throws PermissionException {//先不管这个异常,我们后续会说
        
        //工厂模式
        PersonFactory personFactory = new ManagePersonFactory();
        User managePerson = personFactory.user ("张三",1);
        PersonFactory person2 = new OrdinaryPersonFactory();
        User ordinaryPerson1 = person2.user("李四",2);
        User ordinaryPerson2 = person2.user("王五",3);

        System.out.println("===以下是代理模式===");
        Broker brokerManage = new Broker(managePerson);
        Broker brokerOrdinary1 = new Broker(ordinaryPerson1);
        Broker brokerOrdinary2 = new Broker(ordinaryPerson2);
        }
}

因此目前用户类模块我们差不多已经完成了,接下来我们进入到图书模块

3. 图书模块

我们在book包下创建一个book类,用于图书的管理,实现comparable接口,便于后续排序比较
对于变量isBorrowed和borrowCount,默认是flase值和0,我们无需通过构造方法定义
对于bookId,如果我们直接给予,会造成混乱并且不好管理,又要写方法去生成和管理Id,麻烦

public class Book implements Comparable <Book> {
    private int bookId; //书id
    private String title; // 书名
    private String author; // 作者
    private String category; // 类别
    private int publishYear; // 出版年份
    private boolean isBorrowed; // 借阅的状态
    private int borrowCount; // 借阅次数
    private LocalDate shelfDate; // 上架时间

    //构造函数,初始化图书对象 书籍ID、借阅状态和借阅次数刚刚已经说过不用传参
    public Book(String title, String author, String category,
                int publishYear, LocalDate shelfDate) {
        this.title = title;
        this.author = author;
        this.category = category;
        this.publishYear = publishYear;
        this.isBorrowed = false;
        this.borrowCount = 0;
        this.shelfDate = shelfDate;
    }
    
    //再提供各个变量对应的get和set方法便于对外调用
    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public int getPublishYear() {
        return publishYear;
    }

    public void setPublishYear(int publishYear) {
        this.publishYear = publishYear;
    }

    public boolean isBorrowed() {
        return isBorrowed;
    }

    public void setBorrowed(boolean borrowed) {
        isBorrowed = borrowed;
    }

    public int getBorrowCount() {
        return borrowCount;
    }

    public void setBorrowCount(int borrowCount) {
        this.borrowCount = borrowCount;
    }

    public LocalDate getShelfDate() {
        return shelfDate;
    }

    public void setShelfDate(LocalDate shelfDate) {
        this.shelfDate = shelfDate;
    }

    //再重写toString以便打印对象内容
    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", category='" + category + '\'' +
                ", publishYear=" + publishYear +
                ", isBorrowed=" + isBorrowed +
                ", borrowCount=" + borrowCount +
                ", shelfDate=" + shelfDate +
                '}';
    }
    
    //再重写compareTo方法便于后续排序比较借阅的次数
    public int compareTo(Book o){//比较借阅次数,为后期比较做了相关工作
        return o.getBorrowCount()-this.getBorrowCount();
    }
}

书是有了,那我们要有存放书籍的地方啊,因此我们再定义一个Library类来放入书籍
但是呢,书籍就对应数据,我还没学过MySql啊,嗯,只能使用文件读写把信息存在文件中了
我们导入对应的读写jar包,jar包可以去AI下,我也是不太懂,都是叫AI帮忙的(嘿嘿)

因此我们在utils(专门存放工具类)包下定义一个类AnalyzingBook用于写入书籍内容
再在这个类中定义写数据和读数据以及读取数据的时候分析数据

public class AnalyzingBook {//专门管理文件写入即图书存放
    public void storeObject(Book[] books, String filename) throws IOException{//写入图书信息
    } 
    
    public Book[] loadObject(String filename) throws IOException {
    //从⽂件读取图书信息数据
    }
    
    private Book parseBookJson(String json) {//读取的时候分析图书的数据
    }
    
    //我们先来个测试数据,存入四本书
        Book[] books = new Book[4];
        books[0] = new Book("java", "aaa", "编程", 1994, LocalDate.of(2023, 9,24));
        books[1] = new Book("mysql", "ccc", "编程", 1999, LocalDate.of(2024, 2,10));
        books[2] = new Book("php", "bbb", "编程", 2020, LocalDate.of(2023, 9,23));
        books[3] = new Book("西游记", "吴承恩", "⼩说", 2024, LocalDate.of(2023,9, 23));

我们先向下写入数据的格式是什么?
一本书肯定有很多个信息,那么对于每本书,其作者出版年分等属性我们可以使用“,”分隔
而对于很多本书,我们每一行表示一本书既方便读取也方便存储
那在写入书籍的时候我们难道直接写入吗,不行,每本书都有一连串信息,那我们可以用StringBuilder类中的append()方法去把每本书的各个信息拼接起来

因此对于存储书籍信息方法,我们先要遍历下这个书籍的数组,但是书籍有多大就代表有几本书吗
并不是,可能书籍没有存放满,因此我们先用便利去提取有效的数组长度
完毕后再利用SteingBuilder类创建一个临时的数组去拼接信息
每拼接完一本书(即一个书籍类数组对象的一个元素)后以\n\分隔,直到最后一本书不用以\n分隔
但是系统默认结尾还是会给一个换行符,对于toJSON格式化存储,我们待会就来实现

public class AnalyzingBook {//专门管理文件写入即图书存放
    public void storeObject(Book[] books, String filename) {
        //先遍历books数组当中不为空的数据多少个?
        //在Books数组对中,其长度并不代表书籍个数,可能存在存不满情况
        int booksUseLen = 0;
        for (int i = 0; i < books.length; i++) {
            if (books[i] != null) {
                booksUseLen++;
            }
        }

        StringBuilder jsonArray = new StringBuilder();
        for (int i = 0; i < booksUseLen; i++) {
            if (books[i] != null) {
                jsonArray.append(books[i].toJSON());
                if (i != booksUseLen - 1) {
                    //⼀本书籍完成后以\n进⾏分割,直到最后一本书籍分割完毕
                    jsonArray.append("\n");
                }
            }
        }

        //数据写⼊⽂件
        FileUtils.writeFile(jsonArray.toString(), filename);
    }
}

来,我们现在来在Book类中去实现toJSON方法
我们先分析我们存储数据的格式,每本书中每个属性使用逗号分隔
每个逗号的位置的左右两边的都对应好了对应的属性,那我们就可以让Book类中的对应成员变量等于对应位置的“,”的信息即可

public String toJSON() {//说白了就是将每个属性使用都好分隔存储好
        StringBuilder json = new StringBuilder();
        json.append(bookId).append(",");
        json.append(title).append(",");
        json.append(author).append(",");
        json.append(category).append(",");
        json.append(publishYear).append(",");
        json.append(isBorrowed).append(",");
        json.append(borrowCount).append(",");
        json.append(shelfDate != null ? shelfDate.format(DateTimeFormatter.ISO_LOCAL_DATE) : "null");
        return json.toString();
    }

好,文件存储写完了,我们在AnalyzingBook类中继续实现我们的文件读取

我们怎么存的就怎么读取,先以每个换行符为一个个对象进行切割得到每一本书及其信息内容的字符串
我们再把每本书及其信息内容的字符串变成书籍Book类的对象,即Book类对象数组中每个元素都是一本书即一个对象

public Book[] loadObject(String filename) {
        //从⽂件读取数据
        //怎么存的就怎么读,我们先把\n作为一个个书籍的对象进行切割,得到一个个字符串

        //先判断有没有书籍
        String content = FileUtils.readFile(filename);
        if (content == null || content.isEmpty()) {
            System.out.println("File is empty or does not exist: " + filename);
            return null;
        }

        //2. 使⽤\n作为分隔符进⾏字符串分割
        //当我们去读取时候发现最后一行有系统自带的\n
        //我们通过\n分隔来遍历,
        String[] bookJsonStrings = content.split("\n");//临时读取功能的数组

        //3. 把对应字符串“组装”成书籍对象
        Book[] bookList = new Book[bookJsonStrings.length];//有几本书对应几个数组元素,其长度就是多少
        //使得bookJsonStrings数组中每个元素都等于一个book对象
        for (int i = 0; i < bookJsonStrings.length; i++) {
            Book book = parseBookJson(bookJsonStrings[i]);
            bookList[i] = book;
        }

        return bookList;
    }

好,读取数据的时候我们是不是要分析数据对应的都是什么把,因此我们再实现数据分析方法

首先查看信息是不是空的,再把Book类数组每个元素(即每本书)进行分析,对于每本书使用String类的split方法进行切割
再把每个切割下来的小块的信息对应在Book类中的成员变量进行赋值(注意分析数据方法返回值是Book类)
最后再添加个main方法测试结果

private Book parseBookJson(String json) {
        //解析数据
        //之前我们把文件中每一行作为一个book对象,现在我们需要针对book对象中各个属性进行归位
        //以“,”分隔,逐个去读取信息

        //1. 检查json是否是空的
        if (json.isEmpty()) {
            return null;
        }

        //2. 使⽤逗号分割出每个属性的值
        String[] pairs = json.split(",");

        //3. 每个属性进⾏转化赋值,对应Book类中的成员变量,因为方法返回值是Book类
        int bookId = Integer.parseInt(pairs[0]);
        String title = pairs[1];
        String author = pairs[2];
        String category = pairs[3];
        int publishYear = Integer.parseInt(pairs[4]);
        boolean isBorrowed = Boolean.parseBoolean(pairs[5]);
        int borrowCount = Integer.parseInt(pairs[6]);
        LocalDate shelfDate = LocalDate.parse(pairs[7]);

        //构造书籍对象,针对每一本书籍的对象
        if (title != null && author != null && category != null && shelfDate != null) {
            Book book = new Book(title, author, category, publishYear, shelfDate);
            book.setBorrowed(isBorrowed);
            book.setBorrowCount(borrowCount);
            book.setBookId(bookId);
            return book;
        }
        return null;
    }
    
    //我们再添加测试方法去测试下
    public static void main(String[] args) {//下面是测试的例子
        AnalyzingBook myProperties = new AnalyzingBook();
        // 存储数据
        Book[] books = new Book[4];
        books[0] = new Book("java", "aaa", "编程", 1994, LocalDate.of(2023, 9,
                24));
        books[1] = new Book("mysql", "ccc", "编程", 1999, LocalDate.of(2024, 2,
                10));
        books[2] = new Book("php", "bbb", "编程", 2020, LocalDate.of(2023, 9,
                23));
        books[3] = new Book("西游记", "吴承恩", "⼩说", 2024, LocalDate.of(2023,
                9, 23));
        myProperties.storeObject(books, Constant.ALL_BOOK_FILE_NAME);

        //读取数据
        Book[] loadedBooks = myProperties.loadObject(Constant.ALL_BOOK_FILE_NAME);
        if (loadedBooks != null) {
            System.out.println("Loaded books:");
            for (Book book : loadedBooks) {
                System.out.println(book);
            }
        }
    }

还记得我们之前的图书馆类library类吗,你想想之前那些代码,是不是都是new一个对象,这样势必会导致图书馆存在多个
为了避免这种情况,我们在创建图书馆类的时候就设置成单例模式中的懒汉式

public class Library {
    private Book[] books;//当前图书数组
    private int bookCount;//实际存储的图书数量
    public Library() {
    //当调⽤该构造⽅法的时候,要加载⽂件当中的数据进⾏到books数组当中
    }
}

但是这个Book数组要设置成多大才合情合理,太大又浪费,太小又不够用,那我们可以定义一个较小的长度
如果我们存的书籍内容少于默认的,那我们就把Book类数组的长度变成真实的Book类数组长度,否则我们就进行数组的扩容操作
不知你是否还记得我们之前在定义一些长度等常量值长度的时候给的并不是数值而是一个常量名,为什么要这么做?
你想,当我们想去该某个常量时候,是不是这和那都要改,不合理,因此我们可以定义一个常量的包,再其下定义一个常量类存放规定好的常量值
这就好比我之前写C语言的时候扫雷棋盘大小也是这么规定的

//定义一个常量包constant
//定义一个常量类Constant,存放常量值
public class Constant {
    //内存中的书籍数组初识容量
    public static final int CAPACITY = 5;
}

我们回到之前的图书馆类Library中,刚刚说到定义Book类数组大小的问题我们已经有了思路,那么我们就定义一个loadAllBook方法去展示书籍的全部内容
同时我们再创建一个AnalyzingBook类的对象去解析书籍信息,这个时候工具类就派上用场了

    //书籍解析
    private AnalyzingBook analyzingBook = new AnalyzingBook();
    //analyzingBook:为成员变量AnalyzingBook的引⽤
    
    //我们可以给一个默认值,即常量
    //如果现有数组大小小于它,就读取实际存放的图书,修改bookCount,否则就更新默认长度
    private void loadAllBook() {
        //1. 读取⽂件内容
        Book[] allBook = analyzingBook.loadObject(Constant.ALL_BOOK_FILE_NAME);
        //默认⼤⼩为5
        books = new Book[Constant.CAPACITY];
        //2. 是否有数据 没有数据 有效书籍个数为 0
        if (allBook == null) {
            bookCount = 0;
        } else {
            //3. 查看实际书籍⻓度是多少 是否⼤于默认的⻓度5
            int allBookLen = allBook.length;
            //3.1 ⼤于默认⻓度 books数组 分配实际的⼤⼩
            if (allBookLen > books.length) {
                //按照实际情况进⾏分配数组内存
                books = new Book[allBookLen];
            } //3.2 把读到的元素进⾏赋值
            for (int i = 0; i < allBookLen; i++) {
                books[i] = allBook[i];
            } //4.修改实际有效书籍个数
            bookCount = allBookLen;
        }
    }

既然有文件读取到内存中展示,那就必然会有从内容把书籍信息放入文件

    //存储图书到⽂件中
    private void storeBook() {
        analyzingBook.storeObject(books,Constant.ALL_BOOK_FILE_NAME);
    }

好,此时我们的图书馆类library展示如下

public class Library {//把文件内容读取到内存中来
    private Book[] books;//当前图书数组
    private int bookCount;//实际存储的图书数量

    //书籍解析
    private AnalyzingBook analyzingBook = new AnalyzingBook();
    //analyzingBook:为成员变量AnalyzingBook的引⽤

    private Library() {//private为单例模式修饰
        //当调⽤该构造⽅法的时候,要加载⽂件当中的数据进⾏到books数组当中,先判断有没有书,再进行长度判断
        loadAllBook();//调用读取文件方法,作为构造对象的基础
    }

    //我们可以给一个默认值,即常量
    //如果现有数组大小小于它,就读取实际存放的图书,修改bookCount,否则就更新默认长度
    private void loadAllBook() {
        //1. 读取⽂件内容
        Book[] allBook = analyzingBook.loadObject(Constant.ALL_BOOK_FILE_NAME);
        //默认⼤⼩为5
        books = new Book[Constant.CAPACITY];
        //2. 是否有数据 没有数据 有效书籍个数为 0
        if (allBook == null) {
            bookCount = 0;
        } else {
            //3. 查看实际书籍⻓度是多少 是否⼤于默认的⻓度5
            int allBookLen = allBook.length;
            //3.1 ⼤于默认⻓度 books数组 分配实际的⼤⼩
            if (allBookLen > books.length) {
                //按照实际情况进⾏分配数组内存
                books = new Book[allBookLen];
            } //3.2 把读到的元素进⾏赋值
            for (int i = 0; i < allBookLen; i++) {
                books[i] = allBook[i];
            } //4.修改实际有效书籍个数
            bookCount = allBookLen;
        }
    }

    //存储图书到⽂件中
    private void storeBook() {
        analyzingBook.storeObject(books,Constant.ALL_BOOK_FILE_NAME);
    }
}

4. 完善图书系统LibrarySystem类

之前已经给出代码,我们可以看到我们已经有了三个代理类的对象
分别是:brokerManage——代理管理员的对象,brokerOrdinary1——代理一个普通用户的对象,brokerOrdinary2——代理另一个普通用户的对象
那我们最终还是要通过代理类的构造方法去确定具体的用户类型是谁,因此我们还要在当前类中实现用户类型的选择方法selectProxyRole

    //目前我们还遗留了根据不同用户类型去调用不同业务逻辑的问题
    //选择对应⻆⾊进⾏登录
    //我们传了三个对应的用户类型
public Broker selectProxyRole(Broker proxyUserAdmin, Broker proxyUserNormalW,Broker proxyUserNormalL) {
        System.out.println("选择⻆⾊进⾏登录:");
        System.out.println("1.管理员\n2.普通⽤⼾(翠花)\n3.普通⽤⼾(强哥)\n4.退出系统");
        Broker currentUser = null;//决定用户类型的指向
        Scanner scanner = new Scanner(System.in);
        int choice = scanner.nextInt();
        switch (choice) {
            case 1:
                currentUser = proxyUserAdmin;
                break;
            case 2:
                currentUser = proxyUserNormalW;
                break;
            case 3:
                currentUser = proxyUserNormalL;
                break;
            case 4:
                System.exit(0);
                System.out.println("系统已退出..");
                break;
            default:
                break;
        }
        return currentUser;
    }

因此对于当前类(图书系统)的main方法,我们可以继续测试其角色选择是否能够正确,添加如下代码,非常关键,它们是调用的基础

    //测试用户类型登录
        LibrarySystem librarySystem = new LibrarySystem();
        Broker currentUser = librarySystem.selectProxyRole(brokerManage,brokerOrdinary1,brokerOrdinary2);
        while (true) {//所有调用的基础
            int choice = currentUser.getRealUser().display();
            //此时⽆需关系是 管理员还是普通⽤⼾,代理类会做权限判断
            currentUser.handleOperation(choice);//仅需完善权限判断方法,在代理类中进行判断
        }

5.继续完善工厂模块中的代理类

代理类,说白了就是要代理每个用户的类型,那我们是不是先要对用户的类型进行检查
确定对应用户类型后是不是还要执行不同用户类型对应的操作
而对于“查找图书,显⽰图书,退出系统”这些方法作为不同用户类型的共通方法,我们直接定义到图书馆类Library
因此在代理类Broker中添加如下方法,有些方法之前就写过,不用重复再写

public class Broker {
	private void checkRealUserWhetherAdminUser(String msg) throws 	PermissionException {
        if (!(realName instanceof ManagePerson)) {
            //⾃定义异常
            throw new PermissionException(msg);
        }
    }
    
	public void addBook() throws PermissionException {
        //查看代理的对象是是不是管理员
        System.out.println("上架图书");
        checkRealUserWhetherAdminUser("普通用户无权限");
        //如果确实是管理员,就进不去上面异常(不会抛出上面异常,代码就可以走到这)
        ((ManagePerson) realName).addBook();
    }
    
    //更新书籍操作
    public void updateBook() {
    }

    //移除图书
    public void removeBook() {
    }

    //查看图书的借阅次数
    public void borrowCount() {
    }

    //查看最受欢迎的前K本书
    public void generateBook() {
    }

    //查看库存状态
    public void checkInventoryStatus() {
    }

    //移除上架超过1年的书籍
    public void checkAndRemoveOldBooks() {
    }

    //--------------------------------普通用户相关⽅法------------------------------//
    private void checkRealUserWhetherNormalUser(String exceptionMessage) throws PermissionException {
        if (!(realName instanceof OrdinaryPerson)) {
            throw new PermissionException(exceptionMessage);
        }
    }

    //归还图书
    public void returnBook() {
    }

    //查看个⼈借阅情况
    public void viewBorrowHistory() {
    }

    //再判断对象实例化过程中用户的类型,即检查对应权限
    //要把所有用户类型的所有可能的方法都写这
    public void borrowBook() {
    }

代理类有了这些方法,就可以针对不同用户类型进行操作,但是之前我们写过操作菜单
我们针对不同用户写过不同的操作菜单,每个操作菜单返回值都是int类型,而返回值就传到了这里
还记得我们在图书馆系统LibrarySystem中的这几行代码吗
再之后我们通过返回值明确当前用户类型,然后去调用代理类的实例化对象currentUser去调用代理类中的handleOperation来操作控制权限
而之前我们说过我们还尚未实现,现在我们就来实现

public class LibrarySystem {
        LibrarySystem librarySystem = new LibrarySystem();
        Broker currentUser = librarySystem.selectProxyRole(brokerManage,brokerOrdinary1,brokerOrdinary2);
        while (true) {//所有调用的基础
            int choice = currentUser.getRealUser().display();
            //此时⽆需关系是 管理员还是普通⽤⼾,代理类会做权限判断
            currentUser.handleOperation(choice);//仅需完善权限判断方法,在代理类中进行判断
        }
while (true) {//所有调用的基础
            int choice = currentUser.getRealUser().display();
            //此时⽆需关系是 管理员还是普通⽤⼾,代理类会做权限判断
            currentUser.handleOperation(choice);//仅需完善权限判断方法,在代理类中进行判断
        }

我们现在进入Broker代理类,添加handleOperation方法并且实现它
老样子先检查用户类型以对象不同的操作权限,利用switch语句作选择
一样的我们不使用数字而是使用常量值,便于代码统一修改和提升可读性
而对应的常量值我们老样子添加到常量包constant中的常量类constant

public class Broker {//代理类
    //如果只是使⽤数字,别的程序员可能并不能读懂甚⾄过⼀段时间,自己也可能看不懂。所以定义为常量可以增加代码的可读性。
    public void handleOperation(int choice) throws PermissionException {
        //一切调用基础,跟管理类用户和普通类用户打通
        if (realName instanceof ManagePerson) {
            // 管理员操作
            switch (choice) {
                case Constant.SEARCH_BOOK:
                    library.searchBook();
                    break;
                case Constant.DISPLAY_BOOK:
                    library.displayBooks();
                    break;
                case Constant.EXIT:
                    library.exit();
                    break;
                case Constant.ADD_BOOK:
                    addBook();
                    break;
                case Constant.UPDATE_BOOK:
                    updateBook();
                    break;
                case Constant.REMOVE_BOOK:
                    removeBook();
                    break;
                case Constant.BORROWED_BOOK_COUNT:
                    borrowCount();
                    break;
                case Constant.GENERATE_BOOK:
                    generateBook();
                    break;
                case Constant.CHECK_INVENTORY_STATUS:
                    checkInventoryStatus();
                    break;
                case Constant.CHECK_AND_REMOVE_OLD_BOOK:
                    checkAndRemoveOldBooks();
                    break;
                default:
                    System.out.println("⽆效的操作。");
            }
        } else if (realName instanceof OrdinaryPerson) {
            // 普通⽤⼾操作
            switch (choice) {
                case Constant.SEARCH_BOOK:
                    library.searchBook();
                    break;
                case Constant.DISPLAY_BOOK:
                    library.displayBooks();
                    break;
                case Constant.EXIT:
                    library.exit();
                case Constant.BORROWED_BOOK:
                    borrowBook();
                    break;
                case Constant.RETURN_BOOK:
                    returnBook();
                    break;
                case Constant.VIEW_BORROW_HISTORY_BOOK:
                    viewBorrowHistory();
                    break;
                default:
                    System.out.println("⽆效的操作。");
            }
        }
    }
}

我们在constant常量类中添加以下成员变量

public class Constant {
    //管理员相关操作管理
    //查找图书
    public static final int SEARCH_BOOK = 1;
    //显⽰图书
    public static final int DISPLAY_BOOK = 2;
    //退出系统
    public static final int EXIT = 3;
    //上架图书
    public static final int ADD_BOOK = 4;
    //更新图书
    public static final int UPDATE_BOOK = 5;
    //删除图书
    public static final int REMOVE_BOOK = 6;
    //查看图书的借阅次数
    public static final int BORROWED_BOOK_COUNT = 7;
    //查看受欢迎的图书
    public static final int GENERATE_BOOK = 8;
    //查看库存状态
    public static final int CHECK_INVENTORY_STATUS = 9;
    //移除上架超过1年的书籍
    public static final int CHECK_AND_REMOVE_OLD_BOOK = 10;

    //普通⽤⼾相关操作管理
    //借阅图书
    public static final int BORROWED_BOOK = 4;
    //归还图书
    public static final int RETURN_BOOK = 5;
    //查看个⼈借阅情况
    public static final int VIEW_BORROW_HISTORY_BOOK = 6;
}

6. 继续完善用户模块

我们回到对应的用户类型的用户类,我们之前写了一些方法,但是还没有实现,现在来实现下
在管理员类ManagePerson中,我们完善上架图书的方法并添加Library类实例化的对象,最终在图书馆类Library类中上架图书

public class ManagePerson extends User{
    public Library library = Library.getLibrary();//对于图书类的操作需要实例化图书馆对象
    
    public void addBook() {
        scanner.nextLine();
        System.out.println("请输⼊书名:");
        String title = scanner.nextLine(); // 输⼊书名
        System.out.println("请输⼊作者:");
        String author = scanner.nextLine(); // 输⼊作者
        System.out.println("请输⼊类别:");
        String category = scanner.nextLine(); // 输⼊图书类别
        System.out.println("请输⼊出版年份:");
        int year = scanner.nextInt(); // 输⼊出版年份
        scanner.nextLine(); // 吞掉换⾏符
        LocalDate shelfDate = LocalDate.now(); // 当前时间作为上架时间
        Book newBook = new Book(title, author, category, year, shelfDate);
        // 创建新书对象
        // 调⽤图书馆类 添加图书
        library.addBook(newBook);
    }
}

为什么在方法中有两行scanner.nextLine();代码呢?
第一个是为了吞掉上一个操作的换行符(毕竟之前输入的操作都是默认读取一行内容,回车后才能读取嘛,第二个也类似,也是因为nextLine而吞掉输入内容的换行符
我们继续在ManagePerson管理员类中完善borrowCount借书量统计的方法

public class //统计每本书的借阅次数
    public void borrowCount() {
        //不需要准备其他⼯作,直接调⽤具体的业务实现
        library.borrowCount();
    } extends User{
//统计每本书的借阅次数
    public void borrowCount() {
        //不需要准备其他⼯作,直接调⽤具体的业务实现
        library.borrowCount();
    }
}

我们之前不是在管理员类ManagePerson中写了个程序退出方法吗,其实不需要写
因为我们当前类已经实例化类图书馆类的对象,而在图书馆类中有程序退出的方法,因此我们直接调用就好

管理员类我们完善了一些方法,现在我们来继续完善普通用户类的一些方法
你先想,对于借书,我们绑定借阅的人是通过什么绑定,书名吗?可能会存在同名书籍
因此我们不妨给书籍编码,即加上书籍ID,那怎么知道借书借给了谁呢?
诶,我们可以设置一个文件去存储借阅信息,格式就是 用户 I D , 书籍 I D 用户ID,书籍ID 用户ID,书籍ID
因此我们就在book包下面定义一个PairOfUidAndBookId用来专门存放用户ID和书籍的ID
如果有很多本图书信息,我们就是用到对象数组
注意toJSON这个方法,我们之后会经常调用

public class PairOfUidAndBookId {
    //这个类装门存放用户ID和书的ID
    private int userId;
    private int bookId;
    public PairOfUidAndBookId() {
    }

    public PairOfUidAndBookId(int userId, int bookId) {
        this.userId = userId;
        this.bookId = bookId;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    //把对象序列化为JSON字符串的形式
    public String toJson() {
        StringBuilder json = new StringBuilder();

        json.append(userId).append(",");
        json.append(bookId);
        //上面两行就是为了实现逗号后再拼接的字符串效果

        return json.toString();
    }
}

7. 工具模块添加分析借阅信息工具类

好,既然我们之前解析熟悉内容信息有个工具类AnalyzingBook
那我们分析借阅信息也要有个工具类,我们定义在utils工具包底下
内部的逻辑和这个工具类AnalyzingBook类似
都是分为存储信息和读取信息两个方法,而两个方法各自先判断有没有数据存在,有就按照格式来进行字符串转化再切割读取或者存储

public class AnalyzingBorrowedBook {
    //专门分析借阅书籍数据,与AnalyzingBook类设计类似

    public void storeObject(PairOfUidAndBookId[] pairOfUidAndBookIds, String filename) throws IOException {
        //先遍历pairOfUidAndBookIds数组当中不为空的数据多少个?
        int booksUseLen = 0;
        for (int i = 0; i < pairOfUidAndBookIds.length; i++) {
            if(pairOfUidAndBookIds[i] != null) {
                booksUseLen++;
            }
        }
        StringBuilder jsonArray = new StringBuilder();
        for (int i = 0; i < booksUseLen; i++) {
            if(pairOfUidAndBookIds[i] != null) {
                jsonArray.append(pairOfUidAndBookIds[i].toJson());
                if (i != booksUseLen-1) {
                    jsonArray.append("\n");
                }
            }
        }
        FileUtils.writeFile(jsonArray.toString(),filename);/* */
    }

    public PairOfUidAndBookId[] loadObject(String filename) throws IOException {
        //从⽂件读取数据
        String content = FileUtils.readFile(filename);
        if (content == null || content.isEmpty()) {
            System.out.println("已借阅书籍列表⽆数据,表⽰没有⽤⼾借阅过书籍");
            return null;
        }
        String[] JsonStrings = content.split("\n");
        PairOfUidAndBookId[] pairOfUidAndBookIds = new PairOfUidAndBookId[JsonStrings.length];
        for (int i = 0; i < JsonStrings.length; i++) {
            PairOfUidAndBookId pairOfUidAndBookId = new PairOfUidAndBookId();
            String[] uidAndBookIds = JsonStrings[i].split(",");
            pairOfUidAndBookId.setUserId(Integer.parseInt(uidAndBookIds[0]));
            pairOfUidAndBookId.setBookId(Integer.parseInt(uidAndBookIds[1]));
            pairOfUidAndBookIds[i] = pairOfUidAndBookId;
        }
        return pairOfUidAndBookIds;
    }
}

8. 进一步完善用户模块

好我们继续完善我们的普通用户类OrdinaryPerson模块
我们完善loadBorrowedBook加载借阅信息的方法和storeBorrowedBook存储借阅信息的方法,再添加一些常量

public class OrdinaryPerson extends User {//给选择菜单服务
// ⽤⼾已借阅的图书相关信息
    private PairOfUidAndBookId[] pairOfUidAndBookIds;//文件中读取
    // 当前书籍的借阅量,借了几本书,访问的是数组中有效长度
    private int borrowedCount;

    //最多借阅的图书数量
    private static final int BORROW_BOOK_MAX_NUM = 5;
    private final AnalyzingBorrowedBook analyzingBorrowedBook = new AnalyzingBorrowedBook();

    Scanner scanner = new Scanner(System.in);
    
    private void loadBorrowedBook() {
        PairOfUidAndBookId[] allBorrowedBook;
        try {
        //1.先加载⽂件当中的借阅信息
            allBorrowedBook=
                    analyzingBorrowedBook.loadObject(Constant.BORROWED_BOOK_FILE_NAME);
        //2. 默认已借阅的图书数组⼤⼩为BORROW_BOOK_MAX_NUM,这⾥也可以定义到常量类
                    pairOfUidAndBookIds = new PairOfUidAndBookId[BORROW_BOOK_MAX_NUM];
                    //3.没有读取到已借阅的图书信息
            if (allBorrowedBook== null) {
                borrowedCount = 0;
            } else {
            //4. 查看实际读取到的数组⻓度是多少?
                int allBorrowedBookLen= allBorrowedBook.length;
            //5. 如果读取到了10本书被借阅 但是当前borrowedBooks数组⻓度⼩于10
                if (allBorrowedBookLen> pairOfUidAndBookIds.length) {
            //6. 按照实际情况进⾏分配数组内存
                    pairOfUidAndBookIds = new PairOfUidAndBookId[allBorrowedBookLen];
                }
                //7.把数据拷⻉回到 已借阅图书信息的数组当中
                for (int i = 0; i < allBorrowedBookLen; i++) {
                    //useID为当前的useID才能调用
                    pairOfUidAndBookIds[i] = allBorrowedBook[i];
                }
                //8.更新当前实际借阅书籍的书籍数量
                borrowedCount = allBorrowedBookLen;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void storeBorrowedBook() throws IOException {
        analyzingBorrowedBook.storeObject(pairOfUidAndBookIds,Constant.BORROWED_BOOK_FILE_NAME);
    }
}

最后我们在常量类constant中添加一行新的常量,用于表示存储借阅信息的文件

public class Constant {
    public static final String BORROWED_BOOK_FILE_NAME = "borrowedbook.txt";//定义借书数据的文件
}

9. 完善最后的业务逻辑

我们这个用户模块的逻辑就是通过代理类去明确用户类型,然后再去对应用户类型的类中去调用方法,最后再调用定义在Library类中的方法

1. 管理员业务

我们按照刚刚说的逻辑,我们现在图书馆类中去完善添加图书的方法addBook()
因为在代理类中和管理员类中我们已经把addBook()方法写好了,现在我们只需要完善图书馆类中的addBook()方法即可

public class Library {/
    public void addBook(Book book){//注意参数
        if(bookCount >= books.length) {
            System.out.println("图书馆已满,⽆法上架更多图书!");
            return;
        }
        //修改bookId为⾃增
        book.setBookId(bookCount+1);
        books[bookCount++] = book;
        //此时存储数据的时候会把书籍对象全部存储,虽然部分属性没有输⼊赋值
        storeBook();
        System.out.println("图书 "+book.getTitle()+"上架成功!");
    }
}

接下来是更新书籍,由于我们之前没有在任何类中实现,因此我们先完善代理类中的更新书籍方法

//更新书籍操作
public class Broker {//代理类
    public void updateBook() throws PermissionException {
        checkRealUserWhetherAdminUser("普通⽤⼾没有权限更新图书");
        ((ManagePerson) realName).updateBook();
    }
}

我们进一步在管理员类ManagePerson中去完善更新书籍操作,提供菜单操作

public class Library {
    //图书修改 ⽀持修改书名 作者 类别
    public void updateBook() {
        //1. 先展⽰⼀下⽬前的所有书籍
        library.displayBooks();
        System.out.println("请输⼊要修改的图书id:");
        int bookId = scanner.nextInt();
        // 吞掉换⾏符
        scanner.nextLine();
        // 获取对应的图书
        Book book = library.searchById(bookId);
        if(book == null) {
            System.out.println("没有ID为:"+bookId+" 的书籍!");
            return;
        }
        System.out.println("当前书名:" + book.getTitle());
        System.out.println("请输⼊新的书名:");
        String newTitle = scanner.nextLine(); // 输⼊新的书名
        System.out.println("当前作者:" + book.getAuthor());
        System.out.println("请输⼊新的作者:");
        String newAuthor = scanner.nextLine(); // 输⼊新的作者
        System.out.println("当前类别:" + book.getCategory());
        System.out.println("请输⼊新的类别:");
        String newCategory = scanner.nextLine(); // 输⼊新的类别
        //更新对应书籍的信息
        book.setTitle(newTitle);
        book.setAuthor(newAuthor);
        book.setCategory(newCategory);
        library.updateBook(book);
    }
}

可以看到有几个方法我们还没有在Library类中去定义,我们来实现下
你每一次写入文件都是将原本文件的内容全部重写(覆盖)

//更新图书
    public void updateBook(Book book) {
    //先找到该书是哪个下标
        int index = searchByIdReturnIndex(book.getBookId());
        books[index] = book;
    //⼀定要进⾏存储
        storeBook();
    }
    
    //根据bookId返回书籍索引位置,把每本书作为一个对象取出来
    private int searchByIdReturnIndex(int bookId) {
        loadAllBook();
        for (int i = 0; i < bookCount; i++) {
            Book book = books[i];
            if(book.getBookId()==bookId) {
                return i;
            }
        }
        return -1;
    }
    
    public Book searchById(int bookId) {
        loadAllBook();
        for (int i = 0; i < bookCount; i++) {
            Book book = books[i];
            if(book.getBookId()==bookId) {
                return book;
            }
        }
        return null;
    }

这个根据ID找书说白了就是便利数组对象,拿出来放进新的数组中

同理我们下架书籍也是那一套业务逻辑,代理类->对应的用户类型->图书馆类,说白了最终的操作都是在图书馆这里类中进行

//代理类中
public class Broker {//代理类
    public void removeBook() throws PermissionException {
        checkRealUserWhetherAdminUser("普通⽤⼾没有权限删除图书");//权限检查
        ((ManagePerson) realName).removeBook();
    }
}

//管理员类ManagePerson中
public class ManagePerson extends User{
    //删除书籍
    public void removeBook() {
        //1.展⽰⼀下所有的图书
        library.displayBooks();
        System.out.println("请输⼊要删除的图书的ID:");
        int bookId = scanner.nextInt();
        scanner.nextLine(); // 吞掉换⾏符
        //记录⼀下删除的图书对象
        Book removeBook =library.searchById(bookId);
        //开始删除
        library.removeBook(bookId);
        System.out.println("图书:"+removeBook.getTitle()+" 已经被删除!");
    }
}

//图书馆类Library中
public class Library {
    //删除图书
    public void removeBook(int bookId) {
        int index = searchByIdReturnIndex(bookId);
        //开始删除 从当前位置的后边往前移动
        for (int i = index; i < bookCount-1; i++) {
            books[i] = books[i+1];
        }
        books[bookCount-1] = null;
        storeBook();
        bookCount--;
    }
}

图书馆删除书的逻辑是这样的:从开始的位置向后移动,说白了就是后面的书籍去覆盖前面的书籍,而最后要删除的那个书籍(Book类数组中那个元素)我们就设为Null
但是这样有个问题,你想,因为我们书的ID依赖于根据前面书的ID而自增,这一本书删除了,会导致新的一本书的ID还是旧的那本书的ID
为什么循环的终止条件是bookCount-1呢,因为你删到最后的时候,循环执行语句有个i+1,会导致数组越界情况发生

我们还是一样的按照之前业务逻辑实现查看借阅次数方法

//代理类中
public class Broker {
    public void borrowCount() throws PermissionException {
        checkRealUserWhetherAdminUser("普通⽤⼾没有权限查看图书的借阅次数");
        ((ManagePerson) realName).borrowCount();
    }
}

//管理员类中,这个之前实现过了,不用再重复写了
public class ManagePerson extends User{
    public void borrowCount() {
        //不需要准备其他⼯作,直接调⽤具体的业务实现
        library.borrowCount();
    }
}

//图书馆类中
public class Library {
    public void borrowCount(){
        loadAllBook();
        for (int i = 0; i < bookCount; i++) {
            Book book = books[i];
            System.out.println("书名:"+book.getTitle()+
                    " 借阅次数:"+book.getBorrowCount());
        }
    }
}

好,我们继续实现最后欢迎的前几本书,还是之前一样的业务逻辑,由于我之前没有在Library类中写getBook()方法,因此我们自己可以完善下,这里就不展示了

//代理类中
public class Broker {
public void generateBook() throws PermissionException {
        checkRealUserWhetherAdminUser("普通⽤⼾没有权限查看最受欢迎的前k本书");
        ((ManagePerson) realName).generateBook();
    }
}

//管理类中
    //查询最受欢迎的前n本书
    public void generateBook() {
        System.out.println("请输⼊你要查看的最受欢迎的前K本书,注意k值不能超过:"+library.getBookCount());
        int k = scanner.nextInt();
        if(k <= 0 || k > library.getBookCount()) {
            System.out.println("没有最受欢迎的前"+k+"本书!");
            return;
        }
        library.generateBook(k);
    }

//图书馆类
public class Library {
    //查询最受欢迎的前n本书
    public void generateBook(int k) {
        //1. 加载已有的全部的书籍
        loadAllBook();
        //2.把所有书籍放在 临时数据 进⾏排序
        Book[] tmp = new Book[getBookCount()];
        for (int i = 0; i < getBookCount(); i++) {
            tmp[i] = books[i];
        } 
        //2.1 开始排序
        Arrays.sort(tmp);
        //3. 把前k本书拷⻉到新数组 可以不定义临时数组,直接输出前K个就⾏
        Book[] generateBooks = new Book[k];
        for (int i = 0; i < k; i++) {
            generateBooks[i] = tmp[i];
        } 
        //4.打印新数组
        System.out.println("最受欢迎书籍如下:");
        for (int i = 0; i < generateBooks.length; i++) {
            Book book = generateBooks[i];
            System.out.println("索引: "+i+" 书名:"+ book.getTitle()+" 作者:"+
                    book.getTitle()+" 借阅次数:"+book.getBorrowCount());
        }
    }
}

为什么要去定义临时的数组呢,你想如果你存在Book类数组中,是不是会导致混乱,我们现在只是暂时查看书籍又不是真的写入书籍

好,我们继续来查看图书库存代码,还是与之前一样的业务逻辑

//代理类
public class Broker {
//查看库存状态
    public void checkInventoryStatus() throws PermissionException {
        checkRealUserWhetherAdminUser("普通⽤⼾没有权限查看库存状态");
        ((ManagePerson) realName).checkInventoryStatus();
    }
}

//管理员类
public class ManagePerson extends User{
//查看库存状态
    public void checkInventoryStatus() {
        library.checkInventoryStatus();
    }
}

//图书馆类
//查看库存状态
    public void checkInventoryStatus() {
        loadAllBook();
        for (int i = 0; i < bookCount; i++) {
            Book book = books[i];
            String status = book.isBorrowed() ? "已借出" : "在馆";
            System.out.println("书名:"+book.getTitle()+" ⽬前状态:"+status);
        }
    }

就是一个三元运算符判断是否借出

我们继续来实现移除上架超一年书籍,还是跟之前业务逻辑一样

//代理类
public class Broker {
//移除上架超过1年的书籍
    public void checkAndRemoveOldBooks() throws PermissionException {
        checkRealUserWhetherAdminUser("普通⽤⼾没有权限移除上架超过⼀年的图书");
        ((ManagePerson) realName).checkAndRemoveOldBooks();
    }
}

//管理员类
//并移除上架超过⼀年的图书
public class ManagePerson extends User{
    public void checkAndRemoveOldBooks() {
        library.checkAndRemoveOldBooks();
    }
}

//图书馆类,具体实现细节,用到了很多工具和方法
    //移除上架超过⼀年的图书
    public void checkAndRemoveOldBooks() {
        loadAllBook();
        // 获取当前时间戳
        long currentTimestamp = System.currentTimeMillis();
        // 将当前时间戳转换为 LocalDate
        LocalDate currentDate = Instant.ofEpochMilli(currentTimestamp)
                .atZone(ZoneId.systemDefault())
                .toLocalDate();
        boolean flg = false;
        for (int i = 0; i < getBookCount(); i++) {
            Book book = books[i];
            //获取当前书籍的上架时间
            LocalDate specifiedDate = book.getShelfDate();
            // 计算两个⽇期之间的差值(以年为单位)
            long yearsBetween = ChronoUnit.YEARS.between(specifiedDate,
                    currentDate);
            if (yearsBetween >= 1) {
                System.out.print("图书 " + book.getTitle() + " 已经上架超过⼀年,是否移除? (y/n):");
                scanner.nextLine();
                String response = scanner.nextLine();
                if (response.equalsIgnoreCase("y")) {
                    //确认删除调⽤remove⽅法进⾏删除
                    removeBook(i);
                    i--; // 因为后⾯的书已经向前移动,所以要重新检查当前索引位置
                }
                flg = true;
            }
        }
        if (!flg) {
            System.out.println("没有上架超过⼀年的图书!");
        }
    }

Instant.ofEpochMilli(currentTimestamp)指的是毫秒级转化
ZonedDateTime转时区
toLocalDate() 将ZonedDateTime转换为LocalDate对象

2. 普通用户业务

我们首先来完成借书的模块,还是跟之前一样的业务逻辑
但是之前我们在代理类中并未实现借书方法,我们实现下

//代理类中
public class Broker {
public void borrowBook() throws PermissionException {
        checkRealUserWhetherNormalUser("管理员请以普通⽤⼾的⽅式借阅图书");
        ((OrdinaryPerson) realName).borrowBook();
    }
}

你想,我们要怎么去存储到底这本书被谁借了,因此我们又需要一个文件存储这个信息
之前我们已经写过PairOfUidAndBookId类,我们就按照这个类格式去存储

public class PairOfUidAndBookId {
    //这个类装门存放用户ID和书的ID
    //1,101:表⽰ID为1的⽤⼾借阅了ID为101的书籍

    private int userId;
    private int bookId;
    public PairOfUidAndBookId() {
    }

    public PairOfUidAndBookId(int userId, int bookId) {
        this.userId = userId;
        this.bookId = bookId;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    //把对象序列化为JSON字符串的形式
    public String toJson() {
        StringBuilder json = new StringBuilder();

        json.append(userId).append(",");
        json.append(bookId);
        //上面两行就是为了实现逗号后再拼接的字符串效果

        return json.toString();
    }
}

因此,我们在普通用户类中实现借书方法准备(即借书的选择)
那么首当其冲就是看你借的书在不在,存在就把借阅信息写入文件,如果书在数组里,但是你借不到,说明被其他人借了
我们在普通用户类中先定义图书馆对象,再在构造方法中去实例化,这样调用普通用户类时候就自动有图书馆对象了

public class OrdinaryPerson extends User {
    private Library library;
    
    public OrdinaryPerson(String name, int id) {
        super(name, id,"普通用户");
        library = Library.getLibrary();//添加了这一行
        loadBorrowedBook();
    }
    
    //借阅图书
    public void borrowBook() throws IOException {
        scanner.nextLine();
        System.out.println("请输⼊你要借阅图书的id:");
        int bookId = scanner.nextInt();
        scanner.nextLine();
//如果书架没有书 不能借阅
        if (library.getBookCount() == 0) {
            System.out.println("书架没有书籍暂且不能借阅");
            return;
        }
        //加载已借阅的图书信息
        loadBorrowedBook();
        //判断要借阅的书 是否存在
        Book book = library.searchById(bookId);
        if(book == null) {
            System.out.println("没有该id的相关书籍:"+bookId);
            return;
        }
        //检查通过loadBorrowedBook()加载到pairOfUidAndBookIds数组当中
        //是否有bookId == 当前需要借阅的图书ID && UID也是⼀样的 说明当前⽤⼾借阅过
        //否则就是其他⽤⼾借阅过
        for (int i = 0; i < borrowedCount; i++) {
            PairOfUidAndBookId pairOfUidAndBookId = pairOfUidAndBookIds[i];
            //找到了对应的书籍
            if (pairOfUidAndBookId.getBookId() == book.getBookId()) {
                if (getUserID() == pairOfUidAndBookId.getUserId()) {
                    System.out.println("该书已经被你借阅过了,你的ID是:" +
                            getUserID());
                    return;
                } else {
                    System.out.println("该书已经被其他⼈借阅过了,他的ID是:" +
                            pairOfUidAndBookId.getUserId());
                    return;
                }
            }
        }
        library.borrowBook(bookId);
        //封装对象写到 借阅表当中
        PairOfUidAndBookId pairOfUidAndBookId = new PairOfUidAndBookId(userID,
                book.getBookId());
        pairOfUidAndBookIds[borrowedCount] = pairOfUidAndBookId;
        borrowedCount++;
        //存储借阅图书
        storeBorrowedBook();
        System.out.println("借阅成功!");
    }
}
    
//图书馆类,再在Book类新增几个方法
public class Library {
    //借阅图书
    public void borrowBook(int bookId) {
        loadAllBook();
        for (int i = 0; i < getBookCount(); i++) {
            Book book = books[i];
            if(book.getBookId()== bookId) {
                book.setBorrowed(true);
                book.incrementBorrowCount();
            }
        }
        storeBook();
    }
    
//Book类
public class Book implements Comparable <Book> {
    public void incrementBorrowCount() {
        this.borrowCount++;
    }

    public void decreaseBorrowCount() {
        this.borrowCount--;
    }
}

同理我们继续来写还书方法

//代理类中
public class Broker {
//归还图书
    public void returnBook() throws PermissionException {
        checkRealUserWhetherNormalUser("管理员请以普通⽤⼾的⽅式归还图书");
        ((OrdinaryPerson) realName).returnBook();
    }
}

注意在普通用户类中,要把类pairOfUidAndBookIds 存到文件的信息也删除,即这本书借给了谁的信息

//普通用户类
public class OrdinaryPerson extends User {
//归还图书
    public void returnBook() throws IOException {
        loadBorrowedBook();
        if (borrowedCount == 0) {
            System.out.println("⽬前没有⽤⼾借阅过书籍");
            return;
        } 
        scanner.nextLine();
        System.out.println("请输⼊你要归还图书的id:");
        int bookId = scanner.nextInt();
        scanner.nextLine();
        //判断要借阅的书 是否是已经被⾃⼰借阅过了
        Book book = library.searchById(bookId);
        if(book == null) {
            System.out.println("没有该id的相关书籍:"+bookId);
            return;
        } 
        for (int i = 0; i < borrowedCount; i++) {
        //如果2本书的ID,则认为是同⼀本书
            if (pairOfUidAndBookIds[i].getBookId()==book.getBookId()) {
                //借阅⽤⼾也⼀样
                if (getUserID() == pairOfUidAndBookIds[i].getUserId()) {
                    library.returnBook(bookId);
                    System.out.println("图书 '" + book.getTitle() + "' 已成功归 还。");
                            // ⽤最后⼀本替换归还的书
                            pairOfUidAndBookIds[i] = pairOfUidAndBookIds[borrowedCount - 1];
                    // 清空最后⼀个
                    pairOfUidAndBookIds[borrowedCount - 1] = null;
                    borrowedCount--;
                    storeBorrowedBook();
                }else{
                    System.out.println("该书籍不是你借阅的书籍,不能归 还:"+book.getTitle());
                } 
                return;
            }
        } 
        System.out.println("你没有借阅该书籍,不需要归还,书籍ID为:"+bookId);
    }
}

这个还书的逻辑就是借书的ID和用户的ID是否匹配,我们让最后一本书往前覆盖,再把原位置设为Null

//归还图书
public class Library {
    public void returnBook(int bookId) {
        loadAllBook();
        for (int i = 0; i < getBookCount(); i++) {
            Book book = books[i];
            if(book.getBookId()==bookId) {
                book.setBorrowed(false);
                book.decreaseBorrowCount();
            }
        } 
        storeBook();
    }
}

其次就是查看借阅情况,跟之前业务逻辑一样

//代理类
public class Broker {
//查看个⼈借阅情况
    public void viewBorrowHistory() throws PermissionException {
        checkRealUserWhetherNormalUser("管理员请以普通⽤⼾的⽅式查看个⼈当前借阅情况");
        ((OrdinaryPerson) realName).viewBorrowBooks();
    }
}

//普通用户类,读取数据到内存再遍历
public class OrdinaryPerson extends User {
// 查看个⼈借阅情况
    public void viewBorrowBooks() {
        //读取当前借阅所有⽤⼾的借阅书籍的情况
        loadBorrowedBook();
        System.out.println("您的借阅情况如下:");
        if (borrowedCount == 0) {
            System.out.println("⽬前没有借阅记录.....");
        } else {
            boolean flg = false;
            for (int i = 0; i < borrowedCount; i++) {
                //这⾥只能查看属于⾃⼰借阅的情况
                //⽤⼾ID相同的情况下,使⽤书籍ID查询书籍
                if(pairOfUidAndBookIds[i].getUserId() == userID) {
                    flg = true;
                    Book book =
                            library.searchById(pairOfUidAndBookIds[i].getBookId());
                    System.out.println(book);
                }
            }
            if(!flg) {
                System.out.println("你没有借阅过书籍!");
            }
        }
    }
}

此时我们就已经在普通用户类实现完了,无需再到图书馆类中实现

3. 不同用户类型共通方法

共通方法我们之前就已经全部放在了图书馆类中,因此我们转到图书馆类
首先就是查找图书,我们还要定义个search方法,因为我们之前都是通过书的ID去找
而现在是外界操作,只能通过书名去找

public void searchBook(){//找书
        scanner.nextLine();
        System.out.println("请输⼊你要查找的图书的名称:");
        String title = scanner.nextLine();
        Book book = search(title);
        if(book == null) {
            System.out.println("没有你要找的这本书,你查找的书名为:"+title);
        }else {
            System.out.println("找到了你要查找的书,书的详细信息如下:");
            System.out.println(book);
        }
    }

    private Book search(String title) {
        loadAllBook();
        for (int i = 0; i < getBookCount(); i++) {
            Book book = books[i];
            if(book.getTitle().equals(title)) {
                return book;
            }
        }
        return null;
    }

我们接着实现显示所有图书方法,说白了就是调用Book类数组遍历打印就好了

public void displayBooks(){
        System.out.println("当前图书馆当中所有的图书:");
        //更新⼀次⽂件当中的新数据
        loadAllBook();
        for (int i = 0; i < bookCount; i++) {
            Book book = books[i];
            System.out.println(book);
        }
    }

退出系统就更简单了,还记得C语言的时候函数return0吗,这就是正常退出状态表示

public void exit(){
        System.out.println("退出系统!");
        System.exit(0);
    }

六、总结以及想说的话

首先,这个项目不是我原创的,因此我不能说这全都是我写的
其次,我写的时候并不是完全手把手自己捏造的,而是引用了别人的方法,之前的jar包就是例子
最后,这个项目我后续有时间会回来再看,因为目前可能还存在一些bug,争取出一篇新的文章把这个项目讲的更透
也就是说这篇文章只是初版,后续我会更新,感谢大家,我们再见

文章错误不可避免,期待您的指正,我们共同进步


END

网站公告

今日签到

点亮在社区的每一天
去签到