资讯专栏INFORMATION COLUMN

Java设计模式之(二)——工厂模式

Doyle / 2863人阅读

摘要:需要说明的是在设计模式一书中将工厂模式分为两类工厂方法模式与抽象工厂模式,将简单工厂模式看为工厂方法模式的一种特例,两者归为一类。工厂模式的作用工厂模式的作用封装变化创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。

1、什么是工厂模式

Define an interface for creating an object,but let subclasses decide which class toinstantiate.Factory Method lets a class defer instantiation to subclasses.

定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

说人话:提供创建对象的接口,将创建对象的过程屏蔽,从而达到灵活的目的。

2、工厂模式分类

一般情况下,工厂模式分为三类:

①、简单工厂模式(Simple Factory)

②、工厂方法模式(Factory Method)

③、抽象工厂模式(Abstract Factory)

这三种模式从上到下逐步抽象,并且更具一般性。

需要说明的是:GOF 在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory),将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。

下面我们分别介绍这三种工厂模式。

2.1 简单工厂(Simple Factory)

比如有这样一个需求:

根据导入的不同文件(docx,xlsx,pptx),选择不同的解析器进行解析。

简单工厂有三个核心对象:

1.工厂:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。

2.抽象产品 :简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。

3.具体产品:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

①、抽象解析器

public interface IOfficeParser {    void parse();}

②、具体解析器(docx,xlsx,pptx)

public class WordParser implements IOfficeParser{    private String filePath;    public WordParser(String filePath){        this.filePath = filePath;    }    @Override    public void parse() {        System.out.println("解析 docx 文件");    }}
public class ExcelParser implements IOfficeParser{    private String filePath;    public ExcelParser(String filePath){        this.filePath = filePath;    }    @Override    public void parse() {        System.out.println("解析 xlsx 文件");    }}
public class PptParser implements IOfficeParser {    private String filePath;    public PptParser(String filePath){        this.filePath = filePath;    }    @Override    public void parse() {        System.out.println("解析 pptx 文件");    }}

③、构造解析器的工厂

public class OfficeParserFactory {    public static IOfficeParser getParser(String filePath) throws Exception {        String fileExtension = getFileExtension(filePath);        IOfficeParser parser = null;        if("docx".equalsIgnoreCase(fileExtension)){            parser = new WordParser(filePath);        }else if("xlsx".equalsIgnoreCase(fileExtension)){            parser = new ExcelParser(filePath);        }else if("pptx".equalsIgnoreCase(fileExtension)){            parser = new PptParser(filePath);        }else{            throw new Exception("file is not supported:"+fileExtension);        }        return parser;    }    private static String getFileExtension(String filePath){        // 解析文件名获取文件扩展名,比如 文档.docx,返回 docx        String fileExtension = filePath.substring(filePath.lastIndexOf(".")+1);        return fileExtension;    }}

④、测试类

public class SimpleFactoryTest {    public static void main(String[] args) throws Exception {        String filePath = "文档.docx";        IOfficeParser parser = OfficeParserFactory.getParser(filePath);        parser.parse();        String filePath1 = "表格.xlsx";        IOfficeParser parser1 = OfficeParserFactory.getParser(filePath1);        parser1.parse();    }}

⑤、总结

这便是简单工厂,客户端避免了直接创建解析器的责任,只需要调用工厂类去解析就行了。

可以从开闭原则(对扩展开放,对修改关闭)来分析简单工厂模式:当增加一种文件解析,比如老版本的 doc 格式。这时候只需要新增一个 parser 类即可,客户端(理解为测试类,调用端)不用改变,然后在工厂类 OfficeParserFactory 新增一个 else-if 分支即可。

这时候可能有同学会问了,那修改了 OfficeParserFactory 类,不就违反开闭原则了吗?但其实只要不是频繁的添加新的 parser,偶尔修改一下 OfficeParserFactory 类,稍微不符合开闭原则,也是可以接受的。

看上去比较完美,细心的同学可能会问,所有的解析类对象创建都在 OfficeParserFactory 类中,假设某个解析类,比如 doc 创建parser 对象并不是简单的 new ,还包括一些其它的操作,这时候难道把这些代码也全部写到 OfficeParserFactory 中吗?有没有更优雅的写法呢?

有,就是下面要介绍的 工厂模式。

2.2 工厂方法(Factory Method)

为了解决上面的问题,我们可以为工厂类在创建一个工厂,也就是工厂的工厂,用来创建工厂类对象。

①、给每一个具体解析器创建工厂

public class ExcelParserFactory implements IOfficeParserFactory {    @Override    public IOfficeParser createParser() {        // TODO 进行创建对象的一些操作        return new ExcelParser();    }}

②、创建解析器的工厂

public class OfficeParserFactory {    public static IOfficeParser getParser(String filePath) throws Exception {        String fileExtension = getFileExtension(filePath);        IOfficeParserFactory parserFactory = OfficeParserFactoryMap.getOfficeParseFactory(fileExtension);        if(parserFactory == null){            throw new Exception("file is not supported:"+fileExtension);        }        IOfficeParser parser = parserFactory.createParser();        return parser;    }    private static String getFileExtension(String filePath){        // 解析文件名获取文件扩展名,比如 文档.docx,返回 docx        String fileExtension = filePath.substring(filePath.lastIndexOf(".")+1);        return fileExtension;    }}

③、创建解析器工厂的工厂类

public class OfficeParserFactoryMap {    private static final Map parserFactoryCached = new HashMap<>();    static {        parserFactoryCached.put("docx",new WordParserFactory());        parserFactoryCached.put("xlxs",new ExcelParserFactory());        parserFactoryCached.put("pptx",new PptParserFactory());    }    public static IOfficeParserFactory getOfficeParseFactory(String type){        if(type == null || type.isEmpty()){            return null;        }        return parserFactoryCached.get(type.toLowerCase());    }}

④、测试类

public class FactoryTest {    public static void main(String[] args) throws Exception {        String filePath = "文档.docx";        IOfficeParser parser = OfficeParserFactory.getParser(filePath);        parser.parse();    }}

⑤、总结

在工厂模式中,如果我们要增加新的文件解析,比如 mdb 格式(office access套件),就只需要创建新的 parser 类和 parserFactory 类,并且在 OfficeParserFactoryMap 类中将新的 parserFactory 类添加到 map 中即可。代码的改动非常少,基本上是符合开闭原则的。

但是,我们看到工厂模式新增了很多 factory 类,会增加代码的复制性,如果每个 factory 类只是做简单的 new 操作,则没必要使用该模式,直接用简单工厂模式即可。

2.3 抽象工厂(Abstract Factory)

这种模式比较特殊,使用场景不多,大家简单了解一下就行。

我们知道 doc 和 docx 都是 office word 文档后缀,类似 xls 和 xlsx 都是 office Excel 表格后缀,还有 ppt 和 pptx。doc/xlx/ppt 都是旧版本 office 文件后缀,都是二进制组成,解析的时候有共同之处,而 docx/xlsx/pptx 是office新版本文件后缀,是通过 ooxml 结构组成。相当于一组是老的office,一组是新的office。

如果我们还是用工厂模式来实现的话,那每一种都要编写一个工厂类,过多的类会难以维护,那怎么解决呢?

抽象工厂模式就是针对这种特殊的场景诞生,我们可以让一个工厂复制创建多个不同类型的对象,而不是只创建一个 parser 对象。

具体代码实现如下:

public interface IOfficeParserFactory {    IOfficeParser createParser();    IOldOfficeParser createOldParser();}
public class ExcelParserFactory implements IOfficeParserFactory {    @Override    public IOfficeParser createParser() {        return new ExcelParser();    }    @Override    public IOldOfficeParser createOldParser() {        return new DocParser();    }}

3、简单工厂和工厂方法区别

简单工厂:将创建不同对象的逻辑放在一个工厂类中。

工厂方法:将创建不同对象的逻辑放在不同工厂类中,先用一个工厂类的工厂类得到某个工厂,在某这个工厂来创建对象。

这样讲区别就很明显了,如果创建对象的逻辑比较复杂,要做各种初始化操作,这时候使用工厂方法,能够将复杂的创建逻辑拆分到多个工厂类中;而创建对象的逻辑很简单,就没必要额外创建多个工厂类,直接使用简单工厂即可。

4、工厂模式的作用

封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。

代码复用:创建代码抽离到独立的工厂类之后可以复用。

隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。

控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

看完知道为啥没事别用工厂模式了吧,因为太好用了,你会爱上它的。

作者:IT可乐

资源:微信搜【IT可乐】关注我,回复 【电子书】有我特别筛选的免费电子书。
本文版权归作者所有,欢迎转载,但未经作者同意不能转载,否则保留追究法律责任的权利。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123759.html

相关文章

  • Java - 收藏集 - 掘金

    摘要:强大的表单验证前端掘金支持非常强大的内置表单验证,以及。面向对象和面向过程的区别的种设计模式全解析后端掘金一设计模式的分类总体来说设计模式分为三大类创建型模式,共五种工厂方法模式抽象工厂模式单例模式建造者模式原型模式。 强大的 Angular 表单验证 - 前端 - 掘金Angular 支持非常强大的内置表单验证,maxlength、minlength、required 以及 patt...

    XiNGRZ 评论0 收藏0
  • php设计模式

    摘要:我们今天也来做一个万能遥控器设计模式适配器模式将一个类的接口转换成客户希望的另外一个接口。今天要介绍的仍然是创建型设计模式的一种建造者模式。设计模式的理论知识固然重要,但 计算机程序的思维逻辑 (54) - 剖析 Collections - 设计模式 上节我们提到,类 Collections 中大概有两类功能,第一类是对容器接口对象进行操作,第二类是返回一个容器接口对象,上节我们介绍了...

    Dionysus_go 评论0 收藏0
  • php设计模式

    摘要:我们今天也来做一个万能遥控器设计模式适配器模式将一个类的接口转换成客户希望的另外一个接口。今天要介绍的仍然是创建型设计模式的一种建造者模式。设计模式的理论知识固然重要,但 计算机程序的思维逻辑 (54) - 剖析 Collections - 设计模式 上节我们提到,类 Collections 中大概有两类功能,第一类是对容器接口对象进行操作,第二类是返回一个容器接口对象,上节我们介绍了...

    vspiders 评论0 收藏0
  • Java 设计模式工厂模式

    摘要:与以往的使用的方式不同,工厂模式使用工厂实例化对象。抽象工厂模式亮相抽象工厂模式抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。 写在前面 这篇博文介绍设计模式的形式将与其他篇博文不太一样,这里我们将从一个问题入手,逐步了解到简单工厂、工厂方法与抽象工厂模式。 PS:这篇博文涉及的内容较多,所以篇幅有点长,请耐心阅读。 为什么要使用工厂模式? 在 OO ...

    Jochen 评论0 收藏0
  • 工厂模式理解了没有?

    摘要:我认为按书上的顺序比较好理解因为简单静态工厂模式是在工厂方法模式上缩减,抽象工厂模式是在工厂方法模式上再增强。所以我就先讲工厂方法模式了。抽象工厂模式抽象工厂模式就比较复杂了,我们一般的应用都写不到。 前言 只有光头才能变强 回顾前面: 给女朋友讲解什么是代理模式 包装模式就是这么简单啦 单例模式你会几种写法? 昨天写了单例模式了,今天是时候写工厂模式啦~ 工厂模式我个人认为其实比...

    苏丹 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<