工廠模式
工廠模式是平時開發過程中最常見的設計模式。工廠模式解決類的實例化問題,它屬于創建型模式。工廠模式也經常會和其他設計模式組合使用。
試想你去麥當勞買一個漢堡。你只需要告訴收銀員要一個xx漢堡。過一會就會有一個此類型的漢堡被制作出來。而你完全不需要知道這個漢堡是怎么被制作出來的。這個例子中你就是客戶端代碼,麥當勞就是工廠,負責生產漢堡。漢堡是接口,而具體的某一種漢堡,比如說香辣雞腿堡,就是實現了漢堡接口的類。
我們繼續通過另外一個例子,深入理解工廠模式?,F在我們給某款音樂軟件開發一個推薦功能。需求是能夠根據用戶選擇的音樂風格,推薦不同風格的歌曲清單。那么你打算怎么實現呢?
1. 音樂推薦器1.0版本
如果之前沒有學習過設計模式,很可能你的實現會是這樣。編寫 RecommendMusicService
類,里面有一個 Recommend方法。根據輸入的風格不同,執行不同的推薦邏輯。代碼如下:
public class RecommendMusicService {
public List<String> recommend(String style) {
List<String> recommendMusicList = new ArrayList<>();
if ("metal".equals(style)) {
recommendMusicList.add("Don't cry");
} else if ("country".equals(style)) {
recommendMusicList.add("Hotel california");
} else if ("grunge".equals(style)) {
recommendMusicList.add("About a girl");
}else {
recommendMusicList.add("My heart will go on");
}
return recommendMusicList;
}
}
是不是覺得 recommed 方法太長了? OK,我們重構下,把每種音樂風格的推薦邏輯封裝到相應的方法中。這樣推薦方法就可以復用了。
public class RecommendMusicService {
public List<String> recommend(String style) {
List<String> recommendMusicList = new ArrayList<>();
if ("metal".equals(style)) {
recommendMetal(recommendMusicList);
} else if ("country".equals(style)) {
recommendCountry(recommendMusicList);
} else if ("grunge".equals(style)) {
recommendGrunge(recommendMusicList);
}else {
recommendPop(recommendMusicList);
}
return recommendMusicList;
}
private void recommendPop(List<String> recommendMusicList) {
recommendMusicList.add("My heart will go on");
recommendMusicList.add("Beat it");
}
private void recommendGrunge(List<String> recommendMusicList) {
recommendMusicList.add("About a girl");
recommendMusicList.add("Smells like teen spirit");
}
private void recommendCountry(List<String> recommendMusicList) {
recommendMusicList.add("Hotel california");
recommendMusicList.add("Take Me Home Country Roads");
}
private void recommendMetal(List<String> recommendMusicList) {
recommendMusicList.add("Don't cry");
recommendMusicList.add("Fade to black");
}
}
這樣是不是很完美了!recommend 方法精簡了很多,而且每種不同的推薦邏輯都被封裝到相應的方法中了。那么,如果再加一種風格推薦怎么辦?這有什么難,recommed 方法中加分支就好啦。然后在 RecommendMusicService
中增加一個對應的推薦方法。
等等,是不是哪里不太對?回想一下設計模式6大原則的開閉原則----對擴展開放,對修改關閉。面對新風格推薦的需求,我們一直都在修改 RecommendMusicService
這個類。以后每次有新風格推薦要添加,都會導致修改 RecommendMusicService
。顯然這是個壞味道。
那么如何做到實現新的風格推薦需求時,滿足開閉原則呢?
2. 音樂推薦器2.0版本
添加新需求時,如何做到不修改,去擴展?是不是想到了單一職責?是的,類的職責越單一,那么它就越穩定。RecommendMusicService
類的職責太多了,負責n種風格的推薦。OK,那么我們第一件事就是要減少 RecommendMusicService
類的職責,把每種不同風格的推薦提取到不同的類當中。
比如MetalMusicRecommendService
、PopMusicRecommendService
、CountryMusicRecommendService
。這些類都可以通過 recommed 方法生成推薦的歌曲清單。而 RecommendMusicService
類只是通過調用不同 MusicRecommendService
的 recommed 方法來實現推薦。代碼如下:
MetalMusicRecommendService 類:
public class MetalMusicRecommendService {
public List<String> recommend(){
List<String> recommendMusicList = new ArrayList<>();
recommendMusicList.add("Don't cry");
recommendMusicList.add("Fade to black");
return recommendMusicList;
}
}
同類型的還有 GrungeMusicRecommendService
、PopMusicRecommendService
、CountryMusicRecommendService
類
現在我們來改造 MusicRecommendService
類:
public class RecommendMusicService {
private MetalMusicRecommendService metalMusicRecommendService = new MetalMusicRecommendService();
private GrungeMusicRecommendService grungeMusicRecommendService = new GrungeMusicRecommendService();
private CountryMusicRecommendService countryMusicRecommendService = new CountryMusicRecommendService();
private PopMusicRecommendService popMusicRecommendService = new PopMusicRecommendService();
public List<String> recommend(String style) {
List<String> recommendMusicList = new ArrayList<>();
if ("metal".equals(style)) {
metalMusicRecommendService.recommend();
} else if ("country".equals(style)) {
countryMusicRecommendService.recommend();
} else if ("grunge".equals(style)) {
grungeMusicRecommendService.recommend();
}else {
popMusicRecommendService.recommend();
}
return recommendMusicList;
}
}
改造后,如果有了新音樂風格推薦的需求,只需要增加相應的 xxxMusicRecommendService
類。然后在 RecommendMusicService
中增加相應分支即可。這樣就做到了開閉原則。那么還有什么違背設計原則的地方嗎?RecommendMusicService
是不是依賴的 xxMusicRecommendService
類太多了?
沒錯,而且這么多類,實際上都是做推薦的事情,且都是通過 recommend 方法提供推薦結果。這完全可以抽象出接口,比如 MusicRecommendInterface
。那么 RecommendMusicService
依賴 MusicRecommendInterface
就可以了。這解決了依賴反轉問題----應該依賴接口,而不是依賴具體實現。
我們又復習了單一職責和依賴反轉原則。不愧是指導設計模式的原則,真的是無處不在。依賴 MusicRecommendInterface
沒問題,但是不同的音樂風格,怎么能實例化 MusicRecommendInterface
的某個具體實現呢?工廠模式于是就應運而生了!
3. 音樂推薦器3.0版本
我們回顧一下文章開頭說到,工廠模式解決的是類的實例化。無論你需要哪種風格的 MusicRecommendService
,只需要告訴工廠,工廠會給你實例化好你需要的具體實現。而工廠能做到這些是基于繼承和多態。
RecommendMusicService
只需要依賴 MusicRecommendInterface
,具體需要哪個MusicRecommendService
的實現,只需要告訴 RecommendServiceFactory
即可。MusicRecommendService
拿到具體的實現后調用它的 recommand 方法,就可以得到相應風格的推薦歌曲清單。
首先我們需要定義所有 MusicRecommendService
要實現的接口,很簡單,只有一個 recommend 方法:
public interface MusicRecommendInterface {
List<String> recommend();
}
我們2.0版本中的 xxxMusicRecommendService
都需要實現此接口,例如:
public class GrungeMusicRecommendService implements MusicRecommendInterface {
public List<String> recommend() {
List<String> recommendMusicList = new ArrayList<>();
recommendMusicList.add("About a girl");
recommendMusicList.add("Smells like teen spirit");
return recommendMusicList;
}
}
不同音樂風格的推薦邏輯在各自實現的 recommend() 方法中。
下面就是工廠模式中的工廠代碼了,其實很簡單,只是根據不同的參數實例化不同的實現并返回。
public class MusicRecommendServiceFactory {
MusicRecommendInterface createMusicRecommend(String style) {
if ("metal".equals(style)) {
return new MetalMusicRecommendService();
} else if ("country".equals(style)) {
return new CountryMusicRecommendService();
} else if ("grunge".equals(style)) {
return new GrungeMusicRecommendService();
} else {
return new PopMusicRecommendService();
}
}
}
我們再來看看 RecommendMusicService
的代碼:
public class RecommendMusicService {
private MusicRecommendServiceFactory recommendMusicServiceFactory = new MusicRecommendServiceFactory();
public List<String> recommend(String style) {
MusicRecommendInterface musicRecommend = recommendMusicServiceFactory.createMusicRecommend(style);
return musicRecommend.recommend();
}
}
是不是簡單多了,已經不再依賴那么多的 MusicRecommendInterface
的實現了。它要做的事情僅僅是通過工廠得到想要的 RecommendMusicService
實現,然后調用它的 recommend() 方法,就可以得到你想要的推薦結果。
類圖如下:
以上三種實現方式總結如下:
4. 小結
本節我們通過音樂推薦器的例子,實踐了如何找到程序中違反設計原則的地方,并通過工廠模式來解決這些問題。使用設計模式可以讓程序更符合程序設計原則,從而寫出更為健壯的代碼。我們應牢記工廠模式解決的是類的實例化問題。這個例子很簡單,不過涉及到的知識點卻很多。有封裝、多態、單一職責和依賴反轉等??梢娨氚殉绦蛟O計好,必須熟練掌握這些基本概念和原則。