java工廠模式詳解

??這兩天代碼中同事用到了java工廠模式,所以度娘搜索了下然后自己理解了之后記錄在此滋戳,希望也可以幫助面對網(wǎng)上各種文檔钻蔑、文章很難理解的像我一樣的新手。

??說到設計模式奸鸯,想必大家都不陌生咪笑,基本上每一個java的初學者都聽說過,說實話娄涩,結合筆者這一年多的寫業(yè)務代碼的經(jīng)驗來說窗怒,平時可能真的用不到這些設計模式或者用到了某個設計模式但是不自知,但是蓄拣,一旦在代碼中用到了某些設計模式的話扬虚,首先會讓自己的代碼擴展性很好,而且別人看你的代碼很吊球恤,很專業(yè)孔轴,哈哈哈,設計模式共有23種碎捺,詳情可以百度,今天只介紹其中三種與工廠模式有關的贷洲。
ps:本文中畫的圖均是示意圖收厨,并非嚴格UML圖,只是為了方便讀者理解优构。

簡單工廠模式

簡單工廠模式.jpg

??也叫工廠模式诵叁,這種模式平時在代碼中偶爾自己會用到(雖然極少),也是工廠模式最簡單的一種形態(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

工廠方法模式

工廠方法模式.jpg

??上面雖然說了很多工廠模式匙奴,但是其實只是簡單的工廠模式而已,但是根據(jù)筆者淺薄的經(jīng)驗來說是最常用的⊥螅現(xiàn)在要介紹一下簡單工廠模式的進階版:工廠方法模式泼菌。簡單工廠模式是一個工廠根據(jù)不同的條件生產(chǎn)出來不同的產(chǎn)品(所需對象),是一個工廠類對應多個不同的產(chǎn)品類啦租,而工廠方法模式則是有多個工廠類哗伯,也有多個產(chǎn)品類,然后每個工廠類篷角。產(chǎn)品類一一對應焊刹。
??這樣做的好處是在新增一種產(chǎn)品時就不需要像簡單工廠模式中一樣再改動工廠類了,而是新增一個工廠類并實現(xiàn)/繼承接口/抽象類恳蹲,這樣比簡單工廠模式解耦的更加徹底一點了虐块。當然,麻煩的地方在于要新增的東西比較多嘉蕾,可能工作量會大一些贺奠。這種模式有了上面簡單工廠模式的例子后,應該很容易理解的错忱,筆者就不做代碼的展示了儡率,若有疑問歡迎留言~

抽象工廠模式

抽象工廠模式.jpg

??最后一種工廠模式則是結構最為復雜的抽象工廠模式挂据,筆者其實也沒有在實際工作中遇到過適用這種模式的例子,因此也不貼代碼拉儿普,跟大家分享下我對這個模式的理解:上面的示意圖如果不好理解的話崎逃,可以嘗試更加具象化的理解,假設一件完整的產(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群交流显拜,有疑問也可留言!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末爹袁,一起剝皮案震驚了整個濱河市远荠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌失息,老刑警劉巖譬淳,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盹兢,居然都是意外死亡邻梆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門绎秒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浦妄,“玉大人,你說我怎么就攤上這事替裆⌒1纾” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵辆童,是天一觀的道長宜咒。 經(jīng)常有香客問我,道長把鉴,這世上最難降的妖魔是什么故黑? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮庭砍,結果婚禮上场晶,老公的妹妹穿的比我還像新娘。我一直安慰自己怠缸,他們只是感情好诗轻,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揭北,像睡著了一般扳炬。 火紅的嫁衣襯著肌膚如雪吏颖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天恨樟,我揣著相機與錄音半醉,去河邊找鬼。 笑死劝术,一個胖子當著我的面吹牛缩多,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播养晋,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼衬吆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匙握?” 一聲冷哼從身側響起咆槽,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎圈纺,沒想到半個月后秦忿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蛾娶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年灯谣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛔琅。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胎许,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罗售,到底是詐尸還是另有隱情辜窑,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布寨躁,位于F島的核電站穆碎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏职恳。R本人自食惡果不足惜愧捕,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一牍戚、第九天 我趴在偏房一處隱蔽的房頂上張望眯搭。 院中可真熱鬧错负,春花似錦、人聲如沸操禀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斤寂,卻和暖如春蔑水,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扬蕊。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留丹擎,地道東北人尾抑。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像蒂培,于是被迫代替她去往敵國和親再愈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 目錄1.概念2.角色3.工廠執(zhí)行的具體流程簡單工廠模式(靜態(tài)工廠方法)工廠模式抽象工廠模式4.分類5.總結6.學習...
    在挖坑的猿閱讀 743評論 0 4
  • 設計模式概述 在學習面向?qū)ο笃叽笤O計原則時需要注意以下幾點:a) 高內(nèi)聚护戳、低耦合和單一職能的“沖突”實際上翎冲,這兩者...
    彥幀閱讀 3,741評論 0 14
  • github地址就不用說了,用來做什么的也不說了媳荒,簡書上有很多抗悍,本文只記錄一下用來保存本地聊天IM的記錄的一些使用...
    wingsrao閱讀 2,134評論 1 5
  • 你馬上26歲生日了鱼炒,仍然還單著衔沼。今年你已經(jīng)沒有生日愿望了,因為你知道你最想要什么昔瞧?而又最有可能得不到什么指蚁,...
    我要變更好呀閱讀 625評論 0 1
  • 經(jīng)群眾舉報,濱河南路高新科技新城段路面存在砂石拉運車拋灑遺漏自晰、揚塵等問題凝化,嚴重影響當?shù)鼐用袢粘I睿瑝m土飛揚更是造...
    瘋為白楊閱讀 337評論 0 0