这次我们将通过对代码一点点的改进,逐步了解简单工厂模式

业务需求:数据库操作(增加学生,删除员工,查询老师,更新老师)。

第一步:面向过程的编程,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的关系 猫是一种动物 猫继承动物。可以降低重复代码,代码得到共享,不易出错。

多态:不同的对象执行相同的动作,通过继承抽象类来实现相同的动作。

 

抽象类:提供继承的出发点,可以多个抽象类继承。尽可能多的共同代码和尽可能少的数据。建议是树枝节点都是抽象类,叶子节点都是实现类。

接口:公共方法和属性(针对不同的类),封装特定功能的集合。