先给大家展示一下完成后图书系统的样子
接下来,主要讲解代码编写的思路,完整代码点击文字获取 图书管理小练习
1、分析图书系统设计到的各种类和类的属性
写代码之前,我们先要大致抽象出来一些该有的类和类的属性。
在具体写代码的时候还会有一些部分添加
1)首先 图书系统 肯定要有 书,而我们要想准确描述一本书,我们就要创建一个书类(Book)
包含属性
书名 name
作者 author
价格 price
类型 type
是否被借出 isBorrowed
2)有了书之后,就要有书架(用来存储书籍)。所以我们还要创建一个书架类 (BookList)
包含属性
Book[] (存储书籍,可以要用数组来存储)
3)用户分类,我们的图书操作系统分为 【普通用户】和【管理员】,所以我们要创建 AdminUser类(管理员) 和 NormalUser 类(普通用户)
注意:不同的用户,打印的菜单不同,这里先提醒一下大家
4)各种操作类,我们还要创建一些来对书架进行操作(简称 操作类)。主要的操作如下
查找图书(FindOperation 类)
新增图书(AddOperation 类)
删除图书(DelOperation 类)
显示图书(DisplayOperation 类)
归还图书(ReturnOperation 类)
借阅图书(BorrowOperation 类)
退出书架(ExitOperation 类)
以上我们就基本把这个图书操作系统各个部分分析完了,而为了方便我们管理,我们还要对这些类进行分门别类。
也就是说,我们在编写代码时,把不同的类放在不同包下,用包来对这些类进行封装
2、编写代码
【1】Book类
我们首先来写 Book 类,把图书的各种特性创建成员变量,这些成员变量用 private 修饰
private String name;//书名 private String author;//作者 private int price;//价格 private String type;//类型 private boolean isBorrowed;//是否被借出
用 private 修饰体现封装
同时因为这些成员变量是被 private 修饰,在类外不能获取,所以我们要写一系列 get 和 set 方法,方面我们类外获取和修改这些变量。
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;}
然后,构造 Book 实例时,为了方便给成员变量赋值,所以我们也可以写一个构造方法。为了方便显示 书 的内容,我们还要写一个 toString 方法
public Book(String name, String author, int price, String type) { this.name = name; this.author = author; this.price = price; this.type = type; } public String toString() { return "Book{" + "name='" + name + '\'' + ", author='" + author + '\'' + ", price=" + price + ", type='" + type + '\'' + ((isBorrowed == true) ? "已经借出":"未借出")+ '}'; }
构造方法里面没有给 isBorrowed 赋值,是因为 isBorrowed 是 bollean 类型的变量,创建的初始值为 false,方便我们后续的操作。所以不用再给 isBorrowed 赋值
到这里 Book 类算是完成啦,接下来就是 BookList 类。
【2】BookList 类
单独的书的类型创建好了,现在就来创建一个 BookList 类用来存储书籍。
这个 BookList 就相当于 书架
我这里用数组来存储多本书,创建一个 Book数组 类型的成员变量,存储容量大小为10
private Book[] books = new Book[10];
存储书籍也能用 ArrayList、LinkList等....这里为了方便理解,我用了数组
存储 书 的书架有了,那我们如何知道这个书架里面有多少本书呢?
为了解决这个问题,再来创建一个成员变量来记录我们存储图书的数量
private int usedSize;//实时记录,当前books这个数组当中有多少本书
在书架中,我们可能要取书、存书等操作。而这些操作其实就是对 BookList 类中数组进行的操作,所以我们要在类中写一些方法来完成这些操作供外界使用。
通过下标获取书
在某位置下放书
获取 书架 中书的个数
修改 书架 中书的个数
/** * @param pos pos一定是合法的 * @return */ public Book getBook(int pos){ return books[pos]; } /** * @param pos 此时pos一定是合法的 * @param book 是你要放的书 */ public void setBooks(int pos,Book book){ books[pos] = book; } /** * 实时获取当前书的个数 * @return */ public int getUsedSize(){return usedSize;} /** * 实时的修改当前书架上的书的个数 */ public void setUsedSize(int size){ usedSize = size; }
为了方便我们后面的操作,当我们构造 BookList 对象时,先生成三本书
public BookList(){ books[0] = new Book("三国演义","罗贯中",19,"小说"); books[1] = new Book("西游记","吴承恩",19,"小说"); books[2] = new Book("红楼明","曹雪芹",19,"小说"); usedSize = 3; }
【3】具体操作分析
这里我把把这些操作方法类全部都放到一个 operation 包下面!
因为这些 “方法” 都是对 BookList进行操作,那我们是不是可以创建一个接口,来统一标准的规范
0)IOperation 接口
public interface IOperation { void work(BookList bookList); }
接下来,我们让具体的操作来实现这个接口
例如:添加图书 AddOperation 类
public class AddOperation implements IOperation{ @Override public void work(BookList bookList) { System.out.println("添加图书"); } }
这里先不着急实现具体的业务逻辑,我们先把逻辑整理好
其他的操作也是类似,这里就不在赘述了
借阅图书 BorrowOperation
删除图书 DelOperation
展示图书 DisplayOperation
查找图书 FindOperation
归还图书 ReturnOperation
退出 ExitOperation
【4】用户分类
我们的用户分为两种,一种是管理员,另一种是普通用户。
虽然他们的操作不同,但是都是属于用户这一类,所以我们可以写一个父类 User,在父类中 定义属性和方法,然后让子类继承。这样子类就不必重复写哪些操作
0)父类 User
public class User { protected String name;//用户名 public User(String name){ this.name = name; } public abstract int menu();//打印菜单 //因为管理员和普通用户的菜单内容不一样,所以在父类中 menu 方法我们可以直接写为抽象方法 }
1)子类 AdminUuser 和 NormalUser
我们让子类 AdminUuser 和 NormalUser 继承 父类 User,这样我们就不用重复定义 name这个属性。
继承父类后,我们要提供一个构造方法来显示的帮助父类构造!!!
注意:不同用户菜单不同!
注意:不同用户菜单不同!
public class AdminUser extends User{ public AdminUser(String name) { super(name); } public int menu(){ System.out.println("hello "+this.name+" 欢迎来到图书小练习"); System.out.println("1.查找图书"); System.out.println("2.新增图书"); System.out.println("3.删除图书"); System.out.println("4.显示图书"); System.out.println("0.退出系统"); System.out.println("请输入你的操作:"); Scanner scanner = new Scanner(System.in); int choice = scanner.nextInt(); return choice; } } public class NormalUser extends User{ public NormalUser(String name) { super(name); } public int menu(){ System.out.println("hello "+this.name+" 欢迎来到图书小练习"); System.out.println("1.查找图书"); System.out.println("2.借阅图书"); System.out.println("3.归还图书"); System.out.println("0.退出系统"); System.out.println("请输入你的操作:"); Scanner scanner = new Scanner(System.in); int choice = scanner.nextInt(); return choice; } }
这里用户逻辑也基本实现啦,现在我们开始整合这些类
【5】开始整合(最难)
这里我们新创建一个类 Main(这个类不在上面任何包内),创建一个 main方法。接下来我们将在该方法中整合所以部分。
第一步:我们首先要准备我们的图书
BookList bookList = new BookList(); //准备图书
这里我们不用一本书一本的创建放到 bookList 中,因为在 BookList 实例化时就准备好了三本书
第二步:开始登陆
这里我创建一个 login 方法,然后在 main 中调用
public static User login(){ System.out.println("请输入你的姓名"); Scanner scanner = new Scanner(System.in); String name = scanner.nextLine(); System.out.println("请输入你的姓名:1 -》管理员,0 -》普通用户"); int choice = scanner.nextInt(); if(choice == 1){ return new AdminUser(name); }else { return new NormalUser(name); } }
为什么 login 方法的返回值是 User?因为 AdminUser 类 和 NormalUser 类都是 User 的子类,不管我们选择哪个角色进行实例化,User 都能接收 【向上转型】
在 main 方法中,同样用 User接收
//登陆-> user 这个引用 引用那个对象 User user = login();
第三步:调用菜单,把具体操作跟菜单结合
因为上面我们一下选择 user 的实例化对象是管理员还是普通用户,所以我们直接调用menu 方法显示出来的菜单内容也是不一样的。
所以我们要在 AdminUser 类 和 NormalUser 类分别定义 menu方法
AdminUser 类中 menu() 方法
public int menu(){ System.out.println("hello "+this.name+" 欢迎来到图书小练习"); System.out.println("1.查找图书"); System.out.println("2.新增图书"); System.out.println("3.删除图书"); System.out.println("4.显示图书"); System.out.println("0.退出系统"); System.out.println("请输入你的操作:"); Scanner scanner = new Scanner(System.in); int choice = scanner.nextInt(); return choice; }
NormalUser 类中 menu() 方法
public int menu(){ System.out.println("hello "+this.name+" 欢迎来到图书小练习"); System.out.println("1.查找图书"); System.out.println("2.借阅图书"); System.out.println("3.归还图书"); System.out.println("0.退出系统"); System.out.println("请输入你的操作:"); Scanner scanner = new Scanner(System.in); int choice = scanner.nextInt(); return choice; }
到这里还没结束,我们回到 Main类中,我们选择的用户类型是用 父类 User 接收的,
所以我们需要 user对象来调用 menu()。
此时编译器就会报错,原因就是 User 类中没有定义 menu,所以我们还要在 User 类中写一下menu() 方法
public abstract int menu();
父类中的 menu方法不需要具体实现,定义为抽象类就行。
此时我们在用 user调用就不会出错了,menu() 方法会动态绑定!!!【我们选的哪个就会打印哪个的菜单】
然后我们创建一个变量来接收用户的选择
int choice = user.menu();//动态绑定
我们来看不同用户的菜单,在管理员菜单中 ‘2’ 是新增图书,而在普通用户菜单菜单中 ‘2’ 是借阅图书。
现在问题来了,我们如何才能知道用户想要执行那个 ‘2’ 操作呢?
我们是不是可以让 AdminUuser 对象和 NormalUser 对象中分别存储好各种要执行的操作就可以了。
现在我们重新回到 User类中,添加一个成员
protected IOperation[] iOperations;//此时并没有初始化和分配大小
然后分别在 AdminUuser 和 NormalUser 各自的抽象方法中各自初始化具体的内容
最后,在父类 User 中再定义一个具体调用哪个操作的方法
public void doOperation(int choice, BookList bookList){ this.iOperations[choice].work(bookList); }
通过 choice 作为下标,来找到具体的执行类并执行类中的方法
为什么 doOperation 方法分别定义在 AdminUuser 和 NormalUser 中?
因为如果定义在 AdminUuser 和 NormalUser 中,在整合时 use 对象就无法调用 doOperation 方法。