??這兩天代碼中同事用到了java工廠模式,所以度娘搜索了下然后自己理解了之后記錄在此滋戳,希望也可以幫助面對網(wǎng)上各種文檔钻蔑、文章很難理解的像我一樣的新手。
??說到設計模式奸鸯,想必大家都不陌生咪笑,基本上每一個java的初學者都聽說過,說實話娄涩,結合筆者這一年多的寫業(yè)務代碼的經(jīng)驗來說窗怒,平時可能真的用不到這些設計模式或者用到了某個設計模式但是不自知,但是蓄拣,一旦在代碼中用到了某些設計模式的話扬虚,首先會讓自己的代碼擴展性很好,而且別人看你的代碼很吊球恤,很專業(yè)孔轴,哈哈哈,設計模式共有23種碎捺,詳情可以百度,今天只介紹其中三種與工廠模式有關的贷洲。
ps:本文中畫的圖均是示意圖收厨,并非嚴格UML圖,只是為了方便讀者理解优构。
簡單工廠模式
??也叫工廠模式诵叁,這種模式平時在代碼中偶爾自己會用到(雖然極少),也是工廠模式最簡單的一種形態(tài)钦椭,也非常容易理解拧额,簡單來說,就是“當你需要某個對象的時候彪腔,通常你是new出來的侥锦,但是這樣代碼耦合度太高,因此我們通過一個工廠來生產(chǎn)出來這個對象”德挣,這里工廠生產(chǎn)出來的產(chǎn)品就是我們所需要的那個對象恭垦,當然要注意如果是最簡單的一個類,你直接使用new對象跟使用工廠生產(chǎn)出來對象其實區(qū)別是不大的,舉個栗子:
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(); // 通過工廠方法獲取
}
}
??從上面可以看到番挺,在main方法里使用兩種不同的方法創(chuàng)建A對象唠帝,通過工廠模式創(chuàng)建的雖然完成了對A的解耦,但是又新增了對Factory類的依賴玄柏,因此完全沒什么必要這么干襟衰,但是,當類的結構稍微復雜一點的時候粪摘,就會派上用場了瀑晒,下面來舉個栗子:
??以前幾天剛碰到的代碼中的例子來講解吧,在一個java后臺項目中需要集成支付功能赶熟,而支付功能又分為了支付寶支付瑰妄、微信支付、銀聯(lián)支付三種映砖,并且以后還有可能會擴展百度錢包之類的東西间坐,因此就想到了設計一個支付Pay接口,類中需要commitPayData()邑退、pay()兩個抽象方法(為啥是這兩個方法竹宋,我之后會寫關于集成支付的文章,歡迎關注)地技, 然后設計三個類 AliPay蜈七、WxPay、UnionPay來實現(xiàn)Pay接口并分別實現(xiàn)兩個抽象方法莫矗。如此一來飒硅,當需要哪個支付服務時就new哪個類,在這種情況下我們上面說的簡單工廠模式就派上用場了作谚,因為如果直接new的話代碼中勢必要在if/else中寫 new AliPay()三娩、new WxPay()、new UnionPay()妹懒,這樣的話會對這三個類產(chǎn)生耦合雀监,但是簡單工廠模式就會巧妙的多了,我們新建一個PayFactory類眨唬,在這個類里面寫一個靜態(tài)方法getPayObj()会前,代碼如下(省略了具體的支付代碼):
public interface Pay {
Object commitPayData(String str); // 返回值及參數(shù)是隨手寫的,應根據(jù)實際情況來
boolean pay(Object obj); // 返回值及參數(shù)是隨手寫的匾竿,應根據(jù)實際情況來
}
public class AliPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務瓦宜、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務、功能代碼
return false;
}
}
public class WxPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務岭妖、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務歉提、功能代碼
return false;
}
}
public class UnionPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務笛坦、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務、功能代碼
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 {
// 其他支付方式不支持苔巨,可在此記錄錯誤日志
}
return pay;
}
}
public class Main {
public static void main(String[] args) {
Pay pay = PayFactory.getPayObj("ali");
// 可調(diào)用pay的方法完成支付功能
pay.commitPayData("");
pay.pay("");
}
}
??在Main類中想要調(diào)用某個支付服務時只需要調(diào)用工廠類中的工程方法getPayObj()即可版扩,這樣一來的話如果以后要擴展增加其他支付功能的話只需要繼續(xù)實現(xiàn)Pay接口并且在工廠類的工廠方法中多加一個else if即可。
??上面寫的demo這么使用簡單工廠模式是沒問題的侄泽,但是在真正的使用過程中這么寫的缺點就是往往在擴展的時候還得修改工廠方法(加一個else if)礁芦,有時候難免會遺忘或者會帶來修改上的麻煩,因此悼尾,我們還有一種另外一種方法能在擴展功能的時候不用修改工廠類柿扣,最大限度的實現(xiàn)解耦,代碼也有了更好的可擴展性闺魏,這種方法也算是簡單工廠模式的一種延伸使用未状,用起來也很高大上。
??這種方法的思路是:我們需要在不更改工廠方法的前提下析桥,又能動態(tài)的擴展服務司草,因此我們可以在第一次使用的時候做一個生成緩存的操作(這里是第一次使用的時候獲取到緩存,當然也可以在項目啟動時立即生成緩存泡仗,各有優(yōu)劣)埋虹,把我們所有種類的支付服務都放到一個Map<String,Pay>里,這個Map就是我們的緩存了娩怎,然后獲取緩存再調(diào)用的時候獲取到這個Map然后調(diào)用get(key)方法獲取到支付服務對象搔课,其實也就是我們上面寫的工廠方法類改成一個緩存服務類,在該類中沒有工廠方法了截亦,取而代之的是一個獲取到所有支付服務對象然后存到Map中的方法爬泥。整體思路如上所述,難點在于我們怎么獲取到所有的支付服務崩瓤,在這里我們使用到了自定義注解急灭,使用注解來做一個標記功能,標記出來所有的支付功能谷遂,簡單來說就是 帶這個注解的類就是支付服務類,都要實現(xiàn)Pay接口卖鲤,以后再擴展的時候可以也加上這個注解并實現(xiàn)Pay接口即可肾扰。
Pay類不變還是上面的,AliPay蛋逾、WxPay集晚、UnionPay也不變只是加上了注解
public interface Pay {
Object commitPayData(String str); // 返回值及參數(shù)是隨手寫的,應根據(jù)實際情況來
boolean pay(Object obj); // 返回值及參數(shù)是隨手寫的区匣,應根據(jù)實際情況來
}
@PayService(channel = "ali")
public class AliPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務偷拔、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務、功能代碼
return false;
}
@Override
public String toString() {
return "我是aliPay";
}
}
@PayService(channel = "union")
public class UnionPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務莲绰、功能代碼
return false;
}
@Override
public String toString() {
return "我是unionPay";
}
}
@PayService(channel = "wx")
public class WxPay implements Pay {
@Override
public Object commitPayData(String str) {
// 省略業(yè)務欺旧、功能代碼
return null;
}
@Override
public boolean pay(Object obj) {
// 省略業(yè)務、功能代碼
return false;
}
@Override
public String toString() {
return "我是wxPay";
}
}
自定義的注解
/**
* @Target - 注解使用在類蛤签、接口上辞友,
* @Retention - 注解會存在與運行期
*
* @author fengyr
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayService {
// 注解的屬性的設置類似于方法,之所以是String[]是因為這樣的話可以不同的key關鍵字可以對應同一個支付服務
public String[] channel();
}
payFactory從生產(chǎn)工廠變成了設置、獲取緩存
public class PayFactory {
private static PayFactory payFactory = new PayFactory(); // 保證單例
private Map<String, Pay> payCahe;
/**
* 用這個來保證單例:初始化成員變量時已經(jīng)給payFactory賦值了;若類的構造方法特別復雜的話則應使用雙重校驗方法實現(xiàn)單例
*
* @return
*/
public static PayFactory getInstance() {
return payFactory;
}
/**
* 把所有支付服務對象放進去緩存Map震肮,這里的思路是通過包名及父類class對象Pay.class獲取到包下所有我們需要的class對象称龙,
* 得到class對象后可以通過類對象從而得到支付服務對象;要注意防止多線程向map中重復存放元素 ;如果是使用@PostConstruct
* 注解或者其他方法來讓該方法只在項目初始化時執(zhí)行一次的話,則可以不用考慮這里的多線程并發(fā)的問題了
*/
private synchronized void setPayCache() {
if (payCahe != null && !payCahe.isEmpty()) {
return;
}
payCahe = new HashMap<String, Pay>();
// 通過包名及目標類對象獲取到該包下所有的支付服務類對象戳晌,注意下面這步只是獲取到了該包下的Pay以及Pay的子類鲫尊,但是我們只需要Pay的子類,因此還要做過濾
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) {
// 過濾掉沒有注解的
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);
}
}
最后是工具類
public class PackageUtil {
/**
* 根據(jù)傳入的包名及類對象來掃描出來該包下所有的包含子包的滿足目標泛型T的類對象并返回
*
* @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;
// 包名對應的路徑名稱
String packageDirName = packageName.replace('.', '/');
// 保存目標package下的所有目錄
Enumeration<URL> dirs;
try {
// 獲取目標包下所有目錄
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 遍歷所有目錄
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
// 得到一個URL的協(xié)議
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
// System.out.println("file類型的掃描");
// 對字符串進行URL解碼
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 找到該目錄下所有的class<T>
findClassInPackageByFile(packageName, filePath, recursive, clazzs, clazz);
} else if ("jar".equals(protocol)) {
// jar包不出處理沦偎,一般使用時也是掃描自己寫的代碼疫向,也不會掃描到jar包
System.out.println("jar類型的掃描");
}
}
} catch (Exception e) {
e.printStackTrace();
}
return clazzs;
}
/**
* 在package對應的路徑下找到所有的class
*
* @param packageName package名稱
* @param filePath package對應的路徑
* @param recursive 是否查找子package,final是為了在內(nè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()) {
// 目錄不存在或者該目錄不是一個文件夾都不行
return;
}
// 在給定的目錄下找到所有的文件扛施,并且進行條件過濾
File[] dirFiles = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
boolean acceptDir = recursive && file.isDirectory();// 接受dir目錄鸿捧,既接受文件夾中還有一個文件夾
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 {
// 使用類加載器得到對象
Class<?> clazzz = Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className);
if (clazz.isAssignableFrom(clazzz)) {
// 當clazz是clazzz的父類或者兩者相同的時候返回true,既根據(jù)泛型T過濾掉了不需要的類對象,我們需要的只是T或者T的子類
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);
}
}
結果成功啦疙渣,打印如下語句:
我是wxPay
我是unionPay
我是aliPay
工廠方法模式
??上面雖然說了很多工廠模式匙奴,但是其實只是簡單的工廠模式而已,但是根據(jù)筆者淺薄的經(jīng)驗來說是最常用的⊥螅現(xiàn)在要介紹一下簡單工廠模式的進階版:工廠方法模式泼菌。簡單工廠模式是一個工廠根據(jù)不同的條件生產(chǎn)出來不同的產(chǎn)品(所需對象),是一個工廠類對應多個不同的產(chǎn)品類啦租,而工廠方法模式則是有多個工廠類哗伯,也有多個產(chǎn)品類,然后每個工廠類篷角。產(chǎn)品類一一對應焊刹。
??這樣做的好處是在新增一種產(chǎn)品時就不需要像簡單工廠模式中一樣再改動工廠類了,而是新增一個工廠類并實現(xiàn)/繼承接口/抽象類恳蹲,這樣比簡單工廠模式解耦的更加徹底一點了虐块。當然,麻煩的地方在于要新增的東西比較多嘉蕾,可能工作量會大一些贺奠。這種模式有了上面簡單工廠模式的例子后,應該很容易理解的错忱,筆者就不做代碼的展示了儡率,若有疑問歡迎留言~
抽象工廠模式
??最后一種工廠模式則是結構最為復雜的抽象工廠模式挂据,筆者其實也沒有在實際工作中遇到過適用這種模式的例子,因此也不貼代碼拉儿普,跟大家分享下我對這個模式的理解:上面的示意圖如果不好理解的話崎逃,可以嘗試更加具象化的理解,假設一件完整的產(chǎn)品指的是一部手機箕肃,它由A(屏幕)婚脱、B(外殼)、C(cpu)三部分組成勺像,A1障贸、A2、A3分別是電容屏吟宦、led篮洁、oled ;B1殃姓、B2袁波、B3分別是塑料外殼、玻璃外殼蜗侈、金屬外殼 篷牌;C1、C2踏幻、C3分別是arm枷颊、三星、inter 该面;那么上面的三個工廠可以分別生產(chǎn)不同的組合的手機夭苗;這樣做的好處也是很明顯的,想生產(chǎn)不同組合的手機時就新增一個工廠類并實現(xiàn)隔缀、繼承工廠接口题造、抽象類;想新增不同種類的外殼猾瘸、cpu界赔、屏幕時只要新增類并實現(xiàn)相應接口就行了,并且耦合關系也比較輕量牵触。這種模式的缺點可能是結構太復雜了吧淮悼,不過正是由于結構的復雜我們才會采用這種模式的,所以呢荒吏。。渊鞋。绰更。目前我還沒用到過瞧挤,等以后有什么新的想法了再跟大家分享這種模式的優(yōu)劣性吧~
小結
??上面的三種模式的介紹主要是我自己的理解以及自己寫的代碼、畫的圖儡湾,所以難免有些疏漏特恬、錯誤之處,希望大家能指出來徐钠,我們一起進步癌刽!筆者只是一名剛入行的小小的碼畜,歡迎大家多多交流尝丐,可加948184604 QQ群交流显拜,有疑問也可留言!