目录
- 一、简介
- 1.1、接口定义
- 1.2、调用
- 二、简单工厂模式
- 2.1、解析工厂
- 2.2、调用
- 三、工厂方法模式
- 3.1、解析器接口定义
- 3.2、解析工厂接口定义
- 3.3、解析器工厂的工厂
- 3.4、调用
- 四、抽象工厂模式
- 4.1、内容解析器
- 4.2、工厂类
- 三、优点与缺点
一、简介
工厂模式我将它分为三类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
抽象工厂用得比较少,我主要讲解下简单工厂模式和工厂方法模式,假设我们根据配置文件的扩展名(xml、yml、properties)获取到不同的解析器XmlRuleConfigParser、YmlRuleConfigParser、PropertiesRuleConfigParser,从而解析得到结果(RuleConfig),相信大家很简单的就写出了如下的代码:
1.1、接口定义
public class RuleConfig {// 属性忽略
}public interface IRuleConfigParser {RuleConfig parse(String configText);
}public class XmlRuleConfigParser implements IRuleConfigParser {@Overridepublic RuleConfig parse(String configText) {// 实现 XML 配置解析逻辑// 返回 RuleConfig 对象return new RuleConfig(); // 假设这里返回 RuleConfig 对象}
}public class YmlRuleConfigParser implements IRuleConfigParser {@Overridepublic RuleConfig parse(String configText) {// 实现 YML 配置解析逻辑// 返回 RuleConfig 对象return new RuleConfig(); // 假设这里返回 RuleConfig 对象}
}public class PropertiesRuleConfigParser implements IRuleConfigParser {@Overridepublic RuleConfig parse(String configText) {// 实现 Properties 配置解析逻辑// 返回 RuleConfig 对象return new RuleConfig(); // 假设这里返回 RuleConfig 对象}
}
这里我们定义了一个解析器接口,并且有三个解析都实现了该接口,最终返回解析的结果。
1.2、调用
RuleConfigSource.java
public class RuleConfigSource {public RuleConfig loadConfig() {String configFilePath = "config.xml"; // 假设配置文件路径String fileType = getFileExtension(configFilePath); // 获取文件类型(xml、yml、properties)IRuleConfigParser parser=null;if ("xml".equalsIgnoreCase(fileType)) {parser= new XmlRuleConfigParser();} else if ("yml".equalsIgnoreCase(fileType)) {parser= new YmlRuleConfigParser();} else if ("properties".equalsIgnoreCase(fileType)) {parser= new PropertiesRuleConfigParser();}else {throw new IllegalArgumentException("Unknown file type: " + fileType);}// 解析配置文件RuleConfig ruleConfig = parser.parse(configFilePath);// 返回解析后的 ruleConfig 对象return ruleConfig;}private String getFileExtension(String filePath) {if (filePath == null || filePath.isEmpty()) {throw new IllegalArgumentException("File path is invalid");}int lastDotIndex = filePath.lastIndexOf('.');if (lastDotIndex == -1) {throw new IllegalArgumentException("File path does not contain a valid file extension");}return filePath.substring(lastDotIndex + 1);}}
我敢肯定的说,绝大部分的小伙伴都是这么写的,完全没毛病,我以前也是这么写过,不过我们今天说的是设计模式,讲的是一个接近规范的方法,而不是说代码可读性什么的。那就看看使用简单工厂模式是什么样的。
二、简单工厂模式
简单工厂模式(Simple Factory Pattern)是通过一个工厂类来创建不同类型的对象,客户端通过工厂类的静态方法来获取所需的对象。在简单工厂模式中,工厂类负责所有产品的创建。
首先是要理解为啥要改,一切的依据都是从七大设计原则来探讨设计模式,如果不结合实际业务,确实有点不合适。在我们使用的RuleConfigSource并没有满足单一职责原则,它并不需要知道怎么去获取解析器的详细信息;也不符合开闭原则,所有的逻辑都在这里,修改不便。在接口定义不变的前提下,改造如下:
2.1、解析工厂
RuleConfigParserFactory.java
import java.util.HashMap;
import java.util.Map;public class RuleConfigParserFactory {private static final Map<String, RuleConfigParser> PARSER_MAP = new HashMap<>();static {PARSER_MAP.put("xml", new XmlRuleConfigParser());PARSER_MAP.put("yml", new YmlRuleConfigParser());PARSER_MAP.put("properties", new PropertiesRuleConfigParser());}public static RuleConfigParser createParser(String fileExtension) {if (PARSER_MAP.containsKey(fileExtension)) {return PARSER_MAP.get(fileExtension);}return null;}}
2.2、调用
public class RuleConfigSource {public RuleConfig loadConfig() {String configFilePath = "config.xml"; // 假设配置文件路径// 获取文件后缀(可以放到工具类)String fileExtension = getFileExtension(configFilePath);// 获取解析器RuleConfigParser parser = RuleConfigParserFactory.createParser(fileExtension);if (parser==null){throw new IllegalArgumentException("Unknown file type: " + fileExtension);}// 解析配置文件RuleConfig ruleConfig = parser.parse(configFilePath);// 返回解析后的 ruleConfig 对象return ruleConfig;}private static String getFileExtension(String filePath) {// 从文件路径中提取文件类型(后缀)if (filePath == null || filePath.isEmpty()) {throw new IllegalArgumentException("File path is invalid");}int lastDotIndex = filePath.lastIndexOf('.');if (lastDotIndex == -1) {throw new IllegalArgumentException("File path does not contain a valid file extension");}return filePath.substring(lastDotIndex + 1);}}
这里的RuleConfigSource就不会涉及到具体解析器的构建和解析,并且增加一个也与它没关系,只需要改动RuleConfigParserFactory,有些人说,这也违反了开闭原则?实际上如果不是很频繁的添加新的解析器,只是偶尔修改RuleConfigParserFactory的代码,即便不符合开闭原则,也是可以被接受的。并且这里通过静态代码块,节省了内存和对象创建的开销。
三、工厂方法模式
工厂方法模式(Factory Method Pattern)则是将对象的创建推迟到具体工厂类中,定义一个抽象的工厂接口,每个具体工厂类都实现这个接口,负责创建特定的对象。每个具体工厂类只负责创建单一类型的对象。
还是用之前的示例来改造成工厂方法模式,理论上工厂方法模式比简单工厂模式更加符合开闭原则。
3.1、解析器接口定义
public class RuleConfig {// 属性忽略
}public interface IRuleConfigParser {RuleConfig parse(String configText);
}public class XmlRuleConfigParser implements IRuleConfigParser {@Overridepublic RuleConfig parse(String configText) {// 实现 XML 配置解析逻辑// 返回 RuleConfig 对象return new RuleConfig(); // 假设这里返回 RuleConfig 对象}
}public class YmlRuleConfigParser implements IRuleConfigParser {@Overridepublic RuleConfig parse(String configText) {// 实现 YML 配置解析逻辑// 返回 RuleConfig 对象return new RuleConfig(); // 假设这里返回 RuleConfig 对象}
}public class PropertiesRuleConfigParser implements IRuleConfigParser {@Overridepublic RuleConfig parse(String configText) {// 实现 Properties 配置解析逻辑// 返回 RuleConfig 对象return new RuleConfig(); // 假设这里返回 RuleConfig 对象}
}
3.2、解析工厂接口定义
我们需要给每个解析器都增加一个工厂类。
public interface IRuleConfigParserFactory {IRuleConfigParser createParser();
}public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new XmlRuleConfigParser();}
}public class YmlRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new YmlRuleConfigParser();}
}public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new PropertiesRuleConfigParser();}
}
3.3、解析器工厂的工厂
然后管理这些工程类,也就是建立一个工厂的工厂。
import java.util.HashMap;
import java.util.Map;public class RuleConfigParserFactoryMap {private static final Map<String, IRuleConfigParserFactory> PARSER_MAP = new HashMap<>();static {PARSER_MAP.put("xml", new XmlRuleConfigParserFactory());PARSER_MAP.put("yml", new YmlRuleConfigParserFactory());PARSER_MAP.put("properties", new PropertiesRuleConfigParserFactory());}public static IRuleConfigParserFactory getParserFactory(String fileExtension) {// 获取解析工厂if (PARSER_MAP.containsKey(fileExtension)) {return PARSER_MAP.get(fileExtension);}return null;}}
3.4、调用
public class RuleConfigSource {public RuleConfig loadConfig() {String configFilePath = "config.xml"; // 假设配置文件路径String fileExtension = getFileExtension(configFilePath);// 获取文件类型(xml、yml、properties)// 获取解析器IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(fileExtension);if (parserFactory == null) {throw new IllegalArgumentException("未找到解析工厂");}IRuleConfigParser parser = parserFactory.createParser();// 返回解析后的 ruleConfig 对象return parser.parse(configFilePath);}private static String getFileExtension(String filePath) {// 从文件路径中提取文件类型(后缀)// 这里假设实现获取文件类型的逻辑if (filePath == null || filePath.isEmpty()) {throw new IllegalArgumentException("File path is invalid");}int lastDotIndex = filePath.lastIndexOf('.');if (lastDotIndex == -1) {throw new IllegalArgumentException("File path does not contain a valid file extension");}return filePath.substring(lastDotIndex + 1);}}
当我们需要新的解析器时,只需要定义新的解析器对应的类和工厂类,并且缓存起来,这样代码也改得很少,基本符合开闭原则。但是和简单工厂方法相比,实现上差不多,反而增加了代码的复杂度,尤其是在工厂类的实现非常简单的情况下,有点过渡设计了,这时使用简单工厂方法就可以了如果每个解析对象的创建逻辑都比较的复杂,需要组合其他的组件,那么可以考虑工厂方法模式。
四、抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)用于创建一组相关或相互依赖的对象,它通过提供一个创建一系列相关对象的接口,可以创建不同类型的对象组。
工厂方法模式已经复杂起来了,抽象工厂模式很大程度是在这个基础上演变的。比如我们是之前是按照后缀名解析的,假设我们同时需要按照内容格式来解析呢?那工程类就变成6个了,这个时候就可以使用抽象工厂模式了。
4.1、内容解析器
假设我们增加解析器
内容解析器
public interface IContentConfigParser {RuleConfig parse(String configText);
}public class XmlContentConfigParser implements IContentConfigParser {@Overridepublic RuleConfig parse(String configText) {return new RuleConfig();}
}public class YmlContentConfigParser implements IContentConfigParser {@Overridepublic RuleConfig parse(String configText) {return new RuleConfig();}
}public class PropertiesContentConfigParser implements IContentConfigParser {@Overridepublic RuleConfig parse(String configText) {return new RuleConfig();}
}
4.2、工厂类
public interface IConfigParserFactory {IRuleConfigParser createRuleParser();IContentConfigParser createContentParser();
}public class XmlConfigParserFactory implements IConfigParserFactory {@Overridepublic IRuleConfigParser createRuleParser() {return new XmlRuleConfigParser();}@Overridepublic IContentConfigParser createContentParser() {return new XmlContentConfigParser();}
}public class YmlConfigParserFactory implements IConfigParserFactory {@Overridepublic IRuleConfigParser createRuleParser() {return new YmlRuleConfigParser();}@Overridepublic IContentConfigParser createContentParser() {return new YmlContentConfigParser();}
}public class PropertiesConfigParserFactory implements IConfigParserFactory {@Overridepublic IRuleConfigParser createRuleParser() {return new PropertiesRuleConfigParser();}@Overridepublic IContentConfigParser createContentParser() {return new PropertiesContentConfigParser();}
}
从而达到减少工厂类的个数,不过实际中抽象工厂模式用得比较少。可能很多小伙伴还是觉得这个工厂模式有问题,比如本文中的工厂或者工厂的工厂缓存,可以使用配置文件+反射去掉,完全适合开闭原则,每个设计模式都有自己的特色和缺点,就看是否被接受。遵循一些原则的同时,需要考虑代码的复杂度,清晰度。
三、优点与缺点
简单工厂模式:
优点:
- 实现简单,易于理解和使用。
- 将对象的创建过程封装在工厂类中,客户端无需了解对象创建的具体逻辑。
缺点:
- 工厂类集中了所有对象的创建逻辑,如果需要添加新产品,可能需要修改工厂类的逻辑,不符合开闭原则。
- 不符合单一职责原则,因为工厂类既要负责对象的创建,又要负责选择创建哪个对象。
工厂方法模式:
优点:
-
每个具体工厂只负责创建特定的对象,符合单一职责原则。
-
可以通过扩展具体工厂类来添加新产品,无需修改现有代码,符合开闭原则。
缺点: -
增加了系统中类的个数,增加了系统的复杂度。
-
客户端需要知道所使用的具体工厂类,导致与具体工厂类耦合。
抽象工厂模式:
优点:
- 具有工厂方法模式的所有优点,同时能够创建一组相关的产品对象,形成产品族。
- 客户端与具体产品的实现解耦,更符合依赖倒置原则。
缺点:
- 添加新产品族比较复杂,需要修改抽象工厂接口及其所有实现类。
- 增加系统的抽象性和理解难度,需要同时理解多个抽象类和接口。
根据实际需求选择适合的工厂模式是非常重要的,简单工厂适用于对象较少且创建逻辑不复杂的情况;工厂方法适用于需要创建多种类型对象的情况;而抽象工厂适用于创建一组相关对象的场景,但要考虑到增加产品的复杂度。