目录
一、简介
二、需求
三、具体设计
一、大纲
二、分析过程
三、小结
1.整体流程
2.ListBookOrderByXXXCommand
3.匿名类对象语法知识点
4.类和对象(面向对象设计)
四、完整代码
一、简介
实现一个简单的能对图书馆的书籍进行简单管理的一个系统。所用到的知识有:类,抽象类,封装,继承,多态,接口。(基础知识的学习可看我博客的其他文章~)
二、需求
1.用户登录
2.管理员角色(增加书籍 、查阅书籍(根据书名排序、根据价格排序、根据借阅情况排序) 、删除书籍 、打印书籍列表)
3.普通用户角色(查询书籍 、借阅书籍 、归还书籍)
都在命令行上完成操作
三、具体设计
一、大纲
1.从持久化存储中(文件)加载书籍信息 -> 以顺序表的形式进行呈现
2.用户登录 -> User对象(多态体现)
3.进入一个循环中
3.1打印用户角色对应的菜单,并且让用户选择
3.2根据用户的选择,以Executable 对象来体现(多态),执行对应的操作命令(方法最终的执行是由对象类型决定的,对象不同,执行方法不同)
4.用户退出(Ctrl + D),打印退出信息
二、分析过程
1.输入过程需要处理很多事情:①打印提示信息②读取用户输入③用户有没有按 ctrl + D 退出,因此我们直接封装一个对象去处理输入问题。
Input input = new Input();
2.在用户登录中,我们需要一个用户管理的对象,由该对象完成用户登录的具体操作:①要求用户输入②判断用户角色。
UserStorage userStorage = new UserStorage();User user = userStorage.login(input);
3.在打印用户菜单中,我们需要一个接口,可以返回给我们一个可以执行的命令,从输入中打印菜单并且进行用户选择。
IExecutable command = input.menuAndSelect(); //这是一个命令
command.excute(user,input); //去执行
4.因为我们要写的类很多,所以根据他们不同的职责,进行分类,比如:新建一个输入包把输入的类(Input)放在里面。新建一个用户包,把用户的类(User、UserStorage)放在里面。新建一个命令包,把命令的接口放在里面。
5.写 login
①先让用户输入用户名 ② 根据用户名,角色是管理员还是普通用户 ③ 根据不同角色,创建不同用户,输入时,写成带有提示的输入(prompt)。
6.在Input 类中完成prompt。
7.我们提前定义好一些用户名,作为管理员。
8.完成IExecutable 接口,里面实现 excute 方法。
9.完成menuAndSelect()方法,首先要显示菜单,角色不同,拥有的功能不同,因此需要用户信息,把User user 传入。同时menuAndSelect()方法中的showMenu();方法也要实现,首先需要得到这个角色支持哪些命令(以IExecutable[ ] 数组的方式来体现)
public IExecutable menuAndSelect(User user) {showMenu(user); //角色不同,拥有的功能不同,因此需要用户信息}private void showMenu(User user) {//1.得到角色支持的命令IExecutable[] supportedCommands = user.getSupportedCommands();//2.遍历并打印每个命令的名称,显示操作菜单for(int i = 0;i < supportedCommands.length;i++){IExecutable command = supportedCommands[i];System.out.printf(" %2d. %s\n",command.getName());}}
10.想要增加几个命令,就多写几个 command 利用接口、抽象类 + 继承等实现了多态的效果,使得开发的过程变得简单。(分为管理员用户 和 普通用户)
11.具体的实现,添加书籍:1.让用户输入要添加的书籍 input.prompt() 2.使用用户输入信息,构造成书籍对象Book(name、author、type、price、borrowedBy)
public class Book {public String name;public String author;public String type;public int price;public String borrowedBy; // 如果没人借走,就是null
}
3.把书籍对象添加到类似 BookStorage(书架)对象上。将书籍尾插到书架上
public class BookStorage {private Book[] array;private int size;public BookStorage(){array = new Book[20];size = 0;}//尾插public void add(Book book){//1.确保容量够用ensureCapacity();array[size++] = book;}private void ensureCapacity(){if(size < array.length){//说明至少还可以放一个元素,容量够用return;}//否则进行扩容array = Arrays.copyOf(array,array.length * 2);}
}
书架对象只有一个,书籍对象有很多个。所以两者之间的关系是容器与元素的关系。把书架抽象成关于书籍的顺序表。
12.实现所有的 command 内容,根据需要也可以添加 command 的内容
13.书籍的格式化:职责归属放到Book.toString()
比较:书籍默认按照名称比较(自然顺序),如果需要其他比较,则使用外部比较器
14.功能实现完成之后,先以管理员的身份运行程序,添加书籍,退出;再以普通用户身份运行程序,借阅书籍;发现普通用户中没有书籍 。如何解决?
借助可以在多进程中共享的存储,这个存储还具有持久化(进程退出后,数据仍然保存)。这个共享的存储就是硬盘:想办法把数据放到硬盘上,硬盘上的数据,被操作系统抽象成一个个文件,我们只需要把数据写到文件中即可。
具体实现:1.程序启动时,从文件中(指定一个路径)读取所有的书籍信息,读到内存中(以BookStorage 对象中 Book 的形式体现)
2.凡是对BookStorage 做改动,我们都需要把数据,写一份到文件中(指定路径)
3.规定格式:思考一个Book对象在文件中长什么样?
暂且规定:每行一本书,书的属性之间用一个特殊字符(@)分割。这个成立的前提是,书名,作者名,类型名,借阅人的字符中不能出现 \n 和 @,否则不知道怎么去解读了。字符集编码必须是UTF-8。
三、小结
1.整体流程
从持久化存储中(文件)加载书籍信息 -> 以顺序表的形式进行呈现
通过用户登录 -> 当前登录用户对象(管理员用户 or 普通用户)
循环{
根据不同的角色用户,显示不同的操作菜单(靠多态机制 <- 抽象类、继承、引用指向等等这些语法知识点一起来实现)
让用户选择具体的操作 -> 得到当前要指向的命令
执行命令,根据命令不同,执行不同的操作(靠多态机制 <- 接口、继承、引用指向等等这些语法知识点一起来实现)
}
对于我们input 对象来说,由于在用户登录、用户选择命令、执行命令时都要用到input 对象,因此,①从性能角度来说,没必要每个地方都建立一个独立的input 对象 ②有时候在代码的不同位置,需要共享一些信息的时候。所以我们有一个公共的input 对象就够了,别人只负责去使用就好了。我们的Input 对象,全局只有一个;User 对象(当前登录用户),全局只有一个BookStorage 对象(图书管理系统的书架)全局只有一个。
2.ListBookOrderByXXXCommand
①模板设计模式:父类(抽象类)实现了 execute 方法:使用操作的模板
a.获取外部比较器对象 (留给子类们去实现)
b.获取所有书籍信息
c.根据比较依据排序
d.展示列表
多态 为 模板设计模式 提供了底层服务(没有多态,就做不到 模板设计模式)
抽象类、继承、方法的重写、引用的指向等语法基础,为实现多态,提供了底层服务
②深入应用了对象的比较 Comparable or Comparator
③代码风格 printBook的代码全部抽象到 Book.toString 中,从面向对象的角度来讲,职责封装更合理。这些代码语句,放到哪里都能执行,但是放到合适的类、合适的方法中,让程序员看起来,层次分明,层次划分的更清楚(封装概念的体现)
④属性对象逃逸(逃逸有风险)
3.匿名类对象语法知识点
public class ListBookOrderByBorrowedCommand extends AbsListBookCommand{private static class BorrowedComparator implements Comparator{@Overridepublic int compare(Object o1, Object o2) {Book b1 = (Book) o1;Book b2 = (Book) o2;int br1 = b1.borrowedBy == null ? 0 : 1;int br2 = b2.borrowedBy == null ? 0 : 1;return br1 - br2; //没被借走的比较小,在上面,被借走的在下面}}private final Comparator comparator = new BorrowedComparator();
BorrowedComparator类,这个类,只使用了一次,创建了一个comparator对象。理论上,再也不会用到这个类了。可以用匿名类对象的语法替换,好处:1.不用再花心思为类起名字了。2.从语法角度,保证了确实没有其他地方用到这个类了。3.代码可以短一点。
改进后:
public class ListBookOrderByBorrowedCommand extends AbsListBookCommand{private final Comparator comparator = new Comparator() {@Overridepublic int compare(Object o1, Object o2) {Book b1 = (Book) o1;Book b2 = (Book) o2;int br1 = b1.borrowedBy == null ? 0 : 1;int br2 = b2.borrowedBy == null ? 0 : 1;return br1 - br2; //没被借走的比较小,在上面,被借走的在下面}};
new 父类( ... ) { }; ①定义一个类(没有名字,所以称为匿名类),这个类继承(实现)了父类。②直接用这个类实例化一个对象出来。
4.类和对象(面向对象设计)
输入对象(Input) 整个应用只需要一份
用户存储对象(UserStorage) 整个应用只需要一份
当前登录用户对象(User:AdminUser、CommonUser 也可以添加其他的对象) 整个应用只需要一份
书架管理对象(BookStorage) 整个应用只需要一份
书籍管理对象(Book) 用到的时候创建
命令对象(实现了 IExecutable 的很多 XXXCommand 对象) 理论上:整个应用只需要一份,每个用户子类中独立一份
四、完整代码
详情请看
零七/图书馆管理系统 - Gitee.comhttps://gitee.com/hlingqi/library-management-system/tree/master/out/production/%E5%9B%BE%E4%B9%A6%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9FIDEA