0
点赞
收藏
分享

微信扫一扫

设计模式之简单工厂模式


简单工厂模式也叫静态工厂模式,不属于 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​​ 实例:

设计模式之简单工厂模式_简单工厂模式_02

​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 规范:

  1. 需要在resources目录下配置META-INF/dubbo或者META-INF/dubbo/internal或者META-INF/services,并基于SPI接口去创建一个文件;
  2. 文件名称和接口全类名保持一致,文件内容和Java的SPI有差异,内容是KEY对应Value;

根据规范,创建 ​​resources/MRTA-INF/dubbo​​​ 文件夹,并创建以 ​​Protocol​​ 全类名为文件名的文件:

设计模式之简单工厂模式_java_03

文件内容为:

设计模式之简单工厂模式_简单工厂模式_04

实现 ​​Protocol​​ 接口:

设计模式之简单工厂模式_工厂类_05

测试代码:

设计模式之简单工厂模式_java_06

​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 设计模式》


​​​​​

设计模式之简单工厂模式_java_07


举报

相关推荐

0 条评论