创建型模式
目录
一、简单工厂模式在Logback源码以及JDK源码中的应用
1、简单工厂模式
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创建型模式。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承或实现一个父类或接口)的实例。
1.1 简单工厂模式的UML类图
1.2 日常生活中看简单工厂模式
就拿巴扎黑理发这件事来说,服务员就充当了工厂类,她根据巴扎黑想要剪的价位来安排一位合适的理发师。理发师这个称谓就相当于抽象产品,它描述了所有理发师都具备的能力——理发。服务员安排的这位总监就相当于具体产品。
简单工厂模式中,对于某个具体业务而言,client 无需知道由谁来处理这个业务,怎么处理,它只需告诉工厂类这个业务的类型,并调用工厂类提供的静态方法拿到一个具体的产品,然后调用产品的业务方法来完成业务。就像巴扎黑去餐厅吃饭,他只要告诉服务员他想吃什么,然后自然会有一位师傅帮他做好,自己根本就不需要关心这个师傅是谁以及怎么做出这道美味菜肴。这样看来,吃饭这件事也不麻烦。
但是设想一下,巴扎黑很早就起床了,他想自己做菜来犒劳自己,他有充足的时间来准备。首先他得考虑想要吃什么,于是有了下面的思考:
// 伪代码
prepareToEat(food) {
if (food == "鱼丸粗面") {
去买鱼丸和粗面;
放在锅里烧;
...
} else if (food == "大盘鸡") {
去菜场挑只鸡让大妈杀了,剁好装袋;
买洋葱和其他配料;
把鸡肉浸水洗净;
洋葱切好;
...
} else if (food == "红烧肉") {
去菜场挑一块五花肉; // 一定要买带肥肉的,不然烧出来很难吃
切块洗净放料酒浸泡;
锅里放油煸炒肉块;
...
}
...
}
要想吃点好的,脑袋里还得装一本厚厚的菜谱,想要尝尝新菜,还得不断丰富大脑里的菜谱,着实麻烦。有时候脑袋不够使,还会把菜谱记错,倒不如直接去餐厅点菜吃来的省事儿。
1.3 具体例子
1.3.1 场景
实现一个简单的翻译功能,要求:能够将一段中文文本翻译成不同的语言版本
1.3.2 代码
要实现翻译,我们得有一个翻译器。我们先定义一个抽象的翻译器(AbstractTranslator),用于描述所有翻译器都具备的功能——翻译(translate)
/**
* 翻译器抽象类
* 抽象产品,定义产品必须实现的方法
*
* @author piaolingluo
* @date 2017-11-08
*/
public abstract class AbstractTranslator {
@Autowired
protected BaiduConfig baiduConfig;
/**
* 翻译
*
* @param content 待翻译的内容
* @return 翻译的得到的内容
*/
public abstract String translate(String content);
}
再定义一个具体的翻译器——英语翻译器(EnglishTranslator),继承AbstractTranslator,它能将一段中文翻译成英文
/**
* 英语翻译器
* 具体产品
*
* @author piaolingluo
* @date 2017-11-08
*/
@Service
public class EnglishTranslator extends AbstractTranslator {
@Autowired
private Gson gson;
@Override
public String translate(String content) {
TransApi api = new TransApi(baiduConfig.getAppId(), baiduConfig.getSecurityKey());
String response = api.getTransResult(content, "auto", "en");
TransResult result = gson.fromJson(response, TransResult.class);
return result.getTransResult().get(0).getDst();
}
}
再定义一个具体的翻译器——日语翻译器(JapaneseTranslator),继承AbstractTranslator,它能将一段中文翻译成日语
/**
* 日语翻译器
* 具体产品
*
* @author piaolingluo
* @date 2017-11-08
*/
@Service
public class JapaneseTranslator extends AbstractTranslator {
@Autowired
private Gson gson;
@Override
public String translate(String content) {
TransApi api = new TransApi(baiduConfig.getAppId(), baiduConfig.getSecurityKey());
String response = api.getTransResult(content, "auto", "jp");
TransResult result = gson.fromJson(response, TransResult.class);
return result.getTransResult().get(0).getDst();
}
}
接着,我们定义系统能能翻译的语言有哪些,如果后续支持新的语言,可以追加
/**
* 语言枚举
* 定义系统能翻译的语言
*
* @author piaolingluo
* @date 2017-11-08
*/
@Getter
public enum LanguageEnum {
CHINESE("chinese", "中文"),
ENGLISH("english", "英语"),
JAPANESE("japanese", "日语");
private String code;
private String name;
LanguageEnum(String code, String name) {
this.code = code;
this.name = name;
}
public static LanguageEnum valueOfLanguage(String code) {
return Stream.of(values())
.filter(languageEnum -> languageEnum.getCode().equals(code))
.findFirst()
.orElse(null);
}
}
接着,比较重要的一点是为不同的语言指定各自的实例,后续如果支持新的语言,可以在此扩展
/**
* 语言类型与翻译器实例的映射
*
* @author piaolingluo
* @date 2017-11-08
*/
@Getter
public enum TranslatorEnum {
ENGLISH_TRANSLATOR(LanguageEnum.ENGLISH, "englishTranslator"),
JAPANESE_TRANSLATOR(LanguageEnum.JAPANESE, "japaneseTranslator");
/**
* 语言
*/
private LanguageEnum language;
/**
* 具体翻译器处理bean的名字
*/
private String translatorName;
TranslatorEnum(LanguageEnum language, String translatorName) {
this.language = language;
this.translatorName = translatorName;
}
public static TranslatorEnum valueOfTranslator(String translatorName) {
return Stream.of(values())
.filter(translatorEnum -> translatorEnum.getTranslatorName().equals(translatorName))
.findFirst()
.orElse(null);
}
public static LanguageEnum languageOfTranslator(String translatorName) {
Optional<TranslatorEnum> optional = Optional.ofNullable(valueOfTranslator(translatorName));
return optional.isPresent() ? optional.get().getLanguage() : null;
}
}
最最重要的就是编写这个工厂类(Factory),初始化的时候,将所有的翻译器按各自能处理的语言类型存放在 TRANSLATOR_MAP 里,并提供一个静态方法,能够根据不同的语言拿到具体的翻译器
/**
* 工厂
*
* @author piaolingluo
* @date 2017-11-08
*/
@Component
public class Factory {
private static final Map<LanguageEnum, AbstractTranslator> TRANSLATOR_MAP = new HashMap<>();
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
applicationContext.getBeansOfType(AbstractTranslator.class)
.entrySet().stream()
.filter(entry -> null != TranslatorEnum.valueOfTranslator(entry.getKey()))
.forEach(entry -> TRANSLATOR_MAP.put(
TranslatorEnum.languageOfTranslator(entry.getKey()),
entry.getValue()));
}
/**
* 根据语言枚举拿到指定语言的翻译器
*
* @param languageEnum 语言枚举
* @return 指定语言的翻译器
* @throws Exception 当拿不到翻译器时,抛出此异常
*/
public static AbstractTranslator getTranslator(LanguageEnum languageEnum) throws Exception {
AbstractTranslator translator = TRANSLATOR_MAP.get(languageEnum);
if (null == translator) {
throw new Exception("无法翻译成这种语言");
}
return translator;
}
/**
* 根据语言编码拿到指定语言的翻译器
*
* @param languageCode 语言编码
* @return 指定语言的翻译器
* @throws Exception 当拿不到翻译器时,抛出此异常
*/
public static AbstractTranslator getTranslator(String languageCode) throws Exception {
return getTranslator(LanguageEnum.valueOfLanguage(languageCode));
}
}
然后我们写个 TranslateController ,接收指定的语言编码和待翻译的文本,返回翻译后的文本
/**
* 翻译服务
*
* @author piaolingluo
* @date 2017-11-08
*/
@RestController
@RequestMapping("translate")
public class TranslateController {
@GetMapping("{code}")
public ResponseEntity<String> translate(@PathVariable("code") String code,
@RequestParam(value = "content", required = false) String content) {
try {
// 调用工厂类的静态方法,传入语言编码,拿到具体的翻译器实例进行翻译
return ResponseEntity.ok(Factory.getTranslator(code).translate(content));
} catch (Exception e) {
return ResponseEntity.ok(e.getMessage());
}
}
}
最后我们测试一下。
2、简单工厂模式在Logback源码中的应用
在大家经常使用的 Logback 中,也可以看到 LoggerFactory 中有多个重载的方法 getLogger()。
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
3、简单工厂模式在JDK源码中的应用
3.1 Calendar 类为例简单工厂模式
可以说简单工厂模式在 JDK 源码中无处不在,下面以 Calendar 类为例讲解简单工厂模式在 JDK 源码中的应用。Calendar 类的 getInstance() 方法源码如下。
public static Calendar getInstance() {
Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
cal.sharedZone = true;
return cal;
}
进入 createCalendar() 方法中,源码如下:
private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
Calendar cal = null;
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype == null) {
// Calendar type is not specified.
// If the specified locale is a Thai locale,
// returns a BuddhistCalendar instance.
if ("th".equals(aLocale.getLanguage())
&& ("TH".equals(aLocale.getCountry()))) {
cal = new BuddhistCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
} else if (caltype.equals("japanese")) {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else if (caltype.equals("buddhist")) {
cal = new BuddhistCalendar(zone, aLocale);
} else {
// Unsupported calendar type.
// Use Gregorian calendar as a fallback.
cal = new GregorianCalendar(zone, aLocale);
}
return cal;
}
Calendar 的 UML 类图如下:
3.2 JDBC简单工厂模式
当我们需要MySQL数据库的驱动时,我们就传MySQL的参数,用Oracle的就传相应的参数。在写JDBC的时候,JDK来实现的时候,
Class.forName("com.mysql.jdbc.Driver");
通过Class.forName把mysql的驱动加载进来,那如果写ORACLE的驱动呢,这里就变成对应的ORACLE的JDBC的jar包,ORACLE的driver类,然后调用DriverManager的getConnection方法,
@CallerSensitive
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info,Reflection.getCallerClass()));
}
获取对应的数据库连接,JDBC的过程也是非常简单的,
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
通过Class.forName这种方式,直接通过反射拿到对应的Video,只不过MSYQL这里面还是需要通过注册的,
// Walk through the loaded registeredDrivers attempting to locate someone
// who understands the given URL.
for (DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerClass)) {
try {
if(aDriver.driver.acceptsURL(url)) {
// Success!
println("getDriver returning " + aDriver.driver.getClass().getName());
return (aDriver.driver);
}
} catch(SQLException sqe) {
// Drop through and try the next driver.
}
} else {
println(" skipping: " + aDriver.driver.getClass().getName());
}
}
因为这个可以看出来它是一个for循环,在遍历注册的一个驱动,
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
并且它是CopyOnWriteArrayList,里面是DriverInfo,初始化的时候他是一个空的,具体是什么时候完成注册的呢,
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
这个时候就会在registerDriver(new Driver())这个方法里面直接注册这个Driver,那里面的Driver自然就是MySQL的Driver,
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
如果不存在就往里放
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
}
4、简单工厂模式的优缺点
4.1 简单工厂模式的优点
工厂类是整个模式的关键,包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的。明确了各自的职责和权利,有利于整个软件体系结构的优化。
4.2 简单工厂模式的缺点
由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中。它所能创建的类只能是事先考虑到的,如果需要添加新的类,就需要改变工厂类了。
4.3 简单工厂模式的适用环境
(1) 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂;
(2) 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
参考文章:
https://www.jianshu.com/p/72567cc1d63f
http://c.biancheng.net/view/8387.html
https://www.cnblogs.com/thiaoqueen/p/11169924.html