前言

本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址

1. 简介

上一篇博客介绍了简单工厂模式,简单工厂模式存在一个很严重的问题:

就是当系统需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,这将违背"开闭原则".

本篇将要介绍的工厂方法模式可以规避这个缺点.

2. 工厂方法模式

工厂方法模式又简称为工厂模式,又可称作虚拟构造器模式或多态工厂模式.工厂方法模式是一种创建型模式.

2.1 简单工厂方法模式的弊端

介绍工厂方法模式之前,我们来先看看简单工厂方法模式的弊端.

简单工厂模式最大的缺点是当有新产品要加入到系统中时,需要在其中加入必要的业务逻辑,这违背了"开闭原则".

此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这一问题.

2.2 工厂方法模式定义

定义一个用于创建对象的接口,让子类决定哪一个类实例化.

工厂方法模式让一个类的实例化延迟到其子类.

2.3 结构图

2.4 角色介绍

  • Product(抽象产品)
  • ConcreteProduct(具体产品)
  • Factory(抽象工厂)
  • ConcreteFactory(具体工厂)

2.4.1 Product(抽象产品)

它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类

2.4.2 ConcreteProduct(具体产品)

它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应.

2.4.3 Factory(抽象工厂)

在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品. 抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口.

2.4.4 ConcreteFactory(具体工厂)

它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例.

2.5 代码

  • 抽象工厂
  • 具体工厂
  • 客户端

2.5.1 抽象工厂

与简单工厂模式相比,工厂方法模式最要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或具体类.


/** * @author liuboren * @Title: 工厂接口 * @Description: 工厂模式中的工厂接口 * @date 2019/7/15 14:20 */ public interface Factory { Product factoryMethod(); }

2.5.2 具体工厂

在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责,客户端针对抽象工厂编程,可以在运行时再指定具体工厂类,具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品.


/** * @author liuboren * @Title: 具体工厂 * @Description: 根据需要创建具体的产品类 * @date 2019/7/15 14:21 */ public class ConcreteFactory implements Factory { @Override public Product factoryMethod() { return new ConcreteProduct(); } }

在实际使用时,具体工厂类在实现工厂方法时除了创建具体产品对象之外,还可以负责产品对象的初始化工作以及一些资源和环境配置工作,例如连接数据库、创建文件等

2.5.3 客户端

在客户端代码中,只关心工厂类即可,不同的具体工厂可以创建不同的产品.


/** * @author liuboren * @Title: 客户端类 * @Description: 实际调工厂方法 * @date 2019/7/15 14:28 */ public class Client { public static void main(String[] args) { Factory factory = new ConcreteFactory(); Product product = factory.factoryMethod(); } }

3. 实战

以虚构业务的形式来看看简单工厂模式存在哪些问题以及工厂方法模式是如何解决简单工厂模式存在的问题的.

开发一个系统运行日志记录器.

该记录器可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过更改配置文件自行更换日志记录方式.

需要对日记记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能记录失败.

3.1 设计要点

  • 需要封装日志记录器的初始化过程
  • 用户可能需要更换日志记录方式

3.1.1 需要封装日志记录器的初始化过程

这些初始化工作较为复杂,例如需要初始化其他相关的类,还有可能需要读取配置文件(例如连接数据库或创建文件),导致代码较长,如果将它们都写在构造函数中,会导致构造函数庞大,不利于代码的修改和维护.

3.1.2 用户可能需要更换日志记录方式

在客户端代码中需要提供一种灵活的方式来选择日志记录器,尽量在不修改源代码的基础上更换或者增加日志记录方式.

3.2 使用简单工厂模式解决问题

3.2.1 结构图

3.2.2 工厂类


/** * @author liuboren * @Title: 日志工厂 * @Description: 使用简单工厂方法模式解决需求 * @date 2019/7/15 15:02 */ public class LoggerFactory { //静态工厂方法 public static Logger createLogger(String args){ Logger logger; if("db".equalsIgnoreCase(args)){ logger = new DatabaseLogger(); }else if("file".equalsIgnoreCase(args)){ logger = new FileLogger(); }else{ logger = null; } return logger; } }

上述代码简化了初始化过程,使用简单工厂方法虽然实现了对象的创建和使用分离,但是有其它问题存在.

3.2.3 使用简单工厂模式存在的问题

  • 工厂类过于庞大,包含了大量的if...else...代码,导致维护和测试难度增大.
  • 系统扩展不灵活,如果增加新类型的日志记录器,必须修改静态工厂方法的业务逻辑,违反了"开闭原则"

3.3 使用工厂方法模式

3.3.1 结构图

3.3.2 代码

日志工厂类:


/** * @author liuboren * @Title: 抽象工厂类 * @Description: * @date 2019/7/15 15:21 */ public abstract class AbstractLoggerFactory { public abstract Logger createLogger(); }

文件日志工厂类:



/**

 * @author liuboren

 * @Title: 文件日志工厂

 * @Description: 创建文件工厂类

 * @date 2019/7/15 15:23

 */

public class FileLoggerFactory extends AbstractLoggerFactory {

    @Override

    public Logger createLogger() {

        return new FileLogger();

    }

}

数据库日志工厂类:



/**

 * @author liuboren

 * @Title: 数据库日志工厂方法

 * @Description: 创建数据库日志类

 * @date 2019/7/15 15:26

 */

public class DatabaseLoggerFactory extends AbstractLoggerFactory {

    @Override

    public Logger createLogger() {

        //省略连接数据库代码、初始化数据库代码

        return new DatabaseLogger();

    }

}

日志类:



/**

 * @author liuboren

 * @Title: 日之类

 * @Description:

 * @date 2019/7/15 15:20

 */

public interface Logger {

    void wirteLog();

}

文件日志类:



/**

 * @author liuboren

 * @Title: 文件日志类

 * @Description:

 * @date 2019/7/15 15:06

 */

public class FileLogger implements Logger {

    @Override

    public void wirteLog() {

        System.out.println("文件日志记录日志..");

    }

}

数据库日志类:



/**

 * @author liuboren

 * @Title: 数据库日志类

 * @Description:

 * @date 2019/7/15 15:05

 */

public class DatabaseLogger implements Logger {

    @Override

    public void wirteLog() {

        System.out.println("数据库日志记录日志..");

    }

}

客户端:



/**

 * @author liuboren

 * @Title: 客户端

 * @Description:

 * @date 2019/7/15 15:30

 */

public class Client {

    public static void main(String[] args) {

        AbstractLoggerFactory loggerFactory = new FileLoggerFactory();

        Logger logger = loggerFactory.createLogger();

        logger.wirteLog();

    }

}

3.3.3 工厂方法模式优化

使用反射 + xml文件使工厂方法生成的Logger类型变为可配置的.

xml文件:


<?xml version="1.0" encoding="utf-8" ?> <config> <!--传入完全限定名才能获取到类--> <className>creational.factory.factory.optimize.FileLogger</className> </config>

xml读取工具类:



/**

 * @author liuboren

 * @Title: XML工具类

 * @Description: 读取XMl文件配置

 * @date 2019/7/15 15:50

 */

public class XMLUtil {

    public static Object getBean(){

        try {

            //创建DOM文档对象

            DocumentBuilderFactory dFactory =  DocumentBuilderFactory.newInstance();

            DocumentBuilder builder = dFactory.newDocumentBuilder();

            Document doc;

            doc = builder.parse(new File("Factory_config.xml"));

            //获取包含类名的文本节点

            NodeList nl = doc.getElementsByTagName("className");

            Node classNode = nl.item(0).getFirstChild();

            String cName = classNode.getNodeValue();

            //通过类名生成实例对象并将其返回

            Class c = Class.forName(cName);

            Object obj = c.newInstance();

            return obj;

        }catch (Exception e){

            e.printStackTrace();

            return null;

        }

    }

}

客户端:



/**

 * @author liuboren

 * @Title: 客户端

 * @Description:

 * @date 2019/7/15 15:30

 */

public class Client {

    public static void main(String[] args) {

        AbstractLoggerFactory loggerFactory = new DatabaseLoggerFactory();

        Logger logger = loggerFactory.createLogger();

        logger.wirteLog();

        //测试通过反射方式生成对象

        Logger reflectLogger = (Logger) XMLUtil.getBean();

        reflectLogger.wirteLog();

    }

}

3.3.4 使用反射优化后的工厂类使用方式

  1. 新的日志记录器需要继承抽象日志记录器Logger
  2. 增加新的LoggerFactory对应增加一个新的具体日志记录器工厂,继承抽象日志记录器工厂LoggerFactory,并实现其中的工厂方法createLogger(),设置好初始化参数和环境变量,返回具体日志记录器对象
  3. 修改配置文件LoggerFactory_config.xml,将新增的具体日志记录器工厂类的类名字符串替换原有工厂类类名字符串.
  4. 编译新增的具体日志记录器和具体日志记录器工厂类,运行客户端测试类即可使用心得日志记录方式,而原有类库无须做任何修改,完全符合"开闭原则",通过上述重构可以使得系统更加灵活,由于很多设计模式都关注系统的可扩展性和灵活性,因此都定义了抽象层,在抽象层声明业务方法,而将业务方法的实现放在实现层中.

3.4 重载工厂方法

需求升级,需要使用杜仲方式来初始化日志记录器.

  1. 使用默认实现
  2. 为数据库日记提供数据库连接字符串,为文件日志记录器提供文件路径
  3. 将参数封装到Object类型的对象中,通过Object对象将配置参数传入工厂类.

结构图:

3.5 隐藏工厂方法

有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时,在工厂类中将直接调用产品类的业务方法,客户端无须调用工厂方法创建产品

3.5.1 结构图

3.5.2 代码

xml:


<?xml version="1.0" encoding="utf-8" ?> <config> <!--传入完全限定名才能获取到类--> <className>creational.factory.factory.hide.DatabaseLoggerFactory</className> </config>

抽象工厂:


/** * @author liuboren * @Title: 抽象工厂类 * @Description: * @date 2019/7/15 15:21 */ public abstract class AbstractLoggerFactory { public abstract Logger createLogger(); public void wirteLog(){ Logger logger = this.createLogger(); logger.wirteLog(); } }

客户端:


/** * @author liuboren * @Title: 客户端 * @Description: * @date 2019/7/15 15:30 */ public class Client { public static void main(String[] args) { AbstractLoggerFactory abstractLoggerFactory= (AbstractLoggerFactory) XMLUtil.getBean(); abstractLoggerFactory.wirteLog(); } }

4. 总结

4.1 优点

  • 隐藏创建细节
  • 将创建对象的细节封装在具体工厂内部
  • 易于扩展

4.1.1 隐藏创建细节

在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了那种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名

4.1.2 将创建对象的细节封装在具体工厂内部

基于 工厂角色和产品角色的多态性设计是工厂方法模式的关键.它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部.

工厂方法模式之所以又被成为多态工厂模式,就正是因为所有的具体工厂类都具有统一抽象父类.

4.1.3 易于扩展

在系统加入新产品时无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了.

这样系统的可扩展性也就变得非常好,完全符合"开闭原则"

4.2 缺点

  • 增加了系统的复杂度&性能开销
  • 增加了系统的抽象性和难理解度

4.2.1 增加了系统的复杂度&性能开销

在添加新厂品时,需要编写新的具体产品类,还要提供与之相对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销

4.2.2 增加了系统的抽象性和难理解度

由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时需要用到DOM、反射等技术,增加了系统的实现难度.

4.3 适用场景

  • 客户端不知道它所需要的对象的类
  • 抽象工厂类通过其子类来指定创建哪个对象

4.3.1 客户端不知道它所需要的对象的类

在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中.

4.3.2 抽象工厂类通过其子类来指定创建哪个对象

在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来决定具体需要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖弗雷独享,从而使得系统更容易扩展.

5. 代码&类图下载

代码

类图

Java设计模式学习笔记(三) 工厂方法模式的更多相关文章

  1. Java设计模式(二) 工厂方法模式

    本文介绍了工厂方法模式的概念,优缺点,实现方式,UML类图,并介绍了工厂方法(未)遵循的OOP原则 原创文章.同步自作者个人博客 http://www.jasongj.com/design_patte ...

  2. JAVA设计模式——第 5 章 工厂方法模式【Factory Method Pattern】(转)

    女娲补天的故事大家都听说过吧,今天不说这个,说女娲创造人的故事,可不是“造人”的工作,这个词被现代人滥用了.这个故事是说,女娲在补了天后,下到凡间一看,哇塞,风景太优美了,天空是湛蓝的,水是清澈的,空 ...

  3. Java设计模式(四)工厂方法模式

    定义与类型 定义:定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行. 类型:创建型 适用场景 创建对象需要大量重复的代码 客户端(应用层)不依赖于产 ...

  4. Java设计模式学习笔记三

    工厂模式 简单工厂模式(不属于23种设计模式之一) 属于创建型模式,是工厂模式的一种.简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例.简单工厂模式是工厂模式家族中最简单实用的模式: 简单工厂 ...

  5. Java设计模式菜鸟系列(四)工厂方法模式建模与实现

    转载请注明出处:http://blog.csdn.net/lhy_ycu/article/details/39760895 工厂方法模式(Factory Method) 工厂方法:顾名思义,就是调用工 ...

  6. Java设计模式:Factory Method(工厂方法)模式

    概念定义 工厂方法(Factory Method)模式,又称多态工厂(Polymorphic Factory)模式或虚拟构造器(Virtual Constructor)模式.工厂方法模式通过定义工厂抽 ...

  7. java设计模式学习笔记--接口隔离原则

    接口隔离原则简述 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应建立在最小的接口上 应用场景 如下UML图 类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类 ...

  8. java设计模式学习笔记--单一职责原则

    单一职责原则注意事项和细节 1.降低类的复杂度,一个类只负责一项职责 2.提高可读性,可维护性 3.降低变更引起的风险 4.通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单 ...

  9. Java IO学习笔记三

    Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...

随机推荐

  1. gcc/g++编译(生动形象,从最容易入手的hello world解释了库的概念)

    1. gcc/g++在执行编译工作的时候,总共需要4步 (1).预处理,生成.i的文件[预处理器cpp] (2).将预处理后的文件不转换成汇编语言,生成文件.s[编译器egcs] (3).有汇编变为目 ...

  2. DELPHI编写服务程序总结(在系统服务和桌面程序之间共享内存,在服务中使用COM组件)

    DELPHI编写服务程序总结 一.服务程序和桌面程序的区别 Windows 2000/XP/2003等支持一种叫做“系统服务程序”的进程,系统服务和桌面程序的区别是:系统服务不用登陆系统即可运行:系统 ...

  3. WPF常用第三方控件

    NLog日志控件: Install-Package NLog.Config Mysql数据库控件: Install-Package Mysql.Data 最新版本只支持.net 4.5.2及以上版本, ...

  4. 数据库连接池之_Druid简单使用

    数据库连接池: 连接池是创建和管理一个连接的缓冲池的技术,这些连接真备好被任何需要他们的线程使用,可以对传统的JDBCjava数据库连接()进行优化 在实际开发中,我们需要频繁的操作数据库,这就意味着 ...

  5. File handling in Delphi Object Pascal(处理record类型)

    With new users purchasing Delphi every single day, it’s not uncommon for me to meet users that are n ...

  6. WIN10以后如果Manifest中不写支持WIN10的话,获取版本号的API获取的是6

    if TOSVersion.Major = 10 then  // 高版本的Delphi(比如Berlin)可以这样写 ShowMessage('Windows 10'); 或者: if Win32M ...

  7. Dropbox是同步盘,Box.net是网盘(所以要学习Box)

    自从能无缝用Dropbox后,确实得瑟了很久,但只有可怜巴巴的2G空间,搞不出什么妖蛾子,dropbox的好用,世所共知.百度云盘2T的空间,我却不敢把重要的东西放在里面. 在还没有优盘的时候,我常常 ...

  8. ChartDirector应用笔记(可同时为Web和Qt MFC提供图表)

    ChartDirector介绍 ChartDirector是一款小巧精细的商业图表库.其适用的语言范围非常广泛,包括.Net, Java, Asp, VB, PHP, Python, Ruby, C+ ...

  9. Application生命周期(一)

    1.Application是什么? Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个 application对 ...

  10. Hadoop 学习之路(四)—— Hadoop单机伪集群环境搭建

    一.前置条件 Hadoop的运行依赖JDK,需要预先安装,安装步骤见: Linux下JDK的安装 二.配置免密登录 Hadoop组件之间需要基于SSH进行通讯. 2.1 配置映射 配置ip地址和主机名 ...