这次我们将通过对代码一点点的改进,逐步了解简单工厂模式
业务需求:数据库操作(增加学生,删除员工,查询老师,更新老师)。
第一步:面向过程的编程,main方法中实现所有功能。最符合人编程思维的方式。
1 public static void main(String[] args) { 2 System.out.println("增加学生成功"); 3 System.out.println("删除员工成功"); 4 System.out.println("查询老师成功"); 5 System.out.println("更新老师成功"); 6 }
UML图:
这段代码完成功能,但是所有功能都在main中实现了,不利于复用(下次有相同的业务的时候,只有复制粘贴过去)并不符合单一职责原则。
第二步:封装一下刚刚的代码吧,吧main中的方法提取出来封装起来。
1 class Util1 { 2 void addStudent() { System.out.println("增加学生成功"); } 3 void deleteEmployee() { System.out.println("删除员工成功"); } 4 void queryTeacher() { System.out.println("查询老师成功"); } 5 void updateTeacher() { System.out.println("更新老师成功"); } 6 } 7 8 public static void main(String[] args) { 9 Util1 util1 = new Util1(); 10 util1.addStudent(); 11 util1.deleteEmployee(); 12 util1.queryTeacher(); 13 util1.updateTeacher(); 14 }
UML图(关联长得和书上好像有点不一样。。)
这样看起来代码是可以复用的,但是仔细想想老师的查询和学生的增加放在一起怪怪的。
有可能我们修改了学生的增加方法,却连老师的查询方法一起重新编译了,大大增加了编译的耗时,这是我们不愿意看到的。
项目的重新编译是一个耗时的工作,在分秒必争的行业中显然是不被允许。
第三步:数据库操作代码分类
1 public static void main(String[] args) { 2 StudentService studentService=new StudentService(); 3 EmployeeService employeeService=new EmployeeService(); 4 TeacherService teacherService=new TeacherService(); 5 6 System.out.println(studentService.myAddStudent()); 7 System.out.println(employeeService.myDeleteEmployee()); 8 System.out.println(teacherService.myQueryTeacher()); 9 System.out.println(teacherService.myUpdateTeacher()); 10 } 11 class StudentService{ 12 public String myAddStudent() { 13 return "增加学生成功"; 14 } 15 public String myDeleteStudent() { 16 return "删除学生成功"; 17 } 18 public String myQueryStudent() { 19 return "查询学生成功"; 20 } 21 public String myUpdateStudent() { 22 return "更新学生成功"; 23 } 24 } 25 class EmployeeService { 26 public String myAddEmployee() { 27 return "增加员工成功"; 28 } 29 public String myDeleteEmployee() { 30 return "删除员工成功"; 31 } 32 public String myQueryEmployee() { 33 return "查询员工成功"; 34 } 35 public String myUpdateEmployee() { 36 return "更新员工成功"; 37 } 38 } 39 class TeacherService { 40 public String myAddTeacher() { 41 return "增加老师成功"; 42 } 43 public String myDeleteTeacher() { 44 return "删除老师成功"; 45 } 46 public String myQueryTeacher() { 47 return "查询老师成功"; 48 } 49 public String myUpdateTeacher() { 50 return "更新老师成功"; 51 } 52 }
UML图:
补充:因为是对数据库的操作,这里把service改成dao会比较好
大体到这里就比较像我们平时项目中的代码,service中管理dao层的操作。
但是仔细分析一下还是会发现,耦合度太高了(因为使用的是关联,是一种强关联)。
而且通过观察,我们可以发现其实每个对数据的操作大体都是增删改查,所以我们可以先抽象一个关于dao层操作的接口。
第四步:数据库操作代码封装接口
1 public static void main(String[] args) { 2 BaseDao employeeDao=new EmployeeDao(); 3 BaseDao studentDao=new StudentDao(); 4 BaseDao teacherDao=new TeacherDao(); 5 6 System.out.println(employeeDao.myAdd()); 7 System.out.println(employeeDao.myQuery()); 8 System.out.println(studentDao.myDelete()); 9 System.out.println(teacherDao.myUpdate()); 10 } 11 interface BaseDao { 12 String myAdd(); 13 String myDelete(); 14 String myQuery(); 15 String myUpdate(); 16 } 17 18 class StudentDao implements BaseDao { 19 @Override 20 public String myAdd() { 21 return "增加学生成功"; 22 } 23 @Override 24 public String myDelete() { 25 return "删除学生成功"; 26 } 27 @Override 28 public String myQuery() { 29 return "查询学生成功"; 30 } 31 @Override 32 public String myUpdate() { 33 return "更新学生成功"; 34 } 35 } 36 37 class EmployeeDao implements BaseDao { 38 @Override 39 public String myAdd() { 40 return "增加员工成功"; 41 } 42 @Override 43 public String myDelete() { 44 return "删除员工成功"; 45 } 46 @Override 47 public String myQuery() { 48 return "查询员工成功"; 49 } 50 @Override 51 public String myUpdate() { 52 return "更新员工成功"; 53 } 54 } 55 56 class TeacherDao implements BaseDao { 57 @Override 58 public String myAdd() { 59 return "增加老师成功"; 60 } 61 @Override 62 public String myDelete() { 63 return "删除老师成功"; 64 } 65 @Override 66 public String myQuery() { 67 return "查询老师成功"; 68 } 69 @Override 70 public String myUpdate() { 71 return "更新老师成功"; 72 } 73 }
UML图:
到这里直观的可以看到service中不再需要关联所有用到的数据库操作类,只需要关联也数据库操作相关的接口就可以了。
看着自己的SSM的项目中,dao层mybatis那块都用的是接口,controller调用的也是service的接口,虽然不明白其中接口的好处,但是好像已经在很多地方使用到了接口。
面向接口编程的好处
在传统的项目开发过程中,由于客户的需求经常变化,如果不采用面向接口编程,那么我们必须不停改写现有的业务代码。改写代码可能产生新的BUG,而且改写代码还会影响到调用该业务的类,可能全都需要修改,影响系统本身的稳定性。
而且为了将改写代码带来的影响最小,我们不得不屈服当前的系统状况来完成设计,代码质量和稳定性更低。
当这种情况积累到一定程度时,系统就会出现不可预计的错误,代码凌乱,不易读懂,后接手的人无法读懂代码,系统的维护工作越来越重,最终可能导致项目失败。
接口在项目就是一个业务逻辑,面向接口编程就是先把客户的业务提取出来,作为接口。
业务具体实现通过该接口的实现类来完成。
当客户需求变化时,只需编写该业务逻辑的新的实现类,修改实现类,不需要改写现有代码,减少对系统的影响。(遵循开闭原则)
接口是对业务抽象。我们制定规则,具体的实现不用关心。
讲讲我对接口编程的好处的理解:
还是我们刚刚那个demo,现在增加一个查询所有在校学生的接口,同时查询所有学生的接口也要保留。
如果我们直接修改原来的代码会导致查询所有学生的接口报错。但是如果我们新建一个类,方法,重新制定实现类。
业务倒是完成但是在维护的时候人可读性怎么样呢?系统的稳定性又如何?
(补充一下接口隔离原则。我们刚刚例子中的接口看起来已经很简洁了,虽然只修改查询方法,但是要把删除,增加,更新重新实现了一遍,所以再把代码改改。接口的功能过于复杂)
1 public static void main(String[] args) { 2 //现在是查询所有学生 3 StudentDaoV1 studentDaoV1=new StudentDaoV1(); 4 studentDaoV1.queryStudent(); 5 //修改成查询在校学生 6 StudentDaoV2 studentDaoV2=new StudentDaoV2(); 7 studentDaoV2.queryStudent(); 8 } 9 class StudentDaoV1 10 { 11 public void queryStudent() 12 { 13 System.out.println("查询所有学生"); 14 } 15 } 16 class StudentDaoV2 17 { 18 public void queryStudent() 19 { 20 System.out.println("查询在校学生"); 21 } 22 }
如果我们用接口的方式来实现一下
1 public static void main(String[] args) { 2 //现在是查询所有学生 3 QueryInterface queryInterface = new StudentQueryV1(); 4 queryInterface.Query(); 5 //修改成查询在校学生 6 queryInterface = new StudentQueryV2(); 7 queryInterface.Query(); 8 } 9 interface QueryInterface { 10 void Query(); 11 } 12 13 class StudentQueryV1 implements QueryInterface { 14 @Override 15 public void Query() { 16 System.out.println("查询所有学生"); 17 } 18 } 19 20 class StudentQueryV2 implements QueryInterface { 21 @Override 22 public void Query() { 23 System.out.println("查询在校学生"); 24 } 25 }
使用接口编程的话,我们只需要替换一下实现类就可以了。而且通过接口我们可以很肯定实现类的功能是查询学生,通过v2可以判断是第2版的查询学生。
优点:
1.不影响其他类的功能
2.代码可读性强
3.只需替换实现类
但是如果我们实例化错了对象怎么办?因为全局需要更改的实例化的对象很多。
我们可以使用简单工厂帮助我们来实例化对象
第五步:使用简单工厂模式
1 public static void main(String[] args) { 2 //目标增加学生,删除员工,查询老师,更新老师 3 MySimpleFactory mySimpleFactory = new MySimpleFactory(); 4 System.out.println(mySimpleFactory.getMyService(0).myAdd()); 5 System.out.println(mySimpleFactory.getMyService(1).myDelete()); 6 System.out.println(mySimpleFactory.getMyService(2).myQuery()); 7 System.out.println(mySimpleFactory.getMyService(3).myUpdate()); 8 } 9 interface BaseService { 10 String myAdd(); 11 String myDelete(); 12 String myQuery(); 13 String myUpdate(); 14 } 15 16 class StudentService implements BaseService { 17 @Override 18 public String myAdd() { 19 return "增加学生成功"; 20 } 21 @Override 22 public String myDelete() { 23 return "删除学生成功"; 24 } 25 @Override 26 public String myQuery() { 27 return "查询学生成功"; 28 } 29 @Override 30 public String myUpdate() { 31 return "更新学生成功"; 32 } 33 } 34 35 class EmployeeService implements BaseService { 36 @Override 37 public String myAdd() { 38 return "增加员工成功"; 39 } 40 @Override 41 public String myDelete() { 42 return "删除员工成功"; 43 } 44 @Override 45 public String myQuery() { 46 return "查询员工成功"; 47 } 48 @Override 49 public String myUpdate() { 50 return "更新员工成功"; 51 } 52 } 53 class TeacherService implements BaseService { 54 @Override 55 public String myAdd() { 56 return "增加老师成功"; 57 } 58 @Override 59 public String myDelete() { 60 return "删除老师成功"; 61 } 62 @Override 63 public String myQuery() { 64 return "查询老师成功"; 65 } 66 @Override 67 public String myUpdate() { 68 return "更新老师成功"; 69 } 70 } 71 class MySimpleFactory { 72 BaseService getMyService(int code) { 73 BaseService baseService; 74 //0代表StudentService 75 //1代表EmployeeService 76 //其余代表TeacherService 77 switch (code) { 78 case 0: 79 baseService = new StudentService(); 80 break; 81 case 1: 82 baseService = new EmployeeService(); 83 break; 84 default: 85 baseService = new TeacherService(); 86 } 87 return baseService; 88 } 89 }
UML图:
其实这么一步步的走过来可以发现,代码其实越来越多(哈哈哈哈)。
但是每一步都是有意义的
第一次改进:将代码抽象出来,减少代码冗余,增加复用,使得修改抽象代码可以修改全局代码。
第二次改进:将业务逻辑分类。该处理学生的业务在一起,该处理老师的业务放在一起...软件出了问题,需要及时定位,修改,编译,发布。
分离业务以后,我们能快速定位到问题,修改,编译,发布。
第三次改进:我们将相同行为抽象成接口。首先我们在一开始就为业务设计了接口。修改业务也不需要改动接口,增加新的实现方法。
这样的话,首先维护和修改的时候可读性高,同时只需要替换实现类,对于不需要修改的代码没有什么影响。
第四次改进:我们使用了简单工厂模式:通过我们传入的参数,工厂生产返回我们想要的实现类。我们只需要对操作类进行操作。
假设我们现在使用了查询所有学生的接口,现在需要替换成查询在校学生的接口。那么我们需要全局替换。
使用简单工厂模式,我们只需修改工厂的返回对象(修改一次)。
优点:对象的创建和使用分离,遵循单一职责原则
缺点:增加新的产品对象时必须修改工厂方法,违背开闭原则
使用场景:
模式简单,一般用于小项目或者很少扩展的情况
不适用于多层次的树状结构
总的来说,现在将代码复杂化,是为了将来修改维护起来更加容易。
会有不需要修改的和维护的软件吗?
没有,还不快好好学习设计模式
啰啰嗦嗦的写了很多,其实简单工厂的讲解还是挺少的,主要是为了在这个过程中好好感受面向对象的思想。
再啰嗦一点复习下面向对象:
一切事物皆为对象(特定的属性和行为)
类就是将一些具有相同属性和行为的对象的抽象集合。
方法的重载是为了不影响原方法的基础上,新增功能
三大特性:
封装:将一组对象抽象成类
继承:继承是一种is-a的关系 猫是一种动物 猫继承动物。可以降低重复代码,代码得到共享,不易出错。
多态:不同的对象执行相同的动作,通过继承抽象类来实现相同的动作。
抽象类:提供继承的出发点,可以多个抽象类继承。尽可能多的共同代码和尽可能少的数据。建议是树枝节点都是抽象类,叶子节点都是实现类。
接口:公共方法和属性(针对不同的类),封装特定功能的集合。