1. 程式人生 > >設計模式(2)——建立型——工廠相關:簡單工廠(Simple factory),工廠方法(Factory method),抽象工廠(Abstract factory)

設計模式(2)——建立型——工廠相關:簡單工廠(Simple factory),工廠方法(Factory method),抽象工廠(Abstract factory)

概要

  • 這裡試圖描述23個設計模式中的兩個工廠(Factory)相關的設計模式:工廠方法(Factorymethod),抽象工廠(Abstract factory)。
  • 注意點:
  1. 這兩個都屬於建立型設計模式。
  2. 由於這兩個設計模式都是建立在簡單工廠(Simple factory)模式之上。我們先介紹簡單工廠模式,值得注意的是簡單工廠模式並不包含在23個設計模式之中。
  3. 文章介紹順序:簡單工廠,工廠方法,抽象工廠。

簡單工廠

  • 定義:由一個工廠物件決定創建出哪一種產品類的例項。
  • 型別:建立型,但不屬於GoF23種設計模式。
  • 適用場景:工廠類負責建立的物件比較少;客戶端(應用層)只知道工廠類的傳參,對於如何建立物件(邏輯)不關心
  • 優點:只需要傳入正確引數,就可以獲取我們所需的物件,而無須知道其具體細節。
  • 缺點:工廠類的職責相對過重,增加新的產品需要修改工廠類的判斷邏輯,違背開閉原則。

預設場景:書店現在有三類書籍:IT,Art,Literature。每種書都能被廠家生產。這三種書(三個類)有公共的抽象方法produce。我們現在想要得到這三個類。並且呼叫它們的公共方法。

首先宣告一個抽象類,有一個抽象方法,讓這三個類都繼承這個類。同時宣告一個工廠類,負責接收外部的傳參,並根據外部的引數,返回不同的類。於是可以得到如下UML圖:
簡單工廠模式例UML圖

通過圖我們很容易地看懂幾個類之間的關係,下面是這些類的程式碼:

/*公用的抽象類*/
public abstract class Book {
    public abstract void produce();
}
/*********************三種書類**********************/

//IT類
public class ITBook extends Book {                   
    @Override
    public void produce() {
        System.out.println("Produce a IT book"
); } } //Art類 public class ArtBook extends Book { @Override public void produce() { System.out.println("Produce a Art book"); } } //Literature類 public class LiteratureBook extends Book { @Override public void produce() { System.out.println("Produce a Literature book"); } }
/*******************重點:工廠類,負責以上三個類的物件生產****************/
public class BookFactory {
	// 通過外部已知類來生產物件例項
    public static Book produceBook(Class c){
        Book book = null;
        try {
            book = (Book) Class.forName(c.getName()).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return book;
    }
	//通過傳入字串來判斷生產哪種物件
    public static Book produceBook(String bookType){

        if( "IT".equalsIgnoreCase(bookType) )
            return new ITBook();
        else if( "Art".equalsIgnoreCase(bookType) )
            return new ArtBook();
        else if( "Literature".equalsIgnoreCase(bookType) )
            return new LiteratureBook();

        return null;
    }
}
/*******************測試類************************/
public class Test {
    public static void main(String[] args) {
        //通過字串判斷物件型別
        Book book = BookFactory.produceBook("IT");
        book.produce();
        
		//通過Class類來判斷型別
        Book book1 = BookFactory.produceBook( ArtBook.class );
        book1.produce();
        
		//推薦設定factory類內函式為static型別,這樣不必像下面那樣“生產”前必須要先例項化一個工廠類。
        BookFactory factory = new BookFactory();
        Book book2 = BookFactory.produceBook( LiteratureBook.class );
        book2.produce();

    }
}

這裡值得注意的是我們讓Factory類內部的對外函式為static,這樣方便外部直接生成相關類的例項,而不需要先為Factory類例項化。

工廠方法

定義:定義一個建立物件的介面,但讓實現這個介面的類來決定例項化哪個類;工廠方法讓類的例項化推遲到子類中進行。
型別:建立型。
適用場景:
	1. 建立物件需要大量重複的程式碼
	2. 客戶端(應用層)不依賴產品類例項如何被建立,實現等細節。
	3. 一個類通過其子類來指定建立哪個物件。
優點:
	1. 使用者只需要關心所需產品對應的工廠,無須關心建立細節。
	2. 加入新產品符合開閉原則,提高可擴充套件型。
缺點:
	1. 類個數過多,增加複雜性。
	2. 增加系統的抽象性和理解程度。

工廠方法工廠就是前面我們簡單工廠所說的用於建立物件例項的統一工廠類;方法就是我們把工廠類生產各種物件例項的方法抽離出來,專門放到一個抽象類裡面作為並不具體實現的抽象方法,如此,我們需要生產什麼類的物件,我們就定義一個專門生產該類物件的工廠類,該類顯然應該繼承自我們所說的抽象類。

於是我們對於簡單工廠的例子場景,可以得到下面的UML圖:

工廠方法例UML圖

上圖,重點關注到:第三層的ITBookFactory,ArtBookFactory,LiteratureBookFactory分別與底層的ITBook,ArtBook,LituratureBook有<< create >>關係。而這三個Factory類都繼承自底層的BookFactory,使這三個類重寫Book produceBook()這個方法用於生產各自獨特的物件。

下面是用例的程式碼段(Book,ITBook,ArtBook,LiteratureBook類沒有在下面列出,具體檢視上面):

/*抽象工廠類,並不生產物件。而由具體的子類來生產。
	符合開閉原則:如果新增工廠,只需新增一個類並整合自這個類即可
*/
public abstract class BookFactory {
    public abstract Book produceBook();
}
/***********三個具體工廠類,用於生產不同類的物件******************/
// 專門生產ITBook物件
public class ITBookFactory extends BookFactory {
    @Override
    public Book produceBook() {
        return new ITBook();
    }
}
// 專門生產ArtBook物件
public class ArtBookFactory extends BookFactory {
    @Override
    public Book produceBook() {
        return new ArtBook();
    }
}
// 專門生產LiteratureBook物件
public class LiteratureBookFactory extends BookFactory {
    @Override
    public Book produceBook() {
        return new LiteratureBook();
    }
}
/********************測試類************************/
public class Test {
    public static void main(String[] args) {

        BookFactory itFactory = new ITBookFactory();
        BookFactory artFactory = new ArtBookFactory();
        BookFactory literatureFactory = new LiteratureBookFactory();

        Book book1 = itFactory.produceBook();
        book1.produce();

        Book book2 = artFactory.produceBook();
        book2.produce();

        Book book3 = literatureFactory.produceBook();
        book3.produce();
    }
}

程式碼很容易理解。在外部(Test類),我們只需要知道抽象工廠類即可,並將抽象工廠類的引用指向具體的工廠類。 並呼叫相應的方法生產對應工廠即可以生產的物件即可。

抽象工廠

定義:抽象工廠模式提供一個建立一系列相關或相互依賴物件的介面,而無須制定它們具體的類。
型別:建立型
適用場景:
	1. 客戶端(應用層)不依賴產品類例項如何被建立,實現等細節;
	2. 強調一系列相關產品物件(同一產品族)一起使用對建立物件需要大量重複的程式碼
	3. 提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現。
優點:
	1. 具體產品在應用層程式碼隔離,無須關心建立細節
	2. 將一個系列的產品族統一到一起建立
缺點:
	1. 規定了所有可能被建立的產品集合,產品族中擴充套件新的產品困難,需要修改抽象工廠的介面。
	2. 增加了系統的抽象性,理解難度。

引入兩個概念:產品等級結構產品族。下面是他人的講解文章,講解的很好:
產品等級結構與產品族

預設場景:豆瓣讀書場景:現有IT,Art兩個種類的書籍(Book),並且每種書籍都會有相
關的使用者的讀後感或者對這本書的評價,我們統稱為Article。

開始設計。一個工廠能生產同一產品族的產品:Book,Article。 比如說:ITBookFactory生產ITBook,ITArticle;而ArtBookFactory生產ArtBook,ArtArticle。
其中,ITBook與ArtBook屬於同一產品等級結構,ITArticle與ArtArticle亦如此;

圖最容易理解了,下面是類關係UML圖:
抽象工廠例UML圖

從圖中我們可以看到Test類只需要與ITBookFactory和ArtBookFactory接觸並獲取相應的類。而 這兩個工廠類都實現BookFactory介面,這個介面定義了兩個規範:必須能夠生產Book和Article。 從圖中的ITBookFactory和ArtBookFactory工廠類可以看到他們分別都能建立 (<< create >>) Article,Book的物件例項。

上程式碼。

定義同一產品族內,不同產品等級結構中,各不同產品所要實現的與之對應的介面:

/****兩個介面(Interface)Book,Article分別定義兩個實體的公共動作***/

/****為方便起見,介面的邏輯實現僅僅用一個簡單函式來佔位**********/

// Book 介面:定義Book的統一動作邏輯
public interface Book {
    void produce();
}
// Article 介面:定義Article的統一動作邏輯
public interface Article {
    void produce();
}

定義實現介面的實體:

/***具體實現介面的實體類****/

/**IT方面的**/
public class ITBook implements Book {
    @Override
    public void produce() {
        System.out.println("Produce a IT book.");
    }
}
public class ITArticle implements Article {
    @Override
    public void produce() {
        System.out.println("Produce a IT Article.");
    }
}

/**Art方面的**/
public class ArtBook implements Book {
    @Override
    public void produce() {
        System.out.println("Produce a art Book.");
    }
}
public class ArtArticle implements Article {
    @Override
    public void produce() {
        System.out.println("Produce a art Article.");
    }
}
/**定義每個工廠都需要實現的介面;
*每個工廠生產的產品屬於同一個產品族**/
public interface BookFactory {
    Book produceBook();
    Article produceArticle();
}

定義工廠協議(介面),每個工廠都需要遵循這個協議:每個工廠都能夠生產Book,Article,只是不同工廠生產不同型別的Book,Article而已。

/**定義具體的工廠(相當不同的廠商),生產同一產品族(同一廠商)的產品**//**由於這些工廠類(廠商)都遵循同一個協議(實現同一個介面),
*他們都能生產各種相同型別的產品(Book,Article),只是他們生產具體產品的方式不同
*而已(IT工廠生產ITBook,ITArticle,而Art工廠生產ArtBook,ArtArticle。**/

/**屬於ITBook工廠(ITBookFactory)生產的產品族有:ITBook,ITArticle**/
public class ITBookFactory implements BookFactory {
    @Override
    public Book produceBook() {
        return new ITBook();
    }

    @Override
    public Article produceArticle() {
        return new ITArticle();
    }
}

/**屬於ArtBook工廠(ArtBookFactory)生產的產品族有:ArtBook,ArtArticle**/
public class ArtBookFactory implements BookFactory {
    @Override
    public Book produceBook() {
        return new ArtBook();
    }

    @Override
    public Article produceArticle() {
        return new ArtArticle();
    }
}

下面是測試類:

import java.util.Random;
/**
 * 兩種方法進行測試:
 *  1. 普通測試。為了把兩個類都覆蓋到,是用if判斷傳參。
 *  2. 反射機制。關鍵點:Class.forName(String className).newInstace();
 */
public class Test {
    public static void main(String[] args) {

        // 為了把兩個類都測試到,我們新定義個兩種test方法。
        // 根據傳入的引數不同進行不同型別的測試

        // 5次,隨機對一種測試方法進行測試
        for(int i = 0 ; i < 5 ; i ++ ) {

            double rand = new Random().nextDouble();

            System.out.println( "================第"+ (i+1) + "次隨機測試" + rand + "================\n" );

            if ( rand  < 0.5) {
                test("IT");
                test("Art");
            } else {
                test(ITBookFactory.class);
                test(ArtBookFactory.class);
            }

            System.out.println( "================第"+ i + "次隨機測試END=================================\n" );
        }

    }

    private static void test(String type){
        System.out.println("Calling \"void test(String type);\" ");

        BookFactory bookFactory = null;

        if( "IT".equalsIgnoreCase(type) ){
            bookFactory = new ITBookFactory();
        } else if( "Art".equalsIgnoreCase(type) ){
            bookFactory = new ArtBookFactory();
        } else {
            return;
        }

        Book book = bookFactory.produceBook();
        Article article = bookFactory.produceArticle();

        book.produce();
        article.produce();

        System.out.println();

    }

    private static void test(Class c){
        System.out.println("Calling \"void test(Class c);\" ");

        BookFactory bookFactory = null;

        try {
            bookFactory = (BookFactory) Class.forName(c.getName()).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        Book book = bookFactory.produceBook();
        Article article = bookFactory.produceArticle();

        book.produce();
        article.produce();

        System.out.println();
    }

}