??這兩天代碼中同事用到了java工廠模式,所以度娘搜索了下然后自己理解了之后記錄在此,希望也可以幫助面對(duì)網(wǎng)上各種文檔、文章很難理解的像我一樣的新手。
??說(shuō)到設(shè)計(jì)模式,想必大家都不陌生,基本上每一個(gè)java的初學(xué)者都聽(tīng)說(shuō)過(guò),說(shuō)實(shí)話,結(jié)合筆者這一年多的寫(xiě)業(yè)務(wù)代碼的經(jīng)驗(yàn)來(lái)說(shuō),平時(shí)可能真的用不到這些設(shè)計(jì)模式或者用到了某個(gè)設(shè)計(jì)模式但是不自知,但是,一旦在代碼中用到了某些設(shè)計(jì)模式的話,首先會(huì)讓自己的代碼擴(kuò)展性很好,而且別人看你的代碼很吊,很專(zhuān)業(yè),哈哈哈,設(shè)計(jì)模式共有23種,詳情可以百度,今天只介紹其中三種與工廠模式有關(guān)的。
ps:本文中畫(huà)的圖均是示意圖,并非嚴(yán)格UML圖,只是為了方便讀者理解。
簡(jiǎn)單工廠模式

??也叫工廠模式,這種模式平時(shí)在代碼中偶爾自己會(huì)用到(雖然極少),也是工廠模式最簡(jiǎn)單的一種形態(tài),也非常容易理解,簡(jiǎn)單來(lái)說(shuō),就是“當(dāng)你需要某個(gè)對(duì)象的時(shí)候,通常你是new出來(lái)的,但是這樣代碼耦合度太高,因此我們通過(guò)一個(gè)工廠來(lái)生產(chǎn)出來(lái)這個(gè)對(duì)象”,這里工廠生產(chǎn)出來(lái)的產(chǎn)品就是我們所需要的那個(gè)對(duì)象,當(dāng)然要注意如果是最簡(jiǎn)單的一個(gè)類(lèi),你直接使用new對(duì)象跟使用工廠生產(chǎn)出來(lái)對(duì)象其實(shí)區(qū)別是不大的,舉個(gè)栗子:
public class A {
public A() {
}
}
public class Factory {
public static A AFactory() {
return new A();
}
}
public class Main {
public static void main(String[] args) {
A a1 = new A(); // 直接new
A a2 = Factory.AFactory(); // 通過(guò)工廠方法獲取
}
}
??從上面可以看到,在main方法里使用兩種不同的方法創(chuàng)建A對(duì)象,通過(guò)工廠模式創(chuàng)建的雖然完成了對(duì)A的解耦,但是又新增了對(duì)Factory類(lèi)的依賴(lài),因此完全沒(méi)什么必要這么干,但是,當(dāng)類(lèi)的結(jié)構(gòu)稍微復(fù)雜一點(diǎn)的時(shí)候,就會(huì)派上用場(chǎng)了,下面來(lái)舉個(gè)栗子:
??以前幾天剛碰到的代碼中的例子來(lái)講解吧,在一個(gè)java后臺(tái)項(xiàng)目中需要集成支付功能,而支付功能又分為了支付寶支付、微信支付、銀聯(lián)支付三種,并且以后還有可能會(huì)擴(kuò)展百度錢(qián)包之類(lèi)的東西,因此就想到了設(shè)計(jì)一個(gè)支付Pay接口,類(lèi)中需要commitPayData()、pay()兩個(gè)抽象方法(為啥是這兩個(gè)方法,我之后會(huì)寫(xiě)關(guān)于集成支付的文章,歡迎關(guān)注), 然后設(shè)計(jì)三個(gè)類(lèi) AliPay、WxPay、UnionPay來(lái)實(shí)現(xiàn)Pay接口并分別實(shí)現(xiàn)兩個(gè)抽象方法。如此一來(lái),當(dāng)需要哪個(gè)支付服務(wù)時(shí)就new哪個(gè)類(lèi),在這種情況下我們上面說(shuō)的簡(jiǎn)單工廠模式就派上用場(chǎng)了,因?yàn)槿绻苯觧ew的話代碼中勢(shì)必要在if/else中寫(xiě) new AliPay()、new WxPay()、new UnionPay(),這樣的話會(huì)對(duì)這三個(gè)類(lèi)產(chǎn)生耦合,但是簡(jiǎn)單工廠模式就會(huì)巧妙的多了,我們新建一個(gè)PayFactory類(lèi),在這個(gè)類(lèi)里面寫(xiě)一個(gè)靜態(tài)方法getPayObj(),代碼如下(省略了具體的支付代碼):
public interface Pay {
Object commitPayData(String str); // 返回值及參數(shù)是隨手寫(xiě)的,應(yīng)根據(jù)實(shí)際情況來(lái)
boolean pay(Object obj); // 返回值及參數(shù)是隨手寫(xiě)的,應(yīng)根據(jù)實(shí)際情況來(lái)
}
public class AliPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務(wù)、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務(wù)、功能代碼
return false;
}
}
public class WxPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務(wù)、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務(wù)、功能代碼
return false;
}
}
public class UnionPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務(wù)、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務(wù)、功能代碼
return false;
}
}
public class PayFactory {
public static final String ALI_PAY = "ali";
public static final String WX_PAY = "wx";
public static final String UNION_PAY = "union";
/**
*
* @param payMethod 支付方式
* @return
*/
public static Pay getPayObj(String payMethod) {
Pay pay = null;
if (payMethod.equals(ALI_PAY)) {
pay = new AliPay();
} else if (payMethod.equals(WX_PAY)) {
pay = new WxPay();
} else if (payMethod.equals(UNION_PAY)) {
pay = new UnionPay();
} else {
// 其他支付方式不支持,可在此記錄錯(cuò)誤日志
}
return pay;
}
}
public class Main {
public static void main(String[] args) {
Pay pay = PayFactory.getPayObj("ali");
// 可調(diào)用pay的方法完成支付功能
pay.commitPayData("");
pay.pay("");
}
}
??在Main類(lèi)中想要調(diào)用某個(gè)支付服務(wù)時(shí)只需要調(diào)用工廠類(lèi)中的工程方法getPayObj()即可,這樣一來(lái)的話如果以后要擴(kuò)展增加其他支付功能的話只需要繼續(xù)實(shí)現(xiàn)Pay接口并且在工廠類(lèi)的工廠方法中多加一個(gè)else if即可。
??上面寫(xiě)的demo這么使用簡(jiǎn)單工廠模式是沒(méi)問(wèn)題的,但是在真正的使用過(guò)程中這么寫(xiě)的缺點(diǎn)就是往往在擴(kuò)展的時(shí)候還得修改工廠方法(加一個(gè)else if),有時(shí)候難免會(huì)遺忘或者會(huì)帶來(lái)修改上的麻煩,因此,我們還有一種另外一種方法能在擴(kuò)展功能的時(shí)候不用修改工廠類(lèi),最大限度的實(shí)現(xiàn)解耦,代碼也有了更好的可擴(kuò)展性,這種方法也算是簡(jiǎn)單工廠模式的一種延伸使用,用起來(lái)也很高大上。
??這種方法的思路是:我們需要在不更改工廠方法的前提下,又能動(dòng)態(tài)的擴(kuò)展服務(wù),因此我們可以在第一次使用的時(shí)候做一個(gè)生成緩存的操作(這里是第一次使用的時(shí)候獲取到緩存,當(dāng)然也可以在項(xiàng)目啟動(dòng)時(shí)立即生成緩存,各有優(yōu)劣),把我們所有種類(lèi)的支付服務(wù)都放到一個(gè)Map<String,Pay>里,這個(gè)Map就是我們的緩存了,然后獲取緩存再調(diào)用的時(shí)候獲取到這個(gè)Map然后調(diào)用get(key)方法獲取到支付服務(wù)對(duì)象,其實(shí)也就是我們上面寫(xiě)的工廠方法類(lèi)改成一個(gè)緩存服務(wù)類(lèi),在該類(lèi)中沒(méi)有工廠方法了,取而代之的是一個(gè)獲取到所有支付服務(wù)對(duì)象然后存到Map中的方法。整體思路如上所述,難點(diǎn)在于我們?cè)趺传@取到所有的支付服務(wù),在這里我們使用到了自定義注解,使用注解來(lái)做一個(gè)標(biāo)記功能,標(biāo)記出來(lái)所有的支付功能,簡(jiǎn)單來(lái)說(shuō)就是 帶這個(gè)注解的類(lèi)就是支付服務(wù)類(lèi),都要實(shí)現(xiàn)Pay接口,以后再擴(kuò)展的時(shí)候可以也加上這個(gè)注解并實(shí)現(xiàn)Pay接口即可。
Pay類(lèi)不變還是上面的,AliPay、WxPay、UnionPay也不變只是加上了注解
public interface Pay {
Object commitPayData(String str); // 返回值及參數(shù)是隨手寫(xiě)的,應(yīng)根據(jù)實(shí)際情況來(lái)
boolean pay(Object obj); // 返回值及參數(shù)是隨手寫(xiě)的,應(yīng)根據(jù)實(shí)際情況來(lái)
}
@PayService(channel = "ali")
public class AliPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務(wù)、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務(wù)、功能代碼
return false;
}
@Override
public String toString() {
return "我是aliPay";
}
}
@PayService(channel = "union")
public class UnionPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務(wù)、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務(wù)、功能代碼
return false;
}
@Override
public String toString() {
return "我是unionPay";
}
}
@PayService(channel = "wx")
public class WxPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務(wù)、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務(wù)、功能代碼
return false;
}
@Override
public String toString() {
return "我是wxPay";
}
}
自定義的注解
/**
* @Target - 注解使用在類(lèi)、接口上,
* @Retention - 注解會(huì)存在與運(yùn)行期
*
* @author fengyr
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayService {
// 注解的屬性的設(shè)置類(lèi)似于方法,之所以是String[]是因?yàn)檫@樣的話可以不同的key關(guān)鍵字可以對(duì)應(yīng)同一個(gè)支付服務(wù)
public String[] channel();
}
payFactory從生產(chǎn)工廠變成了設(shè)置、獲取緩存
public class PayFactory {
private static PayFactory payFactory = new PayFactory(); // 保證單例
private Map<String, Pay> payCahe;
/**
* 用這個(gè)來(lái)保證單例:初始化成員變量時(shí)已經(jīng)給payFactory賦值了;若類(lèi)的構(gòu)造方法特別復(fù)雜的話則應(yīng)使用雙重校驗(yàn)方法實(shí)現(xiàn)單例
*
* @return
*/
public static PayFactory getInstance() {
return payFactory;
}
/**
* 把所有支付服務(wù)對(duì)象放進(jìn)去緩存Map,這里的思路是通過(guò)包名及父類(lèi)class對(duì)象Pay.class獲取到包下所有我們需要的class對(duì)象,
* 得到class對(duì)象后可以通過(guò)類(lèi)對(duì)象從而得到支付服務(wù)對(duì)象;要注意防止多線程向map中重復(fù)存放元素 ;如果是使用@PostConstruct
* 注解或者其他方法來(lái)讓該方法只在項(xiàng)目初始化時(shí)執(zhí)行一次的話,則可以不用考慮這里的多線程并發(fā)的問(wèn)題了
*/
private synchronized void setPayCache() {
if (payCahe != null && !payCahe.isEmpty()) {
return;
}
payCahe = new HashMap<String, Pay>();
// 通過(guò)包名及目標(biāo)類(lèi)對(duì)象獲取到該包下所有的支付服務(wù)類(lèi)對(duì)象,注意下面這步只是獲取到了該包下的Pay以及Pay的子類(lèi),但是我們只需要Pay的子類(lèi),因此還要做過(guò)濾
Set<Class<Pay>> clazzs = PackageUtil.getPackageClasses("cn.com.payTest.payService.impl", Pay.class);
for (Class<Pay> clazz : clazzs) {
PayService payService = clazz.getAnnotation(PayService.class);
if (payService == null) {
// 過(guò)濾掉沒(méi)有注解的
continue;
}
String[] payNames = payService.channel();
for (String name : payNames) {
try {
// 放到緩存去
payCahe.put(name, clazz.getConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public Pay getPay(String channel) {
if (payCahe == null || payCahe.isEmpty()) {
this.setPayCache();
}
return payCahe.get(channel);
}
}
最后是工具類(lèi)
public class PackageUtil {
/**
* 根據(jù)傳入的包名及類(lèi)對(duì)象來(lái)掃描出來(lái)該包下所有的包含子包的滿(mǎn)足目標(biāo)泛型T的類(lèi)對(duì)象并返回
*
* @param pack
* @param clazz
* @return
*/
public static <T> Set<Class<T>> getPackageClasses(String pack, Class<T> clazz) {
Set<Class<T>> clazzs = new HashSet<Class<T>>();
// 是否循環(huán)搜索子包
boolean recursive = true;
// 包名字
String packageName = pack;
// 包名對(duì)應(yīng)的路徑名稱(chēng)
String packageDirName = packageName.replace('.', '/');
// 保存目標(biāo)package下的所有目錄
Enumeration<URL> dirs;
try {
// 獲取目標(biāo)包下所有目錄
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 遍歷所有目錄
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
// 得到一個(gè)URL的協(xié)議
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
// System.out.println("file類(lèi)型的掃描");
// 對(duì)字符串進(jìn)行URL解碼
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 找到該目錄下所有的class<T>
findClassInPackageByFile(packageName, filePath, recursive, clazzs, clazz);
} else if ("jar".equals(protocol)) {
// jar包不出處理,一般使用時(shí)也是掃描自己寫(xiě)的代碼,也不會(huì)掃描到j(luò)ar包
System.out.println("jar類(lèi)型的掃描");
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clazzs;
}
/**
* 在package對(duì)應(yīng)的路徑下找到所有的class
*
* @param packageName package名稱(chēng)
* @param filePath package對(duì)應(yīng)的路徑
* @param recursive 是否查找子package,final是為了在內(nèi)部類(lèi)中調(diào)用
* @param clazzs 找到class以后存放的集合
*/
public static <T> void findClassInPackageByFile(String packageName, String filePath, final boolean recursive, Set<Class<T>> clazzs, Class<T> clazz) {
File dir = new File(filePath);
if (!dir.exists() || !dir.isDirectory()) {
// 目錄不存在或者該目錄不是一個(gè)文件夾都不行
return;
}
// 在給定的目錄下找到所有的文件,并且進(jìn)行條件過(guò)濾
File[] dirFiles = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
boolean acceptDir = recursive && file.isDirectory();// 接受dir目錄,既接受文件夾中還有一個(gè)文件夾
boolean acceptClass = file.getName().endsWith("class");// 接受class文件
return acceptDir || acceptClass;
}
});
for (File file : dirFiles) {
if (file.isDirectory()) {
// 如果是文件夾則繼續(xù)調(diào)用該方法:遞歸思想
findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, clazzs, clazz);
} else {
// 去掉class文件的.class后綴
String className = file.getName().substring(0, file.getName().length() - 6);
try {
// 使用類(lèi)加載器得到對(duì)象
Class<?> clazzz = Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className);
if (clazz.isAssignableFrom(clazzz)) {
// 當(dāng)clazz是clazzz的父類(lèi)或者兩者相同的時(shí)候返回true,既根據(jù)泛型T過(guò)濾掉了不需要的類(lèi)對(duì)象,我們需要的只是T或者T的子類(lèi)
clazzs.add((Class<T>) clazzz);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
在main方法中跑一下試試看,看能否獲取到:
public class RunClass {
public static void main(String[] args) {
payAction("wx");
payAction("union");
payAction("ali");
}
public static void payAction(String channel) {
Pay pay = PayFactory.getInstance().getPay(channel);
System.out.println(pay);
}
}
結(jié)果成功啦,打印如下語(yǔ)句:
我是wxPay
我是unionPay
我是aliPay
工廠方法模式

??上面雖然說(shuō)了很多工廠模式,但是其實(shí)只是簡(jiǎn)單的工廠模式而已,但是根據(jù)筆者淺薄的經(jīng)驗(yàn)來(lái)說(shuō)是最常用的?,F(xiàn)在要介紹一下簡(jiǎn)單工廠模式的進(jìn)階版:工廠方法模式。簡(jiǎn)單工廠模式是一個(gè)工廠根據(jù)不同的條件生產(chǎn)出來(lái)不同的產(chǎn)品(所需對(duì)象),是一個(gè)工廠類(lèi)對(duì)應(yīng)多個(gè)不同的產(chǎn)品類(lèi),而工廠方法模式則是有多個(gè)工廠類(lèi),也有多個(gè)產(chǎn)品類(lèi),然后每個(gè)工廠類(lèi)。產(chǎn)品類(lèi)一一對(duì)應(yīng)。
??這樣做的好處是在新增一種產(chǎn)品時(shí)就不需要像簡(jiǎn)單工廠模式中一樣再改動(dòng)工廠類(lèi)了,而是新增一個(gè)工廠類(lèi)并實(shí)現(xiàn)/繼承接口/抽象類(lèi),這樣比簡(jiǎn)單工廠模式解耦的更加徹底一點(diǎn)了。當(dāng)然,麻煩的地方在于要新增的東西比較多,可能工作量會(huì)大一些。這種模式有了上面簡(jiǎn)單工廠模式的例子后,應(yīng)該很容易理解的,筆者就不做代碼的展示了,若有疑問(wèn)歡迎留言~
抽象工廠模式

??最后一種工廠模式則是結(jié)構(gòu)最為復(fù)雜的抽象工廠模式,筆者其實(shí)也沒(méi)有在實(shí)際工作中遇到過(guò)適用這種模式的例子,因此也不貼代碼拉,跟大家分享下我對(duì)這個(gè)模式的理解:上面的示意圖如果不好理解的話,可以嘗試更加具象化的理解,假設(shè)一件完整的產(chǎn)品指的是一部手機(jī),它由A(屏幕)、B(外殼)、C(cpu)三部分組成,A1、A2、A3分別是電容屏、led、oled ;B1、B2、B3分別是塑料外殼、玻璃外殼、金屬外殼 ;C1、C2、C3分別是arm、三星、inter ;那么上面的三個(gè)工廠可以分別生產(chǎn)不同的組合的手機(jī);這樣做的好處也是很明顯的,想生產(chǎn)不同組合的手機(jī)時(shí)就新增一個(gè)工廠類(lèi)并實(shí)現(xiàn)、繼承工廠接口、抽象類(lèi);想新增不同種類(lèi)的外殼、cpu、屏幕時(shí)只要新增類(lèi)并實(shí)現(xiàn)相應(yīng)接口就行了,并且耦合關(guān)系也比較輕量。這種模式的缺點(diǎn)可能是結(jié)構(gòu)太復(fù)雜了吧,不過(guò)正是由于結(jié)構(gòu)的復(fù)雜我們才會(huì)采用這種模式的,所以呢。。。。目前我還沒(méi)用到過(guò),等以后有什么新的想法了再跟大家分享這種模式的優(yōu)劣性吧~
小結(jié)
??上面的三種模式的介紹主要是我自己的理解以及自己寫(xiě)的代碼、畫(huà)的圖,所以難免有些疏漏、錯(cuò)誤之處,希望大家能指出來(lái),我們一起進(jìn)步!筆者只是一名剛?cè)胄械男⌒〉拇a畜,歡迎大家多多交流,可加948184604 QQ群交流,有疑問(wèn)也可留言!