天天看點

Java設計模式之(二)——工廠模式

Java設計模式之(二)——工廠模式

1、什麼是工廠模式

Define an interface for creating an object,but let subclasses decide which class toinstantiate.Factory Method lets a class defer instantiation to subclasses.

定義一個建立對象的接口,讓其子類自己決定執行個體化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。

說人話:提供建立對象的接口,将建立對象的過程屏蔽,進而達到靈活的目的。

2、工廠模式分類

一般情況下,工廠模式分為三類:

①、簡單工廠模式(Simple Factory)

②、工廠方法模式(Factory Method)

③、抽象工廠模式(Abstract Factory)

這三種模式從上到下逐漸抽象,并且更具一般性。

需要說明的是:GOF 在《設計模式》一書中将工廠模式分為兩類:工廠方法模式(Factory Method)與抽象工廠模式(Abstract Factory),将簡單工廠模式(Simple Factory)看為工廠方法模式的一種特例,兩者歸為一類。

下面我們分别介紹這三種工廠模式。

2.1 簡單工廠(Simple Factory)

比如有這樣一個需求:

根據導入的不同檔案(docx,xlsx,pptx),選擇不同的解析器進行解析。

簡單工廠有三個核心對象:

1.工廠:簡單工廠模式的核心,它負責實作建立所有執行個體的内部邏輯。工廠類的建立産品類的方法可以被外界直接調用,建立所需的産品對象。

2.抽象産品 :簡單工廠模式所建立的所有對象的父類,它負責描述所有執行個體所共有的公共接口。

3.具體産品:是簡單工廠模式的建立目标,所有建立的對象都是充當這個角色的某個具體類的執行個體。

①、抽象解析器

public interface IOfficeParser {
    void parse();
}
           

②、具體解析器(docx,xlsx,pptx)

public class WordParser implements IOfficeParser{
    private String filePath;
    public WordParser(String filePath){
        this.filePath = filePath;
    }
    @Override
    public void parse() {
        System.out.println("解析 docx 檔案");
    }
}
           
public class ExcelParser implements IOfficeParser{
    private String filePath;
    public ExcelParser(String filePath){
        this.filePath = filePath;
    }
    @Override
    public void parse() {
        System.out.println("解析 xlsx 檔案");
    }
}
           
public class PptParser implements IOfficeParser {
    private String filePath;
    public PptParser(String filePath){
        this.filePath = filePath;
    }
    @Override
    public void parse() {
        System.out.println("解析 pptx 檔案");
    }
}
           

③、構造解析器的工廠

public class OfficeParserFactory {

    public static IOfficeParser getParser(String filePath) throws Exception {
        String fileExtension = getFileExtension(filePath);
        IOfficeParser parser = null;
        if("docx".equalsIgnoreCase(fileExtension)){
            parser = new WordParser(filePath);
        }else if("xlsx".equalsIgnoreCase(fileExtension)){
            parser = new ExcelParser(filePath);
        }else if("pptx".equalsIgnoreCase(fileExtension)){
            parser = new PptParser(filePath);
        }else{
            throw new Exception("file is not supported:"+fileExtension);
        }
        return parser;
    }

    private static String getFileExtension(String filePath){
        // 解析檔案名擷取檔案擴充名,比如 文檔.docx,傳回 docx
        String fileExtension = filePath.substring(filePath.lastIndexOf(".")+1);
        return fileExtension;
    }
}
           

④、測試類

public class SimpleFactoryTest {

    public static void main(String[] args) throws Exception {
        String filePath = "文檔.docx";
        IOfficeParser parser = OfficeParserFactory.getParser(filePath);
        parser.parse();

        String filePath1 = "表格.xlsx";
        IOfficeParser parser1 = OfficeParserFactory.getParser(filePath1);
        parser1.parse();
    }
}
           

⑤、總結

這便是簡單工廠,用戶端避免了直接建立解析器的責任,隻需要調用工廠類去解析就行了。

可以從開閉原則(對擴充開放,對修改關閉)來分析簡單工廠模式:當增加一種檔案解析,比如老版本的 doc 格式。這時候隻需要新增一個 parser 類即可,用戶端(了解為測試類,調用端)不用改變,然後在工廠類 OfficeParserFactory 新增一個 else-if 分支即可。

這時候可能有同學會問了,那修改了 OfficeParserFactory 類,不就違反開閉原則了嗎?但其實隻要不是頻繁的添加新的 parser,偶爾修改一下 OfficeParserFactory 類,稍微不符合開閉原則,也是可以接受的。

看上去比較完美,細心的同學可能會問,所有的解析類對象建立都在 OfficeParserFactory 類中,假設某個解析類,比如 doc 建立parser 對象并不是簡單的 new ,還包括一些其它的操作,這時候難道把這些代碼也全部寫到 OfficeParserFactory 中嗎?有沒有更優雅的寫法呢?

有,就是下面要介紹的 工廠模式。

2.2 工廠方法(Factory Method)

為了解決上面的問題,我們可以為工廠類在建立一個工廠,也就是工廠的工廠,用來建立工廠類對象。

①、給每一個具體解析器建立工廠

public class ExcelParserFactory implements IOfficeParserFactory {

    @Override
    public IOfficeParser createParser() {
        // TODO 進行建立對象的一些操作
        return new ExcelParser();
    }
}
           

②、建立解析器的工廠

public class OfficeParserFactory {

    public static IOfficeParser getParser(String filePath) throws Exception {
        String fileExtension = getFileExtension(filePath);
        IOfficeParserFactory parserFactory = OfficeParserFactoryMap.getOfficeParseFactory(fileExtension);
        if(parserFactory == null){
            throw new Exception("file is not supported:"+fileExtension);
        }
        IOfficeParser parser = parserFactory.createParser();
        return parser;
    }

    private static String getFileExtension(String filePath){
        // 解析檔案名擷取檔案擴充名,比如 文檔.docx,傳回 docx
        String fileExtension = filePath.substring(filePath.lastIndexOf(".")+1);
        return fileExtension;
    }
}
           

③、建立解析器工廠的工廠類

public class OfficeParserFactoryMap {
    private static final Map<String, IOfficeParserFactory> parserFactoryCached = new HashMap<>();
    static {
        parserFactoryCached.put("docx",new WordParserFactory());
        parserFactoryCached.put("xlxs",new ExcelParserFactory());
        parserFactoryCached.put("pptx",new PptParserFactory());
    }
    public static IOfficeParserFactory getOfficeParseFactory(String type){
        if(type == null || type.isEmpty()){
            return null;
        }
        return parserFactoryCached.get(type.toLowerCase());
    }


}
           
public class FactoryTest {
    public static void main(String[] args) throws Exception {
        String filePath = "文檔.docx";
        IOfficeParser parser = OfficeParserFactory.getParser(filePath);
        parser.parse();
    }
}
           

在工廠模式中,如果我們要增加新的檔案解析,比如 mdb 格式(office access套件),就隻需要建立新的 parser 類和 parserFactory 類,并且在 OfficeParserFactoryMap 類中将新的 parserFactory 類添加到 map 中即可。代碼的改動非常少,基本上是符合開閉原則的。

但是,我們看到工廠模式新增了很多 factory 類,會增加代碼的複制性,如果每個 factory 類隻是做簡單的 new 操作,則沒必要使用該模式,直接用簡單工廠模式即可。

2.3 抽象工廠(Abstract Factory)

這種模式比較特殊,使用場景不多,大家簡單了解一下就行。

我們知道 doc 和 docx 都是 office word 文檔字尾,類似 xls 和 xlsx 都是 office Excel 表格字尾,還有 ppt 和 pptx。doc/xlx/ppt 都是舊版本 office 檔案字尾,都是二進制組成,解析的時候有共同之處,而 docx/xlsx/pptx 是office新版本檔案字尾,是通過 ooxml 結構組成。相當于一組是老的office,一組是新的office。

如果我們還是用工廠模式來實作的話,那每一種都要編寫一個工廠類,過多的類會難以維護,那怎麼解決呢?

抽象工廠模式就是針對這種特殊的場景誕生,我們可以讓一個工廠複制建立多個不同類型的對象,而不是隻建立一個 parser 對象。

具體代碼實作如下:

public interface IOfficeParserFactory {

    IOfficeParser createParser();

    IOldOfficeParser createOldParser();
}
           
public class ExcelParserFactory implements IOfficeParserFactory {

    @Override
    public IOfficeParser createParser() {
        return new ExcelParser();
    }

    @Override
    public IOldOfficeParser createOldParser() {
        return new DocParser();
    }
}
           

3、簡單工廠和工廠方法差別

簡單工廠:将建立不同對象的邏輯放在一個工廠類中。

工廠方法:将建立不同對象的邏輯放在不同工廠類中,先用一個工廠類的工廠類得到某個工廠,在某這個工廠來建立對象。

這樣講差別就很明顯了,如果建立對象的邏輯比較複雜,要做各種初始化操作,這時候使用工廠方法,能夠将複雜的建立邏輯拆分到多個工廠類中;而建立對象的邏輯很簡單,就沒必要額外建立多個工廠類,直接使用簡單工廠即可。

4、工廠模式的作用

封裝變化:建立邏輯有可能變化,封裝成工廠類之後,建立邏輯的變更對調用者透明。

代碼複用:建立代碼抽離到獨立的工廠類之後可以複用。

隔離複雜性:封裝複雜的建立邏輯,調用者無需了解如何建立對象。

控制複雜度:将建立代碼抽離出來,讓原本的函數或類職責更單一,代碼更簡潔。

看完知道為啥沒事别用工廠模式了吧,因為太好用了,你會愛上它的。

作者:IT可樂

出處:http://www.cnblogs.com/ysocean/

資源:微信搜【IT可樂】關注我,回複 【電子書】有我特别篩選的免費電子書。

本文版權歸作者所有,歡迎轉載,但未經作者同意不能轉載,否則保留追究法律責任的權利。