工廠模式
工廠模式是平時(shí)開發(fā)過程中最常見的設(shè)計(jì)模式。工廠模式解決類的實(shí)例化問題,它屬于創(chuàng)建型模式。工廠模式也經(jīng)常會(huì)和其他設(shè)計(jì)模式組合使用。
試想你去麥當(dāng)勞買一個(gè)漢堡。你只需要告訴收銀員要一個(gè)xx漢堡。過一會(huì)就會(huì)有一個(gè)此類型的漢堡被制作出來。而你完全不需要知道這個(gè)漢堡是怎么被制作出來的。這個(gè)例子中你就是客戶端代碼,麥當(dāng)勞就是工廠,負(fù)責(zé)生產(chǎn)漢堡。漢堡是接口,而具體的某一種漢堡,比如說香辣雞腿堡,就是實(shí)現(xiàn)了漢堡接口的類。
我們繼續(xù)通過另外一個(gè)例子,深入理解工廠模式?,F(xiàn)在我們給某款音樂軟件開發(fā)一個(gè)推薦功能。需求是能夠根據(jù)用戶選擇的音樂風(fēng)格,推薦不同風(fēng)格的歌曲清單。那么你打算怎么實(shí)現(xiàn)呢?
1. 音樂推薦器1.0版本
如果之前沒有學(xué)習(xí)過設(shè)計(jì)模式,很可能你的實(shí)現(xiàn)會(huì)是這樣。編寫 RecommendMusicService
類,里面有一個(gè) Recommend方法。根據(jù)輸入的風(fēng)格不同,執(zhí)行不同的推薦邏輯。代碼如下:
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,我們重構(gòu)下,把每種音樂風(fēng)格的推薦邏輯封裝到相應(yīng)的方法中。這樣推薦方法就可以復(fù)用了。
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 方法精簡了很多,而且每種不同的推薦邏輯都被封裝到相應(yīng)的方法中了。那么,如果再加一種風(fēng)格推薦怎么辦?這有什么難,recommed 方法中加分支就好啦。然后在 RecommendMusicService
中增加一個(gè)對應(yīng)的推薦方法。
等等,是不是哪里不太對?回想一下設(shè)計(jì)模式6大原則的開閉原則----對擴(kuò)展開放,對修改關(guān)閉。面對新風(fēng)格推薦的需求,我們一直都在修改 RecommendMusicService
這個(gè)類。以后每次有新風(fēng)格推薦要添加,都會(huì)導(dǎo)致修改 RecommendMusicService
。顯然這是個(gè)壞味道。
那么如何做到實(shí)現(xiàn)新的風(fēng)格推薦需求時(shí),滿足開閉原則呢?
2. 音樂推薦器2.0版本
添加新需求時(shí),如何做到不修改,去擴(kuò)展?是不是想到了單一職責(zé)?是的,類的職責(zé)越單一,那么它就越穩(wěn)定。RecommendMusicService
類的職責(zé)太多了,負(fù)責(zé)n種風(fēng)格的推薦。OK,那么我們第一件事就是要減少 RecommendMusicService
類的職責(zé),把每種不同風(fēng)格的推薦提取到不同的類當(dāng)中。
比如MetalMusicRecommendService
、PopMusicRecommendService
、CountryMusicRecommendService
。這些類都可以通過 recommed 方法生成推薦的歌曲清單。而 RecommendMusicService
類只是通過調(diào)用不同 MusicRecommendService
的 recommed 方法來實(shí)現(xiàn)推薦。代碼如下:
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
類
現(xiàn)在我們來改造 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;
}
}
改造后,如果有了新音樂風(fēng)格推薦的需求,只需要增加相應(yīng)的 xxxMusicRecommendService
類。然后在 RecommendMusicService
中增加相應(yīng)分支即可。這樣就做到了開閉原則。那么還有什么違背設(shè)計(jì)原則的地方嗎?RecommendMusicService
是不是依賴的 xxMusicRecommendService
類太多了?
沒錯(cuò),而且這么多類,實(shí)際上都是做推薦的事情,且都是通過 recommend 方法提供推薦結(jié)果。這完全可以抽象出接口,比如 MusicRecommendInterface
。那么 RecommendMusicService
依賴 MusicRecommendInterface
就可以了。這解決了依賴反轉(zhuǎn)問題----應(yīng)該依賴接口,而不是依賴具體實(shí)現(xiàn)。
我們又復(fù)習(xí)了單一職責(zé)和依賴反轉(zhuǎn)原則。不愧是指導(dǎo)設(shè)計(jì)模式的原則,真的是無處不在。依賴 MusicRecommendInterface
沒問題,但是不同的音樂風(fēng)格,怎么能實(shí)例化 MusicRecommendInterface
的某個(gè)具體實(shí)現(xiàn)呢?工廠模式于是就應(yīng)運(yùn)而生了!
3. 音樂推薦器3.0版本
我們回顧一下文章開頭說到,工廠模式解決的是類的實(shí)例化。無論你需要哪種風(fēng)格的 MusicRecommendService
,只需要告訴工廠,工廠會(huì)給你實(shí)例化好你需要的具體實(shí)現(xiàn)。而工廠能做到這些是基于繼承和多態(tài)。
RecommendMusicService
只需要依賴 MusicRecommendInterface
,具體需要哪個(gè)MusicRecommendService
的實(shí)現(xiàn),只需要告訴 RecommendServiceFactory
即可。MusicRecommendService
拿到具體的實(shí)現(xiàn)后調(diào)用它的 recommand 方法,就可以得到相應(yīng)風(fēng)格的推薦歌曲清單。
首先我們需要定義所有 MusicRecommendService
要實(shí)現(xiàn)的接口,很簡單,只有一個(gè) recommend 方法:
public interface MusicRecommendInterface {
List<String> recommend();
}
我們2.0版本中的 xxxMusicRecommendService
都需要實(shí)現(xiàn)此接口,例如:
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;
}
}
不同音樂風(fēng)格的推薦邏輯在各自實(shí)現(xiàn)的 recommend() 方法中。
下面就是工廠模式中的工廠代碼了,其實(shí)很簡單,只是根據(jù)不同的參數(shù)實(shí)例化不同的實(shí)現(xiàn)并返回。
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();
}
}
是不是簡單多了,已經(jīng)不再依賴那么多的 MusicRecommendInterface
的實(shí)現(xiàn)了。它要做的事情僅僅是通過工廠得到想要的 RecommendMusicService
實(shí)現(xiàn),然后調(diào)用它的 recommend() 方法,就可以得到你想要的推薦結(jié)果。
類圖如下:
以上三種實(shí)現(xiàn)方式總結(jié)如下:
4. 小結(jié)
本節(jié)我們通過音樂推薦器的例子,實(shí)踐了如何找到程序中違反設(shè)計(jì)原則的地方,并通過工廠模式來解決這些問題。使用設(shè)計(jì)模式可以讓程序更符合程序設(shè)計(jì)原則,從而寫出更為健壯的代碼。我們應(yīng)牢記工廠模式解決的是類的實(shí)例化問題。這個(gè)例子很簡單,不過涉及到的知識(shí)點(diǎn)卻很多。有封裝、多態(tài)、單一職責(zé)和依賴反轉(zhuǎn)等??梢娨氚殉绦蛟O(shè)計(jì)好,必須熟練掌握這些基本概念和原則。