在面試中通過工廠模式來證明自己的能力

     在面試中,候選人經常會被問到,你在項目裏用到過哪些設計模式?對此,你可以按本文給出的步驟,系統地通過工廠模式展示自己在設計思想方面的能力。

    1 通過工廠模式屏蔽創建細節

    工廠模式(Factory Method)是用來向使用者屏蔽創建對象的細節。之前我們在講SAX解析XML文件時,已經用到過工廠模式,當時我們是通過如下代碼用SAXParserFacotry這個工廠對象來創建用於解析的parse對象,代碼如下所示。

1	SAXParserFactory factory = SAXParserFactory.newInstance();
2	SAXParser parser = factory.newSAXParser();

    作爲使用者,我們只要能得到parser對象進行後繼的解析動作,至於parser對象是如何創建的,我們不需要,也不應管。如果不用工廠模式,那麼我們還得親自關注如何創建parser對象,比如得考慮創建時傳入的參數,以及是否改用“池”的方式來創建從而提升效率。

    這樣親力親爲的後果是,會讓使用和創建parser對象的代碼耦合度很高,這樣一旦創建parser的方法發生改變,比如日後需要傳入不同的參數,那麼使用parser的代碼也需要對應修改。

    大家別以爲增加修改量沒什麼大不了,如果我們在某個模塊裏修改了代碼,哪怕這個修改點再小,也得經過完整的測試才能把這段代碼放入生產環境,這是需要工作量的。如果我們把“使用”和“創建”對象放在一個模塊裏,那麼“使用”部分的代碼也得測試(雖然沒改),但我們通過了工廠模式分離了兩者,那麼只需要測“創建”模塊,就可以減少工作量了。

    下面我們先來看下工廠模式的實現代碼,比如我們要編寫(創建)Java和數據庫方面的兩本書,先在第1行構建一個Book的基類,在第4和第7行創建兩個子類,而且我們可以把一些通用性的方法(比如“查資料”)放入Book類裏。    

1	class Book {  
2	    public book(){  }  
3	}  
4	public class JavaBook extends Book {  
5	   public JavaBook(){System.out.println("Write Java Book");}  
6	}  
7	public class DBBook extends Book{  
8	   public DBBook(){System.out.println("Write DB Book"); }  
9	}  

    隨後我們通過如第10行的接口來定義創建動作,根據需求,我們可以在第11和17行實現這個接口,在其中分別實現“編寫Java書”和“編寫數據庫書”的代碼。    

10	interface BookFactory { Book createBook(); }  
11	public class JavaFactory implements BookFactory{  
12	      public JavaBook createBook(){  
13	        //省略其它編寫Java書的代碼  
14	        return new JavaBook();  
15	    }    
16	}  
17	public class DBFactory implements BookFactory{  
18	    public DBBook createBook() {  
19	        //省略其它編寫數據庫書的代碼
20	        return new DBBook();
21	    }  
22	}

    在上述代碼裏,我們提供了“創建”的方法,下面我們給出了“調用”的代碼,從第2和第4行的代碼中我們能看到,這裏外部對象可以通過兩種不同的createBook方法分別得到Java和數據庫書。    

1	BookFactory javaFactory = new JavaFactory ();  
2	JavaBook javaBook = javaFactory.createBook();  
3	BookFactory dbFactory = new DBFactory ();  
4	DBBook dbBook = dbFactory.createBook();

2 簡單工廠模式違背了開閉原則

    大家在通過上文,舉例講清楚工廠模式後,可以立即說出這個結論。具體舉例如下。

    在上述的案例中,如果遇到新需求,需要再創建C語言的書,首先可以在Book父類下再創建一個CBook子類,隨後可以在BookFactory接口下再創建一個新的工廠來創建,代碼如下。    

1	public class CBook extends Book { //構建一個新的類
2	   public CBook(){System.out.println("Write C Book");}  
3	}  
4	public class CFactory implements BookFactory{  
5	    public CBook createBook() {  
6	        //省略其它寫C語言書的代碼
7	        return new CBook();
8	    }  
9	}

    對於這個修改需求,我們並沒有修改原有的創建Java和數據庫書籍相關的代碼,而是通過添加新的模塊來實現,這種做法很好地符合了“開閉原則”。

    開閉原則(Open Closed Principle,也叫OCP)和設計模式無關,它是一種設計架構的原則,其核心思想是,系統(或模塊或方法)應當對擴展開放,對修改關閉,比如對於上述案例,遇到擴展了,我們沒有修改現有代碼,從而可以避免測試不相干的模塊。

    我們就用簡單工廠爲例,來看下沒采用開閉原則的後果,比如我們還是要創建Java和數據庫方面的書,那麼是在一個方法里根據參數的不同來返回不同種的類型。    

1	public class BookFactory {
2	   public Book create(String type) {
3	      switch (type) {
4	          case "Java": return new JavaBook(); 
5	          case "DB":return new DBBook();
6	          //要擴展的話,只能加在這裏 
7	          case "C":return new CBook();
8	          default: return null;
9	     }
10	  } 
11	} 

    如果要加新類型的書,只能是新加一個case,一旦有修改,那麼我們得改動第2行的create方法,這樣一來,create方法(乃至BookFactory類)對修改就不關閉了。如果大家對此不理解,可以回顧下工廠模式的案例,當時遇到這個需求,我們是通過添加CFactory類來實現的,原來的BookFactory和DBFactory並沒有改動(它們對修改關閉了)。

    對比一下兩者的差別,由於簡單工廠模式沒遵循開閉原則,那麼一旦添加C語言的書籍,那麼就影響到其它不相干的Java和DB書籍了(這兩部分的case代碼也得隨之測試),這也是爲什麼簡單工廠模式適用場景比較少的原因。

    3 抽象工廠和一般工廠模式的區別

    抽象工廠是對一般工廠模式的擴展,比如我們在寫java和數據庫方面的書籍時,需要添加錄製講解視頻的方法,也就是說,在Java書和數據庫書這兩個產品裏,我們不僅要包含文稿,還得包含視頻。

    具體到生產Java書和數據庫書的這兩個工廠裏,我們要生產多類產品,不僅得包括文稿,還得包括代碼,此時就可以使用抽象模式,示例代碼如下。   

1	class Video { //視頻的基類 
2	    public Video(){  }  
3	}  
4	public class JavaVideo extends Video { 省略定義動作  }  
5	public class DBBook extends Video { 省略定義動作 }  

    在第1行裏,我們創建了視頻的基類,在第4和第5行裏,創建了針對Java和數據庫書視頻的兩個類。    

6	abstract class CreateBook{ //抽象工廠 
7	    public abstract Book createBook();//編寫文稿  
8	    public abstract Book createVideo();//錄製視頻  
9	}  
10	//具體創建java書的工廠  
11	class CreateJavaBook extends CreateBook{  
12	    public JavaBook createBook() {省略編寫文稿的具體動作} 
13	    public JavaVideo createVideo() {省略錄製視頻的具體動作}
14	}  
15	//具體創建數據庫書的工廠 
16	class CreateDBBook extends CreateBook{  
17	    public DBBook createBook() {省略編寫文稿的具體動作} 
18	    public DBVideo createVideo() {省略錄製視頻的具體動作}
19	}

    在第6行裏,我們定義了一個抽象工廠,在其中定義了創建視頻和書籍的兩個方法,在第11和16行,我們通過繼承這個抽象工廠,實現了生產兩個具體Java和數據庫書籍的工廠。

    和一般工廠相比,抽象工廠的頂層類一般是抽象類(也就是抽象工廠名稱的來源),但和一般工廠模式相比,沒有優劣之分,只看哪種模式更能適應需求。比如要在同一類產品(比如書)裏生產多個子產品(比如文稿和視頻),那麼就可以通過抽象工廠模式,而如果需要生產的產品裏只有主部件(比如文稿),而不需要附屬產品(比如視頻),那麼就可以用一般工廠模式。

    4 再進一步分析建造者模式和工廠模式的區別

    建造者模式和工廠模式都是關注於“創建對象”,在面試時,我們一般會問它們的差別。通過工廠模式,我們一般都是創建一個(或一類)產品,而不關心產品的組成部分,建造者模式也是用來創建一個產品,但它不僅創建產品,更專注這個產品的組件和組成過程。

    通過下面的代碼,我們來看下建造者模式的用法,大家可以對比下建造者和工廠模式的差別。    

1	//定義一個待生產的產品,比如帶視頻講解的書
2	public class BookwithVideo {  
3	    //其中包括了稿件和視頻兩個組件
4	    Book PaperBook;
5	    Video Video;
6	}
7	//定義一個抽象的建造者
8	public abstract class Builder {    
9	    public abstract Book createPaperBook();//編寫稿件
10	    public abstract Video createVideo();//錄製視頻
11	}
12	//定義一個具體的建造者,用來創建Java書
13	public class JavaBookProduct extends Builder {    
14	    private BookwithVideo bookWithVideo = new BookwithVideo();
15	    //通過這個方法返回組裝後的書(稿件加視頻)
16	    public BookWithVideo getBook(){return bookWithVideo;}  
17	    //編寫稿件
18	    public void setPaperBook() {  
19	        //創造Java文稿,並賦予javaBook對象
20	        bookWithVideo.book = javaBook;
21	    }  
22	    //錄製視頻
23	    public void setVideo() {  
24	        錄製Java書的視頻,並賦予javaVideo對象
25	        bookWithVideo.video = javaVideo;
26	    }     
27	}  
28	//定義一個具體的數據庫書籍的建造者
29	public class DBBookProduct extends Builder {    
30	    private BookwithVideo bookWithVideo = new BookwithVideo();
31	    //通過這個方法返回組裝後的書(稿件加視頻)
32	    public BookWithVideo getBook(){return bookWithVideo;} 
33	    //紙質書
34	    public void setPaperBook() {  
35	        寫數據庫書的文稿,並賦予dbBook對象
36	        bookWithVideo.book = dbBook;
37	    }  
38	    //錄製視頻
39	    public void setVideo() {  
40	        錄製數據庫書的視頻,並賦予dbVideo對象
41	        bookWithVideo.video = dbVideo;
42	    }     
43	}

    在第8行裏,我們定義了一個抽象的創造者類Builder,在第13和29這兩行裏,我們通過繼承Builder這個創造者類創建了兩個實體創造者,分別用來創造Java和數據庫的書籍。

    在每一個創造者裏,我們通過了setPaperBook方法創造文稿,通過setVideo創建視頻,並把創造好的文稿和視頻分別賦予bookWithVideo對象裏的兩個文稿和視頻組件。

    看到這裏,似乎和工廠模式差不多,由於建造者模式會偏重於組件的創建過程,所以會通過如下的總控類來組裝對象,而工廠模式偏重於“創建產品“的這個結果,而不關注產品中組裝各組件的過程,所以一般不會有總控類。    

44	//總控類
45	public class Director {    
46	   void productBook(Builder builder){
47	         builder.setPaperBook();
48	         builder.setVideo();
49	    } 
50	}  

    在總控類裏的第46行裏,我們定義了用來創建書的productBook方法,請注意這個方法是抽象的builder類,通過下面的代碼,我們能看到如何通過上述定義的總控類和建造者類來動態地創建不同種類的對象。    

1	Director director = new Director();
2	Builder javaBookBuild = new JavaBookProduct();
3	Builder dbBookBuilder = new DBBookProduct();
4	director.productBook(javaBookBuild);
5	director.productBook(dbBookBuilder);

    在第1行裏,我們定義了一個總控類,在第2和第3行裏,我們定義了具體的創建Java和數據庫書籍的建造者對象,在第4和第5行裏,分別傳入了javaBookBuilder和dbBookBuilder這兩個建造者對象,這樣在總控類的productBook方法裏,會根據傳入參數類型的不同,分別建造java和數據庫書籍。

    我們經常通過建造者模式來創建項目裏的業務對象,所以候選人在他們的項目裏一般都會用到這種模式,在面試中也經常聽到候選人用這種模式來舉例,這裏列一種比較好的回答。

    第一,這位候選人用電商平臺的訂單來舉例,首先他創建了一個訂單的基類,在其中包括了商品列表、總價錢、總積分和發貨地址這四個組件。

    第二,通過繼承這個訂單基類,創建了兩類訂單,分別是“一般用戶的訂單”和“VIP客戶的訂單”,它們的算總價和算總積分的業務邏輯是不同的。

    第三,定義了一個抽象的建造者對象,在其中定義了諸如“統計商品”和“算總價”等的方法。

    第四,通過繼承抽象的建造者,定義了兩個具體的建造者,分別用來建造“一般訂單”和“VIP訂單”,在每個具體的建造者對象裏,創建商品列表、總價錢、總積分和發貨地址,並把它們組裝成一個訂單。

    第五,也是關鍵點,需要創建一個總控類(這也是建造者模式的核心,也是和工廠模式的差別點),在其中提供一個productOrder(Builder builder)方法,它的參數是抽象的建造者。

    至此構造了建造者模式的全部代碼,在需要創建訂單時,則可以通過productOrder(VIP訂單的建造者對象)的調用方式,通過傳入的具體的建造者對象(不是抽象的建造者對象)來完成建造。

    上述的敘述是給大家做個參考,其實根據實際的項目需求敘述建造者模式並不困難,一般來說,很多面試官會多問句,建造者模式和工廠模式有什麼差別?這在前文裏也說過了,大家可以通過項目需求詳細說明。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章