目录
一、设计模式
什么是设计模式?
广义概念:面向对象设计,中解决特定问题的经典代码
狭义概念: GOF4人帮定义的 23 种设计模式:工厂,适配器,门面,代理,模板....
二、工厂设计模式
1、什么是工厂设计模式
什么是工厂设计模式?
通过工厂类来创建对象
原来,我们创建对象是这个样子的:
User user = new User();
UserDao userDao = new User();
现在,作为工厂设计模式来讲,并不提倡通过 new 来创建对象,而是通过工厂类来创建对象
这就涉及到了工厂的好处:解耦合
要想理解好解耦合这个概念,就得先了解耦合
耦合:指的是代码间的强关联关系,一方的改变会影响到另一方
我们通过一张图来理解一下:
耦合最大的问题就是不利于代码的维护
可以简单的理解为把接口的实现类,硬编码在程序中
通过工厂类,就可以实现解耦合
2、简单工厂的设计
此时,我们的代码中,有一个 UserService 对象,它是整个系统的业务类,完成核心的业务功能
在这个类中, 我们提供了两个方法:登录方法 login 和 注册方法 register
此外,我们为这个接口,定义了具体的实现类:
DAO 中的代码是这样的:
然后我们再创建一个类,用于测试工厂类解耦合的操作
然后运行程序,发现和我们预期的效果是完全一样的
那么我们现在来分析一下,现在的程序是否存在什么问题呢?
在这个程序中,在这行代码里,就出现了我们之前所说的耦合
UserService 这个实现接口 UserServiceImpl 被硬编码在了程序当中,这就是耦合
一旦出现了这个耦合之后,我们程序的维护性就非常差了,日后我们想要更换这个程序的时候,呢么我们的调用者也就是测试类也跟着要改变
比如,我们后面我们提供了一个 UserServiceImplNew 来替换掉 UserServiceImpl 这个代码,那么必然要重新更改之前的代码,如果这么进行了修改,就会面临一个编译和重新部署的问题,那么也不符合面向对象设计中的开闭原则
此时,我们就发现了, 这就是耦合
要想解决这个耦合,就要借助工厂设计模式,借助工厂类来解耦合
那么我们就来创建一个工厂类
此时,我们创建 UserSrvice 的时候,产生了耦合,所以此时我们工厂类中的工厂方法来创建 UserSrvice 就能达到解耦合的目的,那么此时我们的返回值就是 UserSrvice
由于工厂方法,只是用于创建对象,所以在这,我们可以把它修饰成 static
java
public static UserService getUserService() {
return new UserServiceImpl();
}
此时,我们就可以把之前的代码修改成直接通过工厂类创建对象
此时,站在调用者的角度,这部分的耦合就解决掉了
接下来,我们就看一看工厂类中这部分的耦合怎么解决掉
这个地方存在耦合的原因,就是因为我们在创建 UserServiceImpl 对象的时候,采用了 new 构造方法的形式来创建对象,这个方式一定会把接口的实现类硬编码在程序中,就一定会存在耦合
要想解决掉这个耦合,我们就得先回顾一个我们曾经学过的一个知识点:对象的创建方式
对象的创建一共有两种方式:
1、直接调用构造方法创建对象
java
UserService userService = new UserServiceImpl();
2、通过反射的形式来创建对象,解耦合
java
Class clazz = Class.forName("com.baizhiedu.basic.UserServiceImpl");
UserService userService = (UserService)clazz.newInstance();
那么,我们在工厂类中,就可以运用第二种通过反射的方式来创建对象:
java
public static UserService getUserService() {
UserService userService = null;
try {
Class clazz = Class.forName("com.baizhiedu.basic.UserServiceImpl");
userService = (UserService) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return new UserServiceImpl();
}
我们刚才说,反射可以解耦合,那么反射真的把耦合给解决了吗?
并没有,我们可以发现,在 forName 这个方法中,我们传入的是权限定名
如果有朝一日,我们把权限定名所对应的 UserServiceImpl 替换成新的类,那么这个权限定名也要替换成新类型的权限定名
所以光通过反射这一种方式,并不能彻底把耦合解决掉,我们还需要把耦合的字符串的信息进行一个处理
我们可以通过小的配置文件来解决这个问题:我们把这个字符串的信息,从代码当中转移到一个小的配置文件当中去,这个小的配置文件,一般用 properties 类型的文件来充当
我们可以将配置文件放在我们 resourses 的文件夹当中,那么在这个配置文件当中,我们书写的是在工厂当中,我们所发现的耦合的字符串,也就是这个类的权限定名
那么 propertices 有一个特殊的语法要求:就是得以 key-values 的形式来写
当我们把这个信息转换到小的配置文件中后,还需要将文件读回来,我们可以用一个 properties 类型的集合来存储 properties 文件的内容
那么这个 properties 类型的集合有什么特点呢?
它是一个特殊的 map,properties 中的 key 和 value 都必须得是字符串类型的,那么后续我们就可以把 properties 中的键和值最终封装在这个 properties 类型的集合当中
也就是说,这个 properties 的集合,会有一个 key :userService ,有一个 value:com.baizhiedu.basic.UserServiceImpl
那么我们就可以通过这样的方式,来进行文件内容的读取
那么我们先创建一个 properties 类型的变量:
此时里面并没有我们相关的内容,显然,下一步我们要做的事就是把文件的内容读到集合当中来,让文件内的 key ,作为集合当中的 key,让文件当中的 value 作为集合当中的 value
IO 在整个 java 操作过程中,是一个系统级资源,我们要尽量避免重复性的打开 IO ,并且最好是在程序启动的时候,一次性读取我们想要的内容,那么在这里,我们读取文件的过程中,可以使用静态代码块的方式来完成
那么作为静态代码块,它所做的工作为:
1、获得 IO 流
2、把文件的内容封装到 properties 的集合中,让 userService 作为我们集合当中的 key ,value 则为 com.baizhixx.UserServiceImpl
此时,反射中的代码也要做出相应的改变:
此时,就没有耦合了
通过刚才的工厂类,我们就把 Service 对象的耦合给解决掉了,那么现在我们再来看看我们的项目,除了 Service 对象会有耦合,其它地方是否也存在耦合现象呢?
我们来看一下 UserServiceImpl 这个类
很明显,我们在创建 UseDAO 对象的时候采用的是 new 的方式,显然这块是存在耦合的
我们把 DAO 的实现类,硬编码在了程序中,如果要解决这个实现类,显然是要用到工厂类来创建 UserDAO 的对象,此时,我们就在工厂类中提供 UserDAO 的创建方法,进而解决耦合
需要注意的是,这一块还会有我们曾经说的耦合,所以这个类名,我们最好不要写在代码当中,还是要写在配置文件里
那么工厂当中的代码,此时就应该通过 env .getProperty() 这个方法来获取
此时我们就把耦合转移到了配置文件当中
此时,UserServiceImpl 中也要进行相应的改变了
那么进而就把这个耦合给解决了
3、通用工厂的设计
前面我们解决了 UserService 和 UserDAO 的耦合问题,但是在设计层面上我们会发现一些问题,我们来看一下代码
我们会发现,如果我们想要给 UserService 解耦合,就要创建一个 getUserService 方法,想要给 UserDAO 解耦合,就要创建一个 getUserDAO 方法,那么未来在我们的工厂里面,它的工厂方法可能是无穷无尽的,只要有一个对象解耦合,我们就要提供一个对应的方法
可是此时,我们仔细观察一下,不管是 getUserDAO 还是 getUserService 它们之间有大部分的代码都是重复的
我们会发现,它们的结构和使用的代码,基本上是一样的,只有 key 是有所区别的
所以此时就会有大量的冗余代码出现
那么,我们就可以尝试着设计一个通用的工厂方法,通用的工厂里面,我们就提供一个工厂方法,可以帮我们生产一切我们想要的对象
那么我们该如何设计这个通用的工厂方法呢?
一个方法, 在通用的过程中,其实是有两个部分的:方法的声明和方法的实现
那么声明首先是我们要解决的问题,然后再去解决方法的实现
方法的声明的要素:修饰符,返回值类型,方法名,参数表,(异常)
我们会发现,它们的修饰符都是 pulic static ,所以通用的方法,修饰符也应该是 public static
那么返回值代表它所创建的对象,getUserService 返回的是 UserService ,getUserDAO 返回的是 UserDAO,那么我们现在是通用的方法,就可以让它返回 Object
方法名则没有特殊的要求,我们完全可以根据我们的需要,来做定义
参数表,我们还是按照简单的没有参数的样子来做设计
那么方法的实现怎么写呢?我们再次进行分析
那么我们继续按照套路模拟就行了
唯一的区别就是:如果是 UserService ,就得到小的配置文件中找 userService ,如果是 USerDAO,就得到小的配置文件中找 userDAO
但是在通用工厂方法中,我们并不知道想创建的是什么,那么怎么办呢?
我们可以让调用者通过参数来传给我们,那么此时,我们的代码就可以这样来写:
在参数中定义一个 key ,这个 key 指的是小的配置文件中的 userService 或者 userDAO
那么接下来,我们把这个思路,变成代码:
java
public static Object getBean(String key){
Object ret = null;
try {
Class clazz = Class.forName(env.getProperty(key));
ret = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
4、通用工厂的使用方式
通用工厂在使用当中,应该遵从什么样的使用步骤呢?
首先,我们需要注意,通用工厂核心的目的是创建对象,而创建对象首先我们应该去定义类型,比如,我们想创建 User 类,就得先去定义 User 类型
所以第一步,就是去定义类型(类)
通过 new 去定义一个类是有耦合的,所以我们需要去通过工厂类来创建这个类型,而工厂是我们已经创建好了的,而我们想要创建的类型是我们新定义的
那我们怎么让工厂类认识我们新定义的类呢?
所以第二步就是通过配置文件的配置告知工厂(applicationContext.properties)
配置的形式也是比较简单的,就是典型的 key - value
告知工厂之后,我们需要从工厂中获得这个对象
所以第三步就是通过工厂来获得类的对象
java
Object ret = BeanFactory.getBean(" key ");
总结
Spring 框架的本质就是工厂,只不过这个工厂不用我们再来开发了,Spring 创建的这个工厂叫做 ApplicationContext(applicationContext.xml)
所以,作为 Spring 来讲,主要应用的就是它给我们提供的工厂以及配置文件