简单工厂模式也叫静态工厂模式,不属于 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 设计模式》