提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
1.为什么要了解工厂设计模式
我们为什要了解工厂设计模式呢?
我们首先通过Java web创建一个原始的三层架构项目,我们看看原始的三层架构会有哪些问题。
dao包
DemoDao.java
public interface DemoDao {
void findAll();
}
DemoDaoImpl.java
public class DemoDaoImpl implements DemoDao {
public void findAll() {
System.out.println("DemoDao 查询全部数据~~~");
}
}
Service包
DemoService.java
public interface DemoService {
void findAll();
}
DemoServiceImpl.java
public class DemoServiceDao implements DemoService {
public void findAll() {
DemoDao demoDao = new DemoDaoImpl();
demoDao.findAll();
System.out.println("DemoServiceDao 从dao查询到数据。。。");
}
}
Controller包
DemoController.java
/**
* 只是为了演示,就直接使用主方法代替访问请求
*/
public class DemoController {
public static void main(String[] args) {
DemoService demoService = new DemoServiceDao();
demoService.findAll();
System.out.println("DemoController 通过DemoService查询到全部数据");c
}
}
从上述过程中我们不难发现,我们每次访问资源时,都会创建该资源的对象。例如:service层调用dao层、controller层调用service层时都需要实例化对象。这样使得我们项目中的模块与模块的紧密程度过高,会出现第一个问题:
当我们的需求发生变化时,我们可以直接在源代码上进行修改吗?
不可以,我们写完的代码,不能因为需求变化就修改。我们可以通过新增代码的方式来解决变化的需求。如果每次需求变动都去修改原有的代码,那原有的代码就存在被修改错误的风险,当然这其中存在有意和无意的修改,都会导致原有正常运行的功能失效的风险,这样很有可能会展开可怕的蝴蝶效应,使维护工作剧增。
出现的第二个问题:当一个项目正在运行的时候。这时我需要添加一个新的DemoService实现类DemoServiceImpl2,该实现类修复了DemoService的一些bug并且添加了一些新的功能。这时候我要修改DemoController该怎么办呢。有人会说直接修改不就完了。这当然不可以了,但是我有100个Controller呢?10000个呢?都需要手改吗?
这当然也不可以。这样的项目不仅违背了开闭原则(Open Close Principle),还大大增加了类与类之间关联。我们通常称为高耦合。
那么什么是开闭原则,什么是耦合呢?
- 开闭原则: 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的 代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和 升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这 点。
- 耦合 : 也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决与模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。
- 内聚 : 又称块内联copy系,指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。
- 通常程序书写的目标:高内聚,低耦合 就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却不要那么 紧密。
总结上述程序的问题:
- 违背了开闭原则。
- 模块与模块之间耦合程度很高。
那么我们如何实现解耦呢?
我们可以通过一个工厂类来实例化各个资源对象,然后各个类通过实例化该工厂对象来获取到自己所需的资源对象。这样就可以实现解耦。该思想就成为工厂设计模式思想。
那么我们接下来就详细介绍一下工厂设计模式。
2.工厂设计模式
2.1 工厂模式的概述
其实设计模式和面向对象设计原则都是为了使得开发项目更加容易扩展和维 护,解决方式就是一个“分工”。
社会的发展也是这样,分工越来越细。
原始社会的人:人什么都要会,自己种,自己打猎,自己织衣服,自己治病
现在的人:可以只会一样,其他都不会,只会 Java 也能活,不会做饭,不会开 车,不会…
工厂模式 : 实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离 起来,达到提高灵活性的目的。
工厂模式的分类 :
- 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品, 需要修改已有代码)。也就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
- 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)也就是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传 递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创 建对象。
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无 能为力;支持增加产品族)也就是创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类 就可以了,不需要修改之前的代码。
核心本质 :
- 实例化对象,用工厂方法代替 new 操作。
- 将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
2.2 无工厂模式
interface Car{
void run();
}
class Aodi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
public class Test {
public static void main(String[] args) {
Car a = new Aodi();
Car b = new BYD();
a.run();
b.run();
}
}
图示:
2.3 简单工厂模式
概述 : 用来生产同一等级结构中的任意产品。(对于增加新的产品, 需要修改已有代码)。也就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
代码演示 : 它有两种代码实现
第一种方式:
interface Car{
void run();
}
class Aodi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
/**
* 工厂模式:用于创建Car的实例对象
*/
class CarFactory{
public Car getCar(String typeCar){
if ("BYD".equals(typeCar)){
return new BYD();
}else if ("Aodi".equals(typeCar)){
return new Aodi();
}else {
return null;
}
}
}
public class Test1 {
public static void main(String[] args) {
// 使用工厂模式创建对象
Car byd = new CarFactory().getCar("BYD");
byd.run();
Car aodi = new CarFactory().getCar("Aodi");
aodi.run();
}
}
代码图解 :
调用者只要知道他要什么,从哪里拿,如何创建,不需要知道。分工,多出了一 个专门生产 Car 的实现类对象的工厂类。把调用者与创建者分离。
缺点 : 对于增加新产品,不修改代码的话,是无法扩展的。违反了开闭原则(对 扩展开放;对修改封闭)。
2.4 工厂方法模式
概述 : 为了避免简单工厂模式的缺点,不完全满足 OCP(对扩展开放,对修改关闭)。 工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一 个项目或者一个独立的模块而言)工厂类,而工厂方法模式有一组方法实现了相同接口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的方法来分担。
代码演示 :
interface Car{
void run();
}
class Aodi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
/**
* 工厂模式:用于创建Car的实例对象
*/
class CarFactory{
public Car getAodi(){
return new Aodi();
}
public Car getByd(){
return new BYD();
}
}
public class Test1 {
public static void main(String[] args) {
// 使用工厂模式创建对象
Car byd = new CarFactory().getByd();
byd.run();
Car aodi = new CarFactory().getAodi();
aodi.run();
}
}
代码图解 :
小结 :
简单工厂模式与工厂方法模式真正的避免了代码的改动了?没有。在简单工厂模 式中,新产品的加入要修改工厂角色中的判断语句;而在工厂方法模式中,要么 将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就像 上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。
2.5 静态工厂方法模式
概述 : 将前两个工厂模式的方法设置为静态,不用实例工厂,直接通过类名调用方法称为静态工厂。
代码演示:
interface Car{
void run();
}
class Aodi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
/**
* 工厂模式:用于创建Car的实例对象
*/
class CarFactory{
public static Car getAodi(){
return new Aodi();
}
public static Car getByd(){
return new BYD();
}
}
public class Test1 {
public static void main(String[] args) {
// 使用工厂模式创建对象
Car byd = CarFactory.getByd();
byd.run();
Car aodi = CarFactory.getAodi();
aodi.run();
}
}
2.5 抽象工厂模式
抽象工厂概述 : 工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须 对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决? 就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且 抽象工厂模式是三个里面最为抽象、最具一般性的。
抽象工厂模式的用意为: 给客户端提供一个接口,可以创建多个产品族中的产品 对象。
而且使用抽象工厂模式还要满足一下条件:
-
- 系统中有多个产品族,而系统一次只可能消费其中一族产品。
-
- 同属于同一个产品族的产品以其使用。
代码实现
interface Car{
void run();
}
class Aodi implements Car{
public void run() {
System.out.println("奥迪在跑");
}
}
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
}
}
/**
* 工厂接口
*/
interface CarFactory{
Car getCar();
}
/**
* 工厂实现类
*/
class AodiCarFactory implements CarFactory{
public Car getCar() {
return new Aodi();
}
}
class BydCarFactory implements CarFactory{
public Car getCar() {
return new BYD();
}
}
/**
* 测试
*/
public class Test2 {
public static void main(String[] args) {
Car aodi = new AodiCarFactory().getCar();
aodi.run();
Car byd = new BydCarFactory().getCar();
byd.run();
}
}
代码图解 :
3.案例-程序重构
3.1 案例描述
一开始我们不是通过传统的三层架构编写了一个Java项目吗?我们说这个项目中各个板块之间的耦合程度太高了。下面我们就通过工厂设计模式来重新编写这个三层架构。 这里只是演示简单工厂模式
3.2 代码演示
1.创建一个新的包(factory)用来存放工厂类
2.创建工厂类BeanFactory : 该工厂技既创建Dao资源又能创建Service资源。
public class BeanFactory {
// 获取对象
public Object getBean(String typeName){
if ("demoDao".equals(typeName)){
return new DemoDaoImpl();
}else if ("demoService".equals(typeName)){
return new DemoServiceImpl();
}else {
return null;
}
}
}
3.通过工厂模式获取相应资源对象
dao包
DemoDao.java
public interface DemoDao {
void findAll();
}
DemoDaoImpl.java
public class DemoDaoImpl implements DemoDao {
public void findAll() {
System.out.println("DemoDao 查询全部数据~~~");
}
}
Service包
DemoService.java
public interface DemoService {
void findAll();
}
DemoServiceImpl.java
public class DemoServiceImpl implements DemoService {
public void findAll() {
// 通过工厂获取资源对象
DemoDao demoDao = (DemoDao)new BeanFactory().getBean("demoDao");
demoDao.findAll();
System.out.println("DemoServiceDao 从dao查询到数据。。。");
}
}
Controller包
DemoController.java
public class DemoController {
public static void main(String[] args) {
// 通过工厂获取资源对象
DemoService demoService =(DemoService) new BeanFactory().getBean("demoService");
demoService.findAll();
System.out.println("DemoController 通过DemoService查询到全部数据");
}
}
虽然我们通过工厂模式对代码进行了解耦,但是依然有着如下问题。
出现的问题 :
- 如果我们想要配置更多的资源对象,我们必须要改工厂中的方法,从而导致我们还是要对代码进行改变。
- 工厂中出现字符串硬编码问题。也就是说,如果字符串发生改变,那么我们也要对代码进行修改。
解决方式 : 通过XMl配置+反射技术解决相关问题。
3.3 XMl配置+反射技术对代码进一步升级
设计思想:
-
自定义一个xml配置文件。在xml配置文件中设定一个标签bean,假设一个标签bean代表一个对象信息。该标签中有两个属性 : 一个
id
是bean的唯一标识。另一个属性class
是一个类的全限定类名。 -
通过Dom4j技术解析xml文件,或获取到全限定类名。
-
通过反射技术+工厂模式创建出相应的资源对象。
1.自定义一个XML配置文件factory-config.xml
<beans>
<bean id="demoService" class="com.test.service.impl.DemoServiceImpl"></bean>
<bean id="demoDao" class="com.test.dao.impl.DemoDaoImpl"></bean>
</beans>
2.导入Dom4j资源包
3.创建工厂类,该类用于解析XML获取全限定类名,并创建资源对象
public class BeanFactory {
Map<String,String> nameMap = new HashMap<>();
/**
* 创建对象时,解析XML文档获取所有的全限定类名
* @param configName
*/
public BeanFactory(String configName) {
try {
//1,创建解析对象
SAXReader saxReader = new SAXReader();
//2,获取文档对象
Document document = saxReader.read(BeanFactory.class.getClassLoader().getResourceAsStream(configName));
//3,获取根节点对象
Element rootElement = document.getRootElement();
//4,获取根节点中所有子节点对象
List<Element> elements = rootElement.elements();
for (Element element : elements) {
//获取子节点中的文本
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
nameMap.put(id,clazz);
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
/**
* 根据反射技术创建相应的资源对象
* @param idName
* @return
*/
public Object getBean(String idName){
// 根据id查找到相应的全限定类名
String clazzName = nameMap.get(idName);
Object bean =null;
try {
// 通过全限定类名,根据反射技术创建对象
Class<?> aClass = Class.forName(clazzName);
bean = aClass.newInstance();
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
3.通过工厂模式获取相应资源对象
dao包
DemoDao.java
public interface DemoDao {
void findAll();
}
DemoDaoImpl.java
public class DemoDaoImpl implements DemoDao {
public void findAll() {
System.out.println("DemoDao 查询全部数据~~~");
}
}
Service包
DemoService.java
public interface DemoService {
void findAll();
}
DemoServiceImpl.java
public class DemoServiceImpl implements DemoService {
public void findAll() {
// 通过工厂获取资源对象
DemoDao demoDao = (DemoDao)new BeanFactory("factory-config.xml").getBean("demoDao");
demoDao.findAll();
System.out.println("DemoServiceDao 从dao查询到数据。。。");
}
}
Controller包
DemoController.java
/**
* 只是为了演示,就直接使用主方法代替访问请求
*/
public class DemoController {
public static void main(String[] args) {
// 通过工厂获取资源对象
DemoService demoService =(DemoService) new BeanFactory("factory-config.xml").getBean("demoService");
demoService.findAll();
System.out.println("DemoController 通过DemoService查询到全部数据");
}
}
4.总结
-
在简单工厂模式中,新产品的加入要修改工厂角色中的判断语句;
-
在工厂方法模式中,要么 将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死(就像 上面的例子一样)。而且产品对象创建条件的改变必然会引起工厂角色的修改。
-
面对这种情况,Java 的反射机制与配置文件的巧妙结合突破了限制——这在 Spring 中完美的体现了出来。