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'> |