创建型模式
一、简单工厂模式在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) 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论