简单工厂模式也叫静态工厂模式,不属于 GOF 23 种设计模式。这个模式其实很简单,但是我觉得从这个模式中学到的最重要的是要逐渐锻炼出“设计模式”的思维,即不要仅仅局限在一个类上面。这时候就要想起那道经典的面试题了:
面向对象的三个基本特征?
封装、继承、多态
简单工厂模式就是将创建对象(产品)实例的过程交由工厂类去实现,即将 new 的过程进行了封装,工厂类根据传入的参数返回不同的产品实例,而这些产品实例有统一的父类。就好比现在有“某些场景下尽可能的少用 if else” 的说法,并不是说真的可以完全规避掉 if else,而是对外屏蔽了具体的判断细节。
简单工厂模式主要有三种角色:
- 简单工厂类:负责对外屏蔽实例化细节,根据不同的参数返回不同的对象实例
 - 抽象产品:简单工厂生产的所有对象的父类
 - 具体产品
 
简单示例:
抽象产品接口:
public interface Reader {
    String reader(String path);
}具体实现类:
public class DefaultReader implements Reader{
    @Override
    public String reader(String path) {
        System.out.println("DefaultReader read -> " + path);
        return null;
    }
}public class ExcelReader implements Reader {
    @Override
    public String reader(String path) {
        System.out.println("ExcelReader read -> " + path);
        return null;
    }
}public class TxtReader implements Reader {
    @Override
    public String reader(String path) {
        System.out.println("TxtReader read ->" + path);
        return null;
    }
}工厂类:
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReaderFactory {
public static Reader createReader(String fileType) {
switch (fileType.toUpperCase()) {
case "TXT":
return new TxtReader();
case "EXCEL":
return new ExcelReader();
default:
return new DefaultReader();
}
}
}
测试:
public class Test {
    public static void main(String[] args) {
        Reader txtReader = ReaderFactory.createReader("txt");
        Reader excelReader = ReaderFactory.createReader("excel");
        Reader defaultReader = ReaderFactory.createReader("");
        excelReader.reader("a.xlsx");
        txtReader.reader("b.txt");
        defaultReader.reader("c.avi");
    }
}输出:
ExcelReader read -> a.xlsx
TxtReader read ->b.txt
DefaultReader read -> c.avi
从简单工厂这个名称就可以看出该模式适用于比较简单的场景,而且从某种程度来说违反了高内聚原则,那么什么是“比较简单”呢,如产品实例化逻辑简单,产品类型不是很多。
在 JDK 中的应用
简单工厂模式在 JDK 中的使用还是比较多的,如 Calendar、Locale 等。先看 Calendar:

这里 Calendar 本身又是工厂又是产品的抽象类,java.util.Calendar#getInstance(java.util.TimeZone, java.util.Locale) 方法会根据不同的参数返回不同的 Calendar 实例:

Calendar 内部屏蔽了 Calendar 实例化的具体细节,所以我们使用会非常的方便。
再看 Locale 类,在 java.util.Calendar#getInstance() 方法中没有传递 TimeZone 和 Locale:
public static Calendar getInstance()
{
Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);
return createCalendar(defaultTimeZone(aLocale), aLocale);
}
可以看到 java.util.Locale#getDefault(java.util.Locale.Category) 会根据不同的 Locale.Category 返回不同的 Locale 实例:
public static Locale getDefault(Locale.Category category) {
        // do not synchronize this method - see 4071298
        switch (category) {
        case DISPLAY:
            if (defaultDisplayLocale == null) {
                synchronized(Locale.class) {
                    if (defaultDisplayLocale == null) {
                        defaultDisplayLocale = initDefault(category);
                    }
                }
            }
            return defaultDisplayLocale;
        case FORMAT:
            if (defaultFormatLocale == null) {
                synchronized(Locale.class) {
                    if (defaultFormatLocale == null) {
                        defaultFormatLocale = initDefault(category);
                    }
                }
            }
            return defaultFormatLocale;
        default:
            assert false: "Unknown Category";
        }
        return getDefault();
    }不同的枚举会对应不同的系统参数:
FORMAT("user.language.format",
               "user.script.format",
               "user.country.format",
               "user.variant.format",
               "user.extensions.format");本质也是根据系统参数获取 Locale 的:
private static Locale initDefault(Locale.Category category) {
        Properties props = GetPropertyAction.privilegedGetProperties();
        return getInstance(
            props.getProperty(category.languageKey,
                    defaultLocale.getLanguage()),
            props.getProperty(category.scriptKey,
                    defaultLocale.getScript()),
            props.getProperty(category.countryKey,
                    defaultLocale.getCountry()),
            props.getProperty(category.variantKey,
                    defaultLocale.getVariant()),
            getDefaultExtensions(props.getProperty(category.extensionsKey, ""))
                .orElse(defaultLocale.getLocaleExtensions()));
    }以 Dubbo SPI 为例
在 《Java 设计模式》书中有这么一句话:
通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
这不就是 SPI 么。根据 Dubbo 的 SPI 规范:
- 需要在resources目录下配置META-INF/dubbo或者META-INF/dubbo/internal或者META-INF/services,并基于SPI接口去创建一个文件;
 - 文件名称和接口全类名保持一致,文件内容和Java的SPI有差异,内容是KEY对应Value;
 
根据规范,创建 resources/MRTA-INF/dubbo 文件夹,并创建以 Protocol 全类名为文件名的文件:

文件内容为:

实现 Protocol 接口:

测试代码:

com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionLoader 方法就是根据不同的 Class 返回相应的 loader:
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if(!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }而 EXTENSION_LOADERS 是一个 Map,内容是在 ExtensionLoader 的私有构造方法中进行初始化:
private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }其他相关细节可参看:Dubbo的SPI机制(二)(Dubbo优化后的SPI实现。
References
- 《Java 设计模式》
 
 

                










