設計模式(2)——建立型——工廠相關:簡單工廠(Simple factory),工廠方法(Factory method),抽象工廠(Abstract factory)
概要
- 這裡試圖描述23個設計模式中的兩個工廠(Factory)相關的設計模式:工廠方法(Factorymethod),抽象工廠(Abstract factory)。
- 注意點:
- 這兩個都屬於建立型設計模式。
- 由於這兩個設計模式都是建立在簡單工廠(Simple factory)模式之上。我們先介紹簡單工廠模式,值得注意的是簡單工廠模式並不包含在23個設計模式之中。
- 文章介紹順序:簡單工廠,工廠方法,抽象工廠。
簡單工廠
- 定義:由一個工廠物件決定創建出哪一種產品類的例項。
- 型別:建立型,但不屬於GoF23種設計模式。
- 適用場景:工廠類負責建立的物件比較少;客戶端(應用層)只知道工廠類的傳參,對於如何建立物件(邏輯)不關心
- 優點:只需要傳入正確引數,就可以獲取我們所需的物件,而無須知道其具體細節。
- 缺點:工廠類的職責相對過重,增加新的產品需要修改工廠類的判斷邏輯,違背開閉原則。
預設場景:書店現在有三類書籍:IT,Art,Literature。每種書都能被廠家生產。這三種書(三個類)有公共的抽象方法produce。我們現在想要得到這三個類。並且呼叫它們的公共方法。
首先宣告一個抽象類,有一個抽象方法,讓這三個類都繼承這個類。同時宣告一個工廠類,負責接收外部的傳參,並根據外部的引數,返回不同的類。於是可以得到如下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圖:
上圖,重點關注到:第三層的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圖:
從圖中我們可以看到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();
}
}