工厂模式实现及使用场景--java

文章目录

  • 概述
  • 不使用工厂模式
  • 简单工厂模式
  • 工厂方法
  • 抽象工厂
  • 使用场景
  • 工厂模式好处

概述

工厂模式为一种创建型设计模式,主要目的在于将对象的创建与使用进行解耦,使用者并不用关心对象具体的创建逻辑。一般分为简单工厂、工厂方法、抽象工厂。

下述介绍均以文件解析为例子。根据文件类型的不同,我们需要不同的文件解析器,如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";
}

这种方法存在两个问题:

  • 获取解析器对象的代码耦合在业务代码中,试想如果存在很多地方需要使用解析器,则存在大量获取解析器对象的重复代码
  • 获取解析器对象存在大量if-else,代码扩展性不够好,如果现在新增一个properties文件解析器,则需要定位到具体获取解析器对象的代码新增一个if-else。

简单工厂模式

为了解耦对象的创建和使用,让代码逻辑更加清晰,将对象的创建剥离到一个独立的类中,这个类只负责对象的创建。这就是简单工厂模式

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();
}

使用场景

当创建对象逻辑比较复杂时,可以考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。下面两种情况可视为创建逻辑复杂

  • 类似规则配置解析的例子,代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况。考虑使用工厂模式,将这一大坨 if-else创建对象的代码抽离出来,放到工厂类中。
  • 尽管不需要根据不同的类型创建不同的对象,但是,单个对象本身的创建过程比较复杂,比如要组合其他类对象,做各种初始化操作。在这种情况下,也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。

对于第一种情况,当每个对象的创建逻辑都比较简单的时候,推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类,推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。同理,对于第二种情况,因为单个对象本身的创建逻辑就比较复杂,所以,建议使用工厂方法模式。置于抽象工厂模式,使用场景不多。如果碰到一个工厂需要承担多种不同类型的对象创建时,可以考虑使用。

工厂模式好处

  • 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
  • 代码复用: 创建代码抽离到独立的工厂类之后可以复用。
  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象
  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

你可能感兴趣的:(设计模式,java,设计模式)