java工廠模式詳解

??這兩天代碼中同事用到了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)單工廠模式

簡(jiǎn)單工廠模式.jpg

??也叫工廠模式,這種模式平時(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

工廠方法模式

工廠方法模式.jpg

??上面雖然說(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)歡迎留言~

抽象工廠模式

抽象工廠模式.jpg

??最后一種工廠模式則是結(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)也可留言!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容