问题
一个同事发来的题目:学校有三种人员,第一种为教师,属性包括名字、教工号、电话、地址;第二种为学生,属性包括名字、学号、电话、地址、平均成绩;第三种为辅工,属性包括名字、辅工号、电话、地址、工种。使用你学到的面向对象设计的方法,实现这三种人员的类表示,并实现三种人员的添加、修改、删除,可在内存进行增删改的操作,不需要永久保存。
注,可使用List保存人员,如没有使用OO设计方法,本期作业不通过,仅使用类不是OO设计。
答案v1
大致扫了一眼题目,是一个简单的OO设计问题。于是第一版答案很快出来了。
抽象父类Staff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public abstract class Staff { private String id; private String name; private String mobile; private String address; protected Staff(String id, String name, String mobile, String address) { this.id = id; this.name = name; this.mobile = mobile; this.address = address; } //省略setter和getter public abstract <T extends Staff> Set<T> getSet(); public void add() { getSet().add(this); } public void delete() { getSet().remove(this); } public void update(Staff o) { getSet().remove(o); getSet().add(this); } } |
子类Student,Teacher和Worker类类似,在此省去。静态set用于在内存中保存对象,为了防止出现丧心病狂的方法来获取整个数据集,添加了静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Student extends Staff { private static Set<Student> students = new HashSet<>(); private double gpa; public Student(String id, String name, String mobile, String address, double gpa) { super(id, name, mobile, address); this.gpa = gpa; } @Override @SuppressWarnings("unchecked") public Set<Student> getSet() { return students; } public static Set<Student> list() { return students; } } |
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@Test public void test01() { Teacher teacher1 = new Teacher("1", "hei", "10086", "beijing"); Teacher teacher2 = new Teacher("2", "bai", "10001", "beijing"); teacher1.add(); teacher2.add(); for(Staff s : Teacher.list()) { System.out.println(s.getName()); } System.out.println("---------------"); Student student1 = new Student("110", "yurnom", "1352288xxxx", "北京", 3); Student student2 = new Student("111", "ypp", "1531308xxxx", "北京", 4); student1.add(); student2.add(); for(Staff s : Student.list()) { System.out.println(s.getName()); } System.out.println("---------------"); student1.delete(); for(Staff s : Student.list()) { System.out.println(s.getName()); } System.out.println("---------------"); } |
运行结果
1 2 3 4 5 6 7 8 |
hei bai --------------- yurnom yp --------------- ypp --------------- |
一切都正确的,可以CRUD。但是,为何看起来那么的别扭。没有满足OO设计吗?貌似很符合,好像还采用了模版方法的设计模式。那问题出在哪里?
看看测试代码中的这两句:
1 2 3 4 |
teacher2.add(); for(Staff s : Teacher.list()) { System.out.println(s.getName()); } |
对象可以调用自己的方法来操作自己的CRUD,这是一个不太常见的设计方法。反正我没在哪里看到过其它人这么设计,我第一次这么设计是以前有个系统需要大量的在工作流中添加监听器,而监听器要进行CRUD操作的时候还要从SpringContext中获取相应的Service,显得十分繁琐。于是可以自己操作自己的CURD的类就这样设计出来了。但是,这样的类在某些特殊情况下确实好用,但对于传统的思维方式来说显得就十分的异类了。就好像上面的代码,一会自己操作自己的
答案v2
接口IDao,这里多添加了几个方法,使得更像一个Dao了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public interface IDao<T extends Staff> { public void add(T staff); public void delete(T staff); public void delete(String id); public void update(T staff); public T get(String id); public Set<T> list(); } |
实现类Dao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class Dao<T extends Staff> implements IDao<T> { private Set<T> set = new HashSet<>(); @Override public void add(T staff) { set.add(staff); } @Override public void delete(T staff) { set.remove(staff); } @Override public void delete(String id) { delete(get(id)); } @Override public void update(T staff) { delete(staff.getId()); set.add(staff); } @Override public T get(String id) { for(T t : set) if(id.equals(t.getId())) return t; return null; } @Override public Set<T> list() { return set; } } |
抽象父类Staff,现在的Staff显得十分的简洁,功能也十分的单一。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public abstract class Staff { //属性、构造函数、setter、getter方法同上,略 @Override public String toString() { return getEntityName() + "{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", mobile='" + mobile + '\'' + ", address='" + address + '\'' + '}'; } public abstract String getEntityName(); } |
子类Student,也变得简洁了些。
1 2 3 4 5 6 7 8 |
public class Student extends Staff { //属性、构造函数、setter、getter方法同上,略 @Override public String getEntityName() { return "Student"; } } |
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class Client { @Test public void test02() { Student student1 = new Student("110", "yurnom", "1352288xxxx", "北京", 3); Student student2 = new Student("111", "ypp", "1531308xxxx", "北京", 4); IDao<Student> sDao = new Dao<>(); sDao.add(student1); sDao.add(student2); print(sDao.list()); sDao.delete(student1); student2.setMobile("110"); sDao.update(student2); print(sDao.list()); Teacher teacher1 = new Teacher("1", "hei", "10086", "beijing"); Teacher teacher2 = new Teacher("2", "bai", "10001", "beijing"); IDao<Teacher> tDao = new Dao<>(); tDao.add(teacher1); tDao.add(teacher2); print(tDao.list()); } public static <T> void print(Collection<T> collection) { for(T t : collection) { System.out.println(t.toString()); } System.out.println("---------------"); } } |
运行结果
1 2 3 4 5 6 7 8 |
Student{id='110', name='yurnom', mobile='1352288xxxx', address='北京'} Student{id='111', name='ypp', mobile='1531308xxxx', address='北京'} --------------- Student{id='111', name='ypp', mobile='110', address='北京'} --------------- Teacher{id='1', name='hei', mobile='10086', address='beijing'} Teacher{id='2', name='bai', mobile='10001', address='beijing'} --------------- |
这样就感觉正常多了,通过Dao来操作每个子类的CRUD,功能解耦,而且还方便扩展,例如继续添加Staff的子类,如:校(qin)长(shou),除了添加子类以外不用做任何其它的改动。
继续改进
看到那该死的5个参数的构造函数了吗?为何该死?就是太长了,5个参数,每个参数什么意义一点也不明确。若构造时只需要id和name就可以构造的话就需要重新添加一个构造函数,只需要id、name和mobile也可以构造则又需要添加一个构造函数......抓狂吧?颤抖吧?怎么解决呢?建造者模式。
继续以Student为例,现假设id和name为必须的属性,其它的属性可以设置也可以不设置。采用建造者模式后的代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public class Student extends Staff { private final double gpa; public Student(Builder builder) { super(builder.id, builder.name, builder.mobile, builder.address); this.gpa = builder.gpa; } public static class Builder { private String id; private String name; private String mobile; private String address; private double gpa; public Builder(String id, String name) { this.id = id; this.name = name; } public Builder mobile(String mobile) { this.mobile = mobile; return this; } public Builder address(String address) { this.address = address; return this; } public Builder gpa(double gpa) { this.gpa = gpa; return this; } public Student build() { return new Student(this); } } @Override public String getEntityName() { return "Student"; } } |
这样创建Student对象时则可以显示的设置每个参数,如下。
1 2 3 4 5 |
Student student = new Student.Builder("9532", "yurnom") .address("beijing") .gpa(4.0) .mobile("1352288xxxx") .build(); |
和5个构造参数的构造函数比起来是不是有种高下立判的感觉。正如Effective Java中所说的,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是不错的选择。当然建造者模式相对Setter、Getter方式的类各有利弊,比如上述代码中就无法改变已创建好的Student对象的属性。
最后
不同的环境下,不同的设计方式有不同的效果,能选择最适合当前环境的即可。不必要纠结于其它的条条框框。比如那个自行CRUD的设计,怎么看都是个异类,但在项目里确实是最合适的。当然,需要这样的设计来弥补的项目我不想遇到第二次(该项目持续开发了8年,经过无数人的交接,技术大部分是8年前的技术,没错,还是国企自主研发项目,你懂的。。)。