JAVA 作业二
题目:
要完成一个学校的管理系统,需要管理学生,老师,工作人员,行政人员,同时包括一个管理员,要求:
- 分析根据自己的理解,分析如何设计这几个类,要用继承;
强行继承 - 用方法覆盖实现对功能的扩展,例如,可以说话功能的复写。
谁能告诉我这有什么意义 - 用一个类来管理这五类人员,就是用管理类对这几类人员进行添加,删除,修改
- 写一个测试类测试正确性
关系如下图

思路
定义数据存储
我们需要使用 Manager 类来管理其它类像 Student, Teacher…, 这个管理也就是增删查改, 相当于和数据库
通信, 考虑到这只是一个简单的作业,数据库直接简化为内存就行了,因此我们定义数据库db到 Manager 类
1 | private static Map<Class<?>, Map<Long, Object>> db = new HashMap<Class<?>, Map<Long, Object>>(); |
细节后面再说
设计 API
考虑 client 端, 期望的得到的 API 可能是各种类的实例直接插入, 然后根据(类, id)来查询到指定已经存入的数据,像这样
1 | // 增 |
我们总是需要一个 id(identifier)去查找同一个类的唯一示例(无论对与调用者和 manager), 因此至少在插入操作, 必须限制数据类型至少拥有只读 id 的属性, 为了方便使用我们定义接口(注意不是父类, 使用接口规范数据而不是继承)。
1 | public interface Model<T> { |
详细解释下
因为存入的数据后面需要根据一些属性进行查询,删除,而且需要唯一标识的效果, 就像数据库里面的主键primary key, 虽说 Manager 类我们可以直接设定成任意类都可以直接插入,但是后面查询和删除没有这个主键就没办法进行, 所以要定义一个规范, 一般称为接口interface, 就是上面那个 Model
内容很简单, 就一个 getId 方法, 只要实现这个接口不管什么类都可以直接保存进来
这样, 然后 Manager 只需要设置save和update的方法接受的数据类型为Model<T>即可。例如
1 | public class Manager { |
现在规范初步约定好了,我们可以尝试定义Model, 这里拿Student示例
1 | public class Student extends Person implements Model<Student> { |
这里 Student 类继承了 Person 类(实际上 Person 就是个空类,为了满足作业要求才强行继承的), 实现了Model<Student>接口,我们这里为了方便除了id只定义了一个name属性。
实现 API
实现Manager的各个静态方法。 等等, 为什么偏要是静态方法而不是方法? 理由简单地说就是没必要
所谓的静态方法,在其它语言里就是普通的函数,我们这里只需要用到函数没必要对Manager类实例化。面向对象编程中将数据和它的行为(behavior)绑定在一起,使方法的行为可以很方便的依赖于不断变化的属性, 对这里Manager类来说, 并没有相应的属性需要被依赖例如database dialect来和数据库通信, 因此我们这里只是使用简单的函数, Java 里所谓简单的函数就是静态方法(虽说反而看着更复杂)
回到正题, save方法方法接收一个实现Model接口的任意实例,我们必须根据不同的类来把这些数据分类, 这也是db最外层定义为Map<Class<?>, ...>的理由。而对于每一种类型, 我们只需简单的使用Map<Long, Object>映射 id 到实体即可,db的定义就这么来了, 那么实现这几个方法,如下
1 | public static <T> void save(Model<T> modelInstance) { |
save和udpate写法几乎一致, 甚至可以合并在一起,这里忽略掉。
保护数据
目前为止的 API 存在很严重的安全问题: 数据可以直接被修改。
实际上,保存在数据库中的数据和在内存中的数据并不同(指并没有指向同一块地址)或者说数据库中的数据被处理器 copy 过去的, 而在这里我们用内存作为数据库并没有做出这样的行为, 数据依旧指向同一块内存, client 在执行save操作后对实例的修改同时会影响到db中存储的数据,反过来也是一样,我们对从db中查询到数据直接修改也会影响到db里的原始数据, 为了解决这个问题, 我们必须在save, update和query里增加 copy 行为。 在 Java 里我们称之为clone。
为了方便, 我们在Model里新增方法clone
1 | public interface Model<T> { |
这个时候 Student 类报错, 我们需要在 Student 类里实现clone方法
1 | public class Student extends Person implements Model<Student>, Cloneable { |
注意我们类的声明新增了对Cloneable接口的实现, Cloneable接口时 Java 内置的接口, 这个接口什么都没做, 只是用来告知 Java 此类可以被 Clone(否则一定会出现 CloneNotSupportedException)。
在save, update, query操作时稍作修改即可, 例如
1 | // save, update |
很好, 来测试一下:
1 | // add |
注意最后一行断言, Java 里当两个实例指向不同地址时,尽管数据相同,然而==运算会得出false, 如果想要比较数据, 可以使用equals方法。
稍作总结
- 我们需要数据存储到相应的位置, 一般是数据库,但是这里只是作业,简化成内存,也就是
db的定义 Manager类想要达成增删查改任意数据类型到db的任务, 但是查删之类的操作必须根据identifier来确定唯一实例, 因此至少要限制插入进来的数据行为(或者属性, 因此定义了 Model 接口, 凡是实现这个接口的(拥有可读 id(long))的实例, 都可以保存入数据库- 分离
db和 client 端数据, 我们在保存,更新和查找操作时都会进行clone将存储到数据库db中的实例和 client 端的实例的内存分离开, 防止数据被随意更改
测试
至于测试类, 随便写下就 OK, 示例代码和执行结果如下
1 | public class Main { |
1 | <Student id:'111' name:'kanai'> |

