工厂模式为一种创建型设计模式,主要目的在于将对象的创建与使用进行解耦,使用者并不用关心对象具体的创建逻辑。一般分为简单工厂、工厂方法、抽象工厂。
下述介绍均以文件解析为例子。根据文件类型的不同,我们需要不同的文件解析器,如json解析器、xml解析器来实现不同的解析逻辑。
ITypeParser是一个接口,定义解析方法
public interface ITypeParser {
void parse(String text);
}
JsonTypeParser是一个具体的解析器,实现ITypeParser接口,用于解析json文件
public class JsonTypeParser implements ITypeParser {
@Override
public void parse(String text) {
System.out.println("jsonParser");
}
}
XmlTypeParser是一个具体的解析器,实现ITypeParser接口,用于解析xml文件
public class XmlTypeParser implements ITypeParser {
@Override
public void parse(String text) {
System.out.println("xmlParser");
}
}
在不使用工厂模式的情况下,我们想要根据不同的文件类型获取不同的解析器,一般的做法如下:
@Test
public void test1(){
String fileExtension = getFileExtension();
ITypeParser parser = null;
if("json".equalsIgnoreCase(fileExtension)){
parser = new JsonTypeParser();
}else if("xml".equalsIgnoreCase(fileExtension)){
parser = new XmlTypeParser();
}
parser.parse("");
}
public String getFileExtension(){
return "json";
}
这种方法存在两个问题:
为了解耦对象的创建和使用,让代码逻辑更加清晰,将对象的创建剥离到一个独立的类中,这个类只负责对象的创建。这就是简单工厂模式
public class TypeParserFactory {
public static ITypeParser getTypeParser(String fileExtension){
ITypeParser parser = null;
if("json".equalsIgnoreCase(fileExtension)){
parser = new JsonTypeParser();
}else if("xml".equalsIgnoreCase(fileExtension)){
parser = new XmlTypeParser();
}
return parser;
}
}
程序员在需要一个解析器对象时,只需要调用TypeParserFactory.getTypeParser()
即可,如果需要扩展其他 的解析器也只需要修改这一处,不用找到所有创建解析器对象的地方进行修改,增加了扩展性。
实际上,如果parser可以复用,为了节省内存和对象创建的时间,可以将parser缓存起来,改进后代码如下:
public class TypeParserFactory {
private static final Map<String, ITypeParser> cacheParsers = new HashMap<>();
static {
cacheParsers.put("json",new JsonTypeParser());
cacheParsers.put("xml",new XmlTypeParser());
}
public static ITypeParser getTypeParser(String fileExtension){
return cacheParsers.get(fileExtension);
}
}
这有点类似单例模式与简单工厂的结合。
上述代码在扩展新解析器时虽然只需要修改一处,增加if-else即可,但是违反了开闭原则(对扩展开放,对修改关闭)。当然,在没有太多parser情况下,这样的代码是可行的。
如果一定要将if分支逻辑去掉,更加复合开闭原则(第二种简单工厂并不通用,只适合用于具体对象可复用时),这就需要用到工厂方法模式
ITypeParserFactory是一个接口,定义获取具体工厂的方法
public interface ITypeParserFactory {
ITypeParser getTypeParser();
}
JsonTypeParserFactory是一个具体的解析器工厂,实现了ITypeParserFactory接口,用于获取json解析器
public class JsonTypeParserFactory implements ITypeParserFactory{
@Override
public ITypeParser getTypeParser() {
return new JsonTypeParser();
}
}
XmlTypeParserFactory是一个具体的解析器工厂,实现了ITypeParserFactory接口,用于获取xml解析器
public class XmlTypeParserFactory implements ITypeParserFactory{
@Override
public ITypeParser getTypeParser() {
return new XmlTypeParser();
}
}
将解析器的获取放在该解析器对应的具体的工厂中进一步解耦,这个时候如果需要扩展一个新的解析器,只需要实现一个类实现ITypeParserFactory
接口,在getTypeParser()
方法中创建新的解析器即可。工厂方法模式比简单工厂模式更符合开闭原则。
看起来很完美,但是怎样获取具体的解析器工厂呢
ITypeParserFactory parserFactory = null;
if ("json".equalsIgnoreCase(fileExtension)) {
parserFactory = new JsonTypeParserFactory ();
} else if ("xml".equalsIgnoreCase(fileExtension)) {
parserFactory = new XmlTypeParserFactory ();
}
ITypeParser parser = parserFactory.getTypeParser();
如果按照上述方式获取不同的解析器工厂,工厂再创建解析器,一切好像又回到了原点。
但是对于工厂类而言,其复用性比具体的对象要好得多,因为工厂类一般不包含可作为参数配置的成员变量,完全可以复用。因此上述提到的简单工厂第二种方式更适合此时使用。
可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象:
public class TypeParserFactoryMap {
private static final Map<String,ITypeParserFactory> cacheFactories = new HashMap<>();
static {
cacheFactories.put("json",new JsonTypeParserFactory());
cacheFactories.put("xml",new XmlTypeParserFactory());
}
public static ITypeParserFactory getParserFactory(String type){
return cacheFactories.get("type");
}
}
这个时候如果要扩展新的解析器,只需要创建实现了ITypeParserFactory
接口的类,然后将其加入cache中即可。
但是,工厂方法模式较为复杂,可以看到新增了许多类,实际上降低了可读性。如果每个factory只是简单的new操作,功能单薄,没必要设计成独立的类。所以在此场景下,简单工厂模式更加合适。
简单工厂和工厂方法中,解析器只有一种分类方式,根据文件类型解析(json,xml等)。如果现在还需要根据文件环境来分类,如(开发环境,测试环境)或根据权限分类,如(管理员,普通用户)等。针对这种场景,如果使用工厂方法模式,需要针对每个parser编写工厂类,这样会产生大量的类。
抽象工厂可以让一个工厂负责创建多个不同类型的对象
public interface IParserFactory {
ITypeParser getTypeParser();
IEnvParser getEnvParser();
}
当创建对象逻辑比较复杂时,可以考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。下面两种情况可视为创建逻辑复杂
对于第一种情况,当每个对象的创建逻辑都比较简单的时候,推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类,推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。同理,对于第二种情况,因为单个对象本身的创建逻辑就比较复杂,所以,建议使用工厂方法模式。置于抽象工厂模式,使用场景不多。如果碰到一个工厂需要承担多种不同类型的对象创建时,可以考虑使用。