当前位置: 首页 > 科技观察

最好不要使用工厂模式

时间:2023-03-21 16:31:59 科技观察

大家好,我说的是北军。在软件开发的过程中,我们会用到很多设计模式,前面介绍过的单例模式,后面还会介绍代理模式、适配器模式、建造者模式等等。针对合适的场景使用合适的设计模式,你会发现业务逻辑会清晰很多。但是对于工厂模式,我奉劝大家,没事最好不要用。什么,为什么这么问?1、什么是工厂模式?定义用于创建对象的接口,但让子类决定实例化哪个类。工厂方法让类将实例化推迟到子类。定义一个创建对象的接口,让其子类决定实例化哪个工厂类,工厂模式将创建过程延迟到子类。说人话:提供创建对象的接口,屏蔽创建对象的过程,从而达到灵活的目的。2.工厂模式的分类工厂模式一般分为三类:①简单工厂②工厂方法③抽象工厂这三种模式从上到下逐渐抽象,更具有通用性。需要注意的是,GOF在书中将工厂模式分为两类《设计模式》:FactoryMethod和AbstractFactory,并将SimpleFactory视为工厂方法模式的一个特例,将两者归为一类。下面我们分别介绍这三种工厂模式。2.1以简单工厂(SimpleFactory)为例,有这样一个需求:根据不同的导入文件(docx、xlsx、pptx),选择不同的解析器进行解析。简单工厂有3个核心对象:Factory:简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类中创建产品类的方法可以被外界直接调用,创建需要的产品对象。.抽象产品:简单工厂模式创建的所有对象的父类,负责描述所有实例共享的公共接口。具体产品:是简单工厂模式的创建对象,所有创建的对象都是扮演这个角色的具体类的实例。①抽像解析器publicinterfaceIOfficeParser{voidparse();}②工具解析器(docx,xlsx,pptx)publicclassWordParserimplementsIOfficeParser{privateStringfilePath;publicWordParser(StringfilePath){this.filePath=filePath;}@Overridepublicvoidparse(){System.out.println("解析docx文件");}}publicclassExcelParser实现IOfficeParser{privateStringfilePath;publicExcelParser(StringfilePath){this.filePath=filePath;}@Overridepublicvoidparse(){System.out.println("解析xlsx文件");}}publicclassPptParserimplementsIOfficeParser{privateStringfilePath;publicPptParser(StringfilePath){this.filePath=filePath;}@Overridepublicvoidparse(){System.out.println("解析pptx文件");}}③构造解析器的工厂publicclassOfficeParserFactory{publicstaticIOfficeParsergetParser(StringfilePath)throwsException{StringfileExtension=getFileExtens离子(文件路径);IOfficeParser解析器=null;if("docx".equalsIgnoreCase(fileExtension)){parser=newWordParser(filePath);}elseif("xlsx".equalsIgnoreCase(fileExtension)){parser=newExcelParser(filePath);}elseif("pptx".equalsIgnoreCase(fileExtension)){parser=newPptParser(filePath);}else{thrownewException("不支持文件:"+fileExtension);}返回解析器;}privatestaticStringgetFileExtension(StringfilePath){//解析文件名获取文件扩展名,如document.docx,返回docxStringfileExtension=filePath.substring(filePath.lastIndexOf(".")+1);返回文件扩展名;}}④测试类publicclassSimpleFactoryTest{publicstaticvoidmain(String[]args)throwsException{StringfilePath="Document.docx";IOfficeParser解析器=OfficeParserFactory.getParser(filePath);解析器.parse();StringfilePath1="Table.xlsx";IOfficeParserparser1=OfficeParserFactory.getParser(filePath1);解析器1.解析();}}⑤综上所述,这是一个简单的工厂,客户端避免了直接创建解析器的责任,只需要调用工厂类进行解析即可。原理(对扩展开放,对修改关闭)分析简单工厂模式:当添加一个文件解析时,比如老版本的doc格式。这时候只需要添加一个解析器类,客户端(理解为测试类,调用者)不需要改变,然后在工厂类OfficeParserFactory中添加一个else-if分支。这时候可能有同学会问,那么修改OfficeParserFactory类,不是违反了开闭原则吗?但实际上,只要不经常添加新的解析器,偶尔修改OfficeParserFactory类,不符合开闭原则,也是可以的。公认。看起来很完美,细心的同学可能会问,所有的解析类对象都是在OfficeParserFactory类中创建的,假设是某个解析类,比如doc,创建一个解析器对象不是简单的new,还包括一些其他的操作,此时是否可以将这些代码全部写入OfficeParserFactory?有没有更优雅的写法?没错,就是下面要介绍的工厂模式。2.2工厂方法(FactoryMethod)为了解决以上问题,我们可以为工厂类创建一个工厂,也就是工厂的工厂,来创建工厂类对象。①为每个特定的解析器创建一个工厂publicclassExcelParserFactoryimplementsIOfficeParserFactory{@OverridepublicIOfficeParsercreateParser(){//TODO对对象创建执行一些操作}}②创建解析器工厂publicclassOfficeParserFactory{publicstaticIOfficeParsergetParser(StringfilePath)throwsException{StringfileExtension=getFileExtension(filePath);IOfficeParserFactoryparserFactory=OfficeParserFactoryMap.getOfficeParseFactory(fileExtension);if(parserFactory==null){thrownewException("不支持文件:"+file);}IOfficeParser解析器=parserFactory.createParser();返回解析器;}privatestaticStringgetFileExtension(StringfilePath){//解析文件名获取文件扩展名,如document.docx,返回docxStringfileExtension=filePath.substring(filePath.lastIndexOf(".")+1);返回文件扩展名;}}③创建解析器工厂的工厂类publicclassOfficeParserFactoryMap{privatestaticfinalMapparserFactoryCached=newHashMap<>();static{parserFactoryCached.put("docx",newWordParserFactory());parserFactoryCached.put("xlxs",newExcelParserFactory());parserFactoryCached.put("pptx",newPptParserFactory());}publicstaticIOfficeParserFactorygetOfficeParseFactory(Stringtype){if(type==null||type.isEmpty()){returnnull;}返回parserFactoryCached.get(type.toLowerCase());}}④测试类publicclassFactoryTest{publicstaticvoidmain(String[]args)throwsException{StringfilePath="document.docx";IOfficeParser解析器=OfficeParserFactory.getParser(filePath);解析器.parse();}⑤小结在工厂模式下,如果我们要增加新的文件解析,比如mdb格式(办公访问套件),只需要新建一个解析器类和parserFactory类,将新的parserFactory类添加到OfficeParserFactoryMap类中图中代码改动很少,基本符合开闭原则。但是,我们看到工厂模式增加了很多工厂类,会增加代码的重复。如果每个工厂类只是一个简单的new操作,就没有必要使用这种模式,直接使用简单工厂模式即可。2.3抽象工厂(AbstractFactory)这种模式比较特殊,使用场景不多,大家可以简单了解一下。我们知道doc和docx都是officeword文档后缀,类似于xls和xlsx都是officeExcel表格后缀,还有ppt和pptx。doc/xlx/ppt是旧版office的文件后缀,都是二进制文件组成。它们在解析时有共同点,而docx/xlsx/pptx都是新版office的文件后缀,由ooxml结构组成。相当于一组旧办公室和一组新办公室。如果还是用工厂模式来实现,那我们就需要为每个类型写一个工厂类,类太多维护起来会很困难,那怎么解决呢?抽象工厂模式就是为这种特殊场景而生的。我们可以使工厂复制创建多个不同类型的对象,而不是只创建一个解析器对象。具体代码实现如下:publicinterfaceIOfficeParserFactory{IOfficeParsercreateParser();IOldOfficeParsercreateOldParser();}publicclassExcelParserFactoryimplementsIOfficeParserFactory{@OverridepublicIOfficeParsercreateParser(){返回新的ExcelParser();}@OverridepublicIOldOfficeParsercreateOldParser{reOldnewDocParser();}}3。简单工厂和工厂方法的区别简单工厂:把创建不同对象的逻辑放在一个工厂类中。工厂方法:将创建不同对象的逻辑放在不同的工厂类中,先使用某个工厂类的一个工厂类获取某个工厂,在某个工厂中创建对象。这样一来,区别就很明显了。如果创建对象的逻辑比较复杂,需要进行各种初始化操作,此时可以使用工厂方法,将复杂的创建逻辑拆分到多个工厂类中;创建对象的逻辑非常简单。简单,不需要创建多个额外的工厂类,直接使用简单工厂即可。4.工厂模式封装变化的作用:创建逻辑可能会发生变化,封装成工厂类后,创建逻辑的变化对调用者是透明的。代码复用:创建代码,提取到独立的工厂类后可以复用。隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。控制复杂度:抽取创建代码,让原来的函数或类职责单一,代码更简洁。