實(shí)戰(zhàn)!工作中常用到哪些設(shè)計(jì)模式
前言
大家好,我是撿田螺的小男孩。
平時(shí)我們寫(xiě)代碼呢,多數(shù)情況都是流水線(xiàn)式寫(xiě)代碼,基本就可以實(shí)現(xiàn)業(yè)務(wù)邏輯了。如何在寫(xiě)代碼中找到樂(lè)趣呢,我覺(jué)得,最好的方式就是:使用設(shè)計(jì)模式優(yōu)化自己的業(yè)務(wù)代碼。今天跟大家聊聊日常工作中,我都使用過(guò)哪些設(shè)計(jì)模式。
工作中常用到哪些設(shè)計(jì)模式
1.策略模式
1.1 業(yè)務(wù)場(chǎng)景
假設(shè)有這樣的業(yè)務(wù)場(chǎng)景,大數(shù)據(jù)系統(tǒng)把文件推送過(guò)來(lái),根據(jù)不同類(lèi)型采取不同的解析方式。多數(shù)的小伙伴就會(huì)寫(xiě)出以下的代碼:
- if(type=="A"){
- //按照A格式解析
- }else if(type=="B"){
- //按B格式解析
- }else{
- //按照默認(rèn)格式解析
- }
這個(gè)代碼可能會(huì)存在哪些問(wèn)題呢?
- 如果分支變多,這里的代碼就會(huì)變得臃腫,難以維護(hù),可讀性低。
- 如果你需要接入一種新的解析類(lèi)型,那只能在原有代碼上修改。
說(shuō)得專(zhuān)業(yè)一點(diǎn)的話(huà),就是以上代碼,違背了面向?qū)ο缶幊痰拈_(kāi)閉原則以及單一原則。
- 開(kāi)閉原則(對(duì)于擴(kuò)展是開(kāi)放的,但是對(duì)于修改是封閉的):增加或者刪除某個(gè)邏輯,都需要修改到原來(lái)代碼
- 單一原則(規(guī)定一個(gè)類(lèi)應(yīng)該只有一個(gè)發(fā)生變化的原因):修改任何類(lèi)型的分支邏輯代碼,都需要改動(dòng)當(dāng)前類(lèi)的代碼。
如果你的代碼就是醬紫:有多個(gè)if...else等條件分支,并且每個(gè)條件分支,可以封裝起來(lái)替換的,我們就可以使用策略模式來(lái)優(yōu)化。
1.2 策略模式定義
策略模式定義了算法族,分別封裝起來(lái),讓它們之間可以相互替換,此模式讓算法的變化獨(dú)立于使用算法的的客戶(hù)。這個(gè)策略模式的定義是不是有點(diǎn)抽象呢?那我們來(lái)看點(diǎn)通俗易懂的比喻:
假設(shè)你跟不同性格類(lèi)型的小姐姐約會(huì),要用不同的策略,有的請(qǐng)電影比較好,有的則去吃小吃效果不錯(cuò),有的去逛街買(mǎi)買(mǎi)買(mǎi)最合適。當(dāng)然,目的都是為了得到小姐姐的芳心,請(qǐng)看電影、吃小吃、逛街就是不同的策略。
策略模式針對(duì)一組算法,將每一個(gè)算法封裝到具有共同接口的獨(dú)立的類(lèi)中,從而使得它們可以相互替換。
1.3 策略模式使用
策略模式怎么使用呢?醬紫實(shí)現(xiàn)的:
一個(gè)接口或者抽象類(lèi),里面兩個(gè)方法(一個(gè)方法匹配類(lèi)型,一個(gè)可替換的邏輯實(shí)現(xiàn)方法)
不同策略的差異化實(shí)現(xiàn)(就是說(shuō),不同策略的實(shí)現(xiàn)類(lèi))
使用策略模式
1.3.1 一個(gè)接口,兩個(gè)方法
- public interface IFileStrategy {
- //屬于哪種文件解析類(lèi)型
- FileTypeResolveEnum gainFileType();
- //封裝的公用算法(具體的解析方法)
- void resolve(Object objectparam);
- }
1.3.2 不同策略的差異化實(shí)現(xiàn)
A 類(lèi)型策略具體實(shí)現(xiàn)
- @Component
- public class AFileResolve implements IFileStrategy {
- @Override
- public FileTypeResolveEnum gainFileType() {
- return FileTypeResolveEnum.File_A_RESOLVE;
- }
- @Override
- public void resolve(Object objectparam) {
- logger.info("A 類(lèi)型解析文件,參數(shù):{}",objectparam);
- //A類(lèi)型解析具體邏輯
- }
- }
B 類(lèi)型策略具體實(shí)現(xiàn)
- @Component
- public class BFileResolve implements IFileStrategy {
- @Override
- public FileTypeResolveEnum gainFileType() {
- return FileTypeResolveEnum.File_B_RESOLVE;
- }
- @Override
- public void resolve(Object objectparam) {
- logger.info("B 類(lèi)型解析文件,參數(shù):{}",objectparam);
- //B類(lèi)型解析具體邏輯
- }
- }
默認(rèn)類(lèi)型策略具體實(shí)現(xiàn)
- @Component
- public class DefaultFileResolve implements IFileStrategy {
- @Override
- public FileTypeResolveEnum gainFileType() {
- return FileTypeResolveEnum.File_DEFAULT_RESOLVE;
- }
- @Override
- public void resolve(Object objectparam) {
- logger.info("默認(rèn)類(lèi)型解析文件,參數(shù):{}",objectparam);
- //默認(rèn)類(lèi)型解析具體邏輯
- }
- }
1.3.3 使用策略模式
如何使用呢?我們借助spring的生命周期,使用ApplicationContextAware接口,把對(duì)用的策略,初始化到map里面。然后對(duì)外提供resolveFile方法即可。
- /**
- * @author 公眾號(hào):撿田螺的小男孩
- */
- @Component
- public class StrategyUseService implements ApplicationContextAware{
- private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
- public void resolveFile(FileTypeResolveEnum fileTypeResolveEnum, Object objectParam) {
- IFileStrategy iFileStrategy = iFileStrategyMap.get(fileTypeResolveEnum);
- if (iFileStrategy != null) {
- iFileStrategy.resolve(objectParam);
- }
- }
- //把不同策略放到map
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
- tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
- }
- }
2. 責(zé)任鏈模式
2.1 業(yè)務(wù)場(chǎng)景
我們來(lái)看一個(gè)常見(jiàn)的業(yè)務(wù)場(chǎng)景,下訂單。下訂單接口,基本的邏輯,一般有參數(shù)非空校驗(yàn)、安全校驗(yàn)、黑名單校驗(yàn)、規(guī)則攔截等等。很多伙伴會(huì)使用異常來(lái)實(shí)現(xiàn):
- public class Order {
- public void checkNullParam(Object param){
- //參數(shù)非空校驗(yàn)
- throw new RuntimeException();
- }
- public void checkSecurity(){
- //安全校驗(yàn)
- throw new RuntimeException();
- }
- public void checkBackList(){
- //黑名單校驗(yàn)
- throw new RuntimeException();
- }
- public void checkRule(){
- //規(guī)則攔截
- throw new RuntimeException();
- }
- public static void main(String[] args) {
- Order order= new Order();
- try{
- order.checkNullParam();
- order.checkSecurity ();
- order.checkBackList();
- order2.checkRule();
- System.out.println("order success");
- }catch (RuntimeException e){
- System.out.println("order fail");
- }
- }
- }
這段代碼使用了異常來(lái)做邏輯條件判斷,如果后續(xù)邏輯越來(lái)越復(fù)雜的話(huà),會(huì)出現(xiàn)一些問(wèn)題:如異常只能返回異常信息,不能返回更多的字段,這時(shí)候需要自定義異常類(lèi)。
并且,阿里開(kāi)發(fā)手冊(cè)規(guī)定:禁止用異常做邏輯判斷。
【強(qiáng)制】 異常不要用來(lái)做流程控制,條件控制。說(shuō)明:異常設(shè)計(jì)的初衷是解決程序運(yùn)行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。
如何優(yōu)化這段代碼呢?可以考慮責(zé)任鏈模式
2.2 責(zé)任鏈模式定義
當(dāng)你想要讓一個(gè)以上的對(duì)象有機(jī)會(huì)能夠處理某個(gè)請(qǐng)求的時(shí)候,就使用責(zé)任鏈模式。
責(zé)任鏈模式為請(qǐng)求創(chuàng)建了一個(gè)接收者對(duì)象的鏈。執(zhí)行鏈上有多個(gè)對(duì)象節(jié)點(diǎn),每個(gè)對(duì)象節(jié)點(diǎn)都有機(jī)會(huì)(條件匹配)處理請(qǐng)求事務(wù),如果某個(gè)對(duì)象節(jié)點(diǎn)處理完了,就可以根據(jù)實(shí)際業(yè)務(wù)需求傳遞給下一個(gè)節(jié)點(diǎn)繼續(xù)處理或者返回處理完畢。這種模式給予請(qǐng)求的類(lèi)型,對(duì)請(qǐng)求的發(fā)送者和接收者進(jìn)行解耦。
責(zé)任鏈模式實(shí)際上是一種處理請(qǐng)求的模式,它讓多個(gè)處理器(對(duì)象節(jié)點(diǎn))都有機(jī)會(huì)處理該請(qǐng)求,直到其中某個(gè)處理成功為止。責(zé)任鏈模式把多個(gè)處理器串成鏈,然后讓請(qǐng)求在鏈上傳遞:
責(zé)任鏈模式
打個(gè)比喻:
假設(shè)你晚上去上選修課,為了可以走點(diǎn)走,坐到了最后一排。來(lái)到教室,發(fā)現(xiàn)前面坐了好幾個(gè)漂亮的小姐姐,于是你找張紙條,寫(xiě)上:“你好, 可以做我的女朋友嗎?如果不愿意請(qǐng)向前傳”。紙條就一個(gè)接一個(gè)的傳上去了,后來(lái)傳到第一排的那個(gè)妹子手上,她把紙條交給老師,聽(tīng)說(shuō)老師40多歲未婚...
2.3 責(zé)任鏈模式使用
責(zé)任鏈模式怎么使用呢?
- 一個(gè)接口或者抽象類(lèi)
- 每個(gè)對(duì)象差異化處理
- 對(duì)象鏈(數(shù)組)初始化(連起來(lái))
2.3.1 一個(gè)接口或者抽象類(lèi)
這個(gè)接口或者抽象類(lèi),需要:
- 有一個(gè)指向責(zé)任下一個(gè)對(duì)象的屬性
- 一個(gè)設(shè)置下一個(gè)對(duì)象的set方法
- 給子類(lèi)對(duì)象差異化實(shí)現(xiàn)的方法(如以下代碼的doFilter方法)
- /**
- * 關(guān)注公眾號(hào):撿田螺的小男孩
- */
- public abstract class AbstractHandler {
- //責(zé)任鏈中的下一個(gè)對(duì)象
- private AbstractHandler nextHandler;
- /**
- * 責(zé)任鏈的下一個(gè)對(duì)象
- */
- public void setNextHandler(AbstractHandler nextHandler){
- this.nextHandler = nextHandler;
- }
- /**
- * 具體參數(shù)攔截邏輯,給子類(lèi)去實(shí)現(xiàn)
- */
- public void filter(Request request, Response response) {
- doFilter(request, response);
- if (getNextHandler() != null) {
- getNextHandler().filter(request, response);
- }
- }
- public AbstractHandler getNextHandler() {
- return nextHandler;
- }
- abstract void doFilter(Request filterRequest, Response response);
- }
2.3.2 每個(gè)對(duì)象差異化處理
責(zé)任鏈上,每個(gè)對(duì)象的差異化處理,如本小節(jié)的業(yè)務(wù)場(chǎng)景,就有參數(shù)校驗(yàn)對(duì)象、安全校驗(yàn)對(duì)象、黑名單校驗(yàn)對(duì)象、規(guī)則攔截對(duì)象
- /**
- * 參數(shù)校驗(yàn)對(duì)象
- **/
- @Component
- @Order(1) //順序排第1,最先校驗(yàn)
- public class CheckParamFilterObject extends AbstractHandler {
- @Override
- public void doFilter(Request request, Response response) {
- System.out.println("非空參數(shù)檢查");
- }
- }
- /**
- * 安全校驗(yàn)對(duì)象
- */
- @Component
- @Order(2) //校驗(yàn)順序排第2
- public class CheckSecurityFilterObject extends AbstractHandler {
- @Override
- public void doFilter(Request request, Response response) {
- //invoke Security check
- System.out.println("安全調(diào)用校驗(yàn)");
- }
- }
- /**
- * 黑名單校驗(yàn)對(duì)象
- */
- @Component
- @Order(3) //校驗(yàn)順序排第3
- public class CheckBlackFilterObject extends AbstractHandler {
- @Override
- public void doFilter(Request request, Response response) {
- //invoke black list check
- System.out.println("校驗(yàn)黑名單");
- }
- }
- /**
- * 規(guī)則攔截對(duì)象
- */
- @Component
- @Order(4) //校驗(yàn)順序排第4
- public class CheckRuleFilterObject extends AbstractHandler {
- @Override
- public void doFilter(Request request, Response response) {
- //check rule
- System.out.println("check rule");
- }
- }
2.3.3 對(duì)象鏈連起來(lái)(初始化)&& 使用
- @Component("ChainPatternDemo")
- public class ChainPatternDemo {
- //自動(dòng)注入各個(gè)責(zé)任鏈的對(duì)象
- @Autowired
- private List<AbstractHandler> abstractHandleList;
- private AbstractHandler abstractHandler;
- //spring注入后自動(dòng)執(zhí)行,責(zé)任鏈的對(duì)象連接起來(lái)
- @PostConstruct
- public void initializeChainFilter(){
- for(int i = 0;i<abstractHandleList.size();i++){
- if(i == 0){
- abstractHandler = abstractHandleList.get(0);
- }else{
- AbstractHandler currentHander = abstractHandleList.get(i - 1);
- AbstractHandler nextHander = abstractHandleList.get(i);
- currentHander.setNextHandler(nextHander);
- }
- }
- }
- //直接調(diào)用這個(gè)方法使用
- public Response exec(Request request, Response response) {
- abstractHandler.filter(request, response);
- return response;
- }
- public AbstractHandler getAbstractHandler() {
- return abstractHandler;
- }
- public void setAbstractHandler(AbstractHandler abstractHandler) {
- this.abstractHandler = abstractHandler;
- }
- }
運(yùn)行結(jié)果如下:
- 非空參數(shù)檢查
- 安全調(diào)用校驗(yàn)
- 校驗(yàn)黑名單
- check rule
3. 模板方法模式
3.1 業(yè)務(wù)場(chǎng)景
假設(shè)我們有這么一個(gè)業(yè)務(wù)場(chǎng)景:內(nèi)部系統(tǒng)不同商戶(hù),調(diào)用我們系統(tǒng)接口,去跟外部第三方系統(tǒng)交互(http方式)。走類(lèi)似這么一個(gè)流程,如下:
一個(gè)請(qǐng)求都會(huì)經(jīng)歷這幾個(gè)流程:
- 查詢(xún)商戶(hù)信息
- 對(duì)請(qǐng)求報(bào)文加簽
- 發(fā)送http請(qǐng)求出去
- 對(duì)返回的報(bào)文驗(yàn)簽
這里,有的商戶(hù)可能是走代理出去的,有的是走直連。假設(shè)當(dāng)前有A,B商戶(hù)接入,不少伙伴可能這么實(shí)現(xiàn),偽代碼如下:
- // 商戶(hù)A處理句柄
- CompanyAHandler implements RequestHandler {
- Resp hander(req){
- //查詢(xún)商戶(hù)信息
- queryMerchantInfo();
- //加簽
- signature();
- //http請(qǐng)求(A商戶(hù)假設(shè)走的是代理)
- httpRequestbyProxy()
- //驗(yàn)簽
- verify();
- }
- }
- // 商戶(hù)B處理句柄
- CompanyBHandler implements RequestHandler {
- Resp hander(Rreq){
- //查詢(xún)商戶(hù)信息
- queryMerchantInfo();
- //加簽
- signature();
- // http請(qǐng)求(B商戶(hù)不走代理,直連)
- httpRequestbyDirect();
- // 驗(yàn)簽
- verify();
- }
- }
假設(shè)新加一個(gè)C商戶(hù)接入,你需要再實(shí)現(xiàn)一套這樣的代碼。顯然,這樣代碼就重復(fù)了,一些通用的方法,卻在每一個(gè)子類(lèi)都重新寫(xiě)了這一方法。
如何優(yōu)化呢?可以使用模板方法模式。
3.2 模板方法模式定義
定義一個(gè)操作中的算法的骨架流程,而將一些步驟延遲到子類(lèi)中,使得子類(lèi)可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。它的核心思想就是:定義一個(gè)操作的一系列步驟,對(duì)于某些暫時(shí)確定不下來(lái)的步驟,就留給子類(lèi)去實(shí)現(xiàn),這樣不同的子類(lèi)就可以定義出不同的步驟。
打個(gè)通俗的比喻:
模式舉例:追女朋友要先“牽手”,再“擁抱”,再“接吻”, 再“拍拍..額..手”。至于具體你用左手還是右手牽,無(wú)所謂,但是整個(gè)過(guò)程,定了一個(gè)流程模板,按照模板來(lái)就行。
3.3 模板方法使用
- 一個(gè)抽象類(lèi),定義骨架流程(抽象方法放一起)
- 確定的共同方法步驟,放到抽象類(lèi)(去除抽象方法標(biāo)記)
- 不確定的步驟,給子類(lèi)去差異化實(shí)現(xiàn)
我們繼續(xù)那以上的舉例的業(yè)務(wù)流程例子,來(lái)一起用 模板方法優(yōu)化一下哈:
3.3.1 一個(gè)抽象類(lèi),定義骨架流程
因?yàn)橐粋€(gè)個(gè)請(qǐng)求經(jīng)過(guò)的流程為一下步驟:
- 查詢(xún)商戶(hù)信息
- 對(duì)請(qǐng)求報(bào)文加簽
- 發(fā)送http請(qǐng)求出去
- 對(duì)返回的報(bào)文驗(yàn)簽
所以我們就可以定義一個(gè)抽象類(lèi),包含請(qǐng)求流程的幾個(gè)方法,方法首先都定義為抽象方法哈:
- /**
- * 抽象類(lèi)定義骨架流程(查詢(xún)商戶(hù)信息,加簽,http請(qǐng)求,驗(yàn)簽)
- */
- abstract class AbstractMerchantService {
- //查詢(xún)商戶(hù)信息
- abstract queryMerchantInfo();
- //加簽
- abstract signature();
- //http 請(qǐng)求
- abstract httpRequest();
- // 驗(yàn)簽
- abstract verifySinature();
- }
3.3.2 確定的共同方法步驟,放到抽象類(lèi)
- abstract class AbstractMerchantService {
- //模板方法流程
- Resp handlerTempPlate(req){
- //查詢(xún)商戶(hù)信息
- queryMerchantInfo();
- //加簽
- signature();
- //http 請(qǐng)求
- httpRequest();
- // 驗(yàn)簽
- verifySinature();
- }
- // Http是否走代理(提供給子類(lèi)實(shí)現(xiàn))
- abstract boolean isRequestByProxy();
- }
3.3.3 不確定的步驟,給子類(lèi)去差異化實(shí)現(xiàn)
因?yàn)槭欠褡叽砹鞒淌遣淮_定的,所以給子類(lèi)去實(shí)現(xiàn)。
商戶(hù)A的請(qǐng)求實(shí)現(xiàn):
- CompanyAServiceImpl extends AbstractMerchantService{
- Resp hander(req){
- return handlerTempPlate(req);
- }
- //走h(yuǎn)ttp代理的
- boolean isRequestByProxy(){
- return true;
- }
商戶(hù)B的請(qǐng)求實(shí)現(xiàn):
- CompanyBServiceImpl extends AbstractMerchantService{
- Resp hander(req){
- return handlerTempPlate(req);
- }
- //公司B是不走代理的
- boolean isRequestByProxy(){
- return false;
- }
4. 觀(guān)察者模式
4.1 業(yè)務(wù)場(chǎng)景
登陸注冊(cè)應(yīng)該是最常見(jiàn)的業(yè)務(wù)場(chǎng)景了。就拿注冊(cè)來(lái)說(shuō)事,我們經(jīng)常會(huì)遇到類(lèi)似的場(chǎng)景,就是用戶(hù)注冊(cè)成功后,我們給用戶(hù)發(fā)一條消息,又或者發(fā)個(gè)郵件等等,因此經(jīng)常有如下的代碼:
- void register(User user){
- insertRegisterUser(user);
- sendIMMessage();
- sendEmail();
- }
這塊代碼會(huì)有什么問(wèn)題呢?如果產(chǎn)品又加需求:現(xiàn)在注冊(cè)成功的用戶(hù),再給用戶(hù)發(fā)一條短信通知。于是你又得改register方法的代碼了。。。這是不是違反了開(kāi)閉原則啦。
- void register(User user){
- insertRegisterUser(user);
- sendIMMessage();
- sendMobileMessage();
- sendEmail();
- }
并且,如果調(diào)發(fā)短信的接口失敗了,是不是又影響到用戶(hù)注冊(cè)了?!這時(shí)候,是不是得加個(gè)異步方法給通知消息才好。。。
實(shí)際上,我們可以使用觀(guān)察者模式優(yōu)化。
4.2 觀(guān)察者模式定義
觀(guān)察者模式定義對(duì)象間的一種一對(duì)多的依賴(lài)關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴(lài)于它的對(duì)象都得到通知并被完成業(yè)務(wù)的更新。
觀(guān)察者模式屬于行為模式,一個(gè)對(duì)象(被觀(guān)察者)的狀態(tài)發(fā)生改變,所有的依賴(lài)對(duì)象(觀(guān)察者對(duì)象)都將得到通知,進(jìn)行廣播通知。它的主要成員就是觀(guān)察者和被觀(guān)察者。
被觀(guān)察者(Observerable):目標(biāo)對(duì)象,狀態(tài)發(fā)生變化時(shí),將通知所有的觀(guān)察者。
觀(guān)察者(observer):接受被觀(guān)察者的狀態(tài)變化通知,執(zhí)行預(yù)先定義的業(yè)務(wù)。
使用場(chǎng)景: 完成某件事情后,異步通知場(chǎng)景。如,登陸成功,發(fā)個(gè)IM消息等等。
4.3 觀(guān)察者模式使用
觀(guān)察者模式實(shí)現(xiàn)的話(huà),還是比較簡(jiǎn)單的。
- 一個(gè)被觀(guān)察者的類(lèi)Observerable ;
- 多個(gè)觀(guān)察者Observer ;
- 觀(guān)察者的差異化實(shí)現(xiàn)
- 經(jīng)典觀(guān)察者模式封裝:EventBus實(shí)戰(zhàn)
4.3.1 一個(gè)被觀(guān)察者的類(lèi)Observerable 和 多個(gè)觀(guān)察者Observer
- public class Observerable {
- private List<Observer> observers
- = new ArrayList<Observer>();
- private int state;
- public int getState() {
- return state;
- }
- public void setState(int state) {
- notifyAllObservers();
- }
- //添加觀(guān)察者
- public void addServer(Observer observer){
- observers.add(observer);
- }
- //移除觀(guān)察者
- public void removeServer(Observer observer){
- observers.remove(observer);
- }
- //通知
- public void notifyAllObservers(int state){
- if(state!=1){
- System.out.println(“不是通知的狀態(tài)”);
- return ;
- }
- for (Observer observer : observers) {
- observer.doEvent();
- }
- }
- }
4.3.2 觀(guān)察者的差異化實(shí)現(xiàn)
- //觀(guān)察者
- interface Observer {
- void doEvent();
- }
- //Im消息
- IMMessageObserver implements Observer{
- void doEvent(){
- System.out.println("發(fā)送IM消息");
- }
- }
- //手機(jī)短信
- MobileNoObserver implements Observer{
- void doEvent(){
- System.out.println("發(fā)送短信消息");
- }
- }
- //EmailNo
- EmailObserver implements Observer{
- void doEvent(){
- System.out.println("發(fā)送email消息");
- }
- }
4.3.3 EventBus實(shí)戰(zhàn)
自己搞一套觀(guān)察者模式的代碼,還是有點(diǎn)小麻煩。實(shí)際上,Guava EventBus就封裝好了,它 提供一套基于注解的事件總線(xiàn),api可以靈活的使用,爽歪歪。
我們來(lái)看下EventBus的實(shí)戰(zhàn)代碼哈,首先可以聲明一個(gè)EventBusCenter類(lèi),它類(lèi)似于以上被觀(guān)察者那種角色Observerable。
- public class EventBusCenter {
- private static EventBus eventBus = new EventBus();
- private EventBusCenter() {
- }
- public static EventBus getInstance() {
- return eventBus;
- }
- //添加觀(guān)察者
- public static void register(Object obj) {
- eventBus.register(obj);
- }
- //移除觀(guān)察者
- public static void unregister(Object obj) {
- eventBus.unregister(obj);
- }
- //把消息推給觀(guān)察者
- public static void post(Object obj) {
- eventBus.post(obj);
- }
- }
然后再聲明觀(guān)察者EventListener
- public class EventListener {
- @Subscribe //加了訂閱,這里標(biāo)記這個(gè)方法是事件處理方法
- public void handle(NotifyEvent notifyEvent) {
- System.out.println("發(fā)送IM消息" + notifyEvent.getImNo());
- System.out.println("發(fā)送短信消息" + notifyEvent.getMobileNo());
- System.out.println("發(fā)送Email消息" + notifyEvent.getEmailNo());
- }
- }
- //通知事件類(lèi)
- public class NotifyEvent {
- private String mobileNo;
- private String emailNo;
- private String imNo;
- public NotifyEvent(String mobileNo, String emailNo, String imNo) {
- this.mobileNo = mobileNo;
- this.emailNo = emailNo;
- this.imNo = imNo;
- }
- }
使用demo測(cè)試:
- public class EventBusDemoTest {
- public static void main(String[] args) {
- EventListener eventListener = new EventListener();
- EventBusCenter.register(eventListener);
- EventBusCenter.post(new NotifyEvent("13372817283", "123@qq.com", "666"));
- }
- }
運(yùn)行結(jié)果:
- 發(fā)送IM消息666
- 發(fā)送短信消息13372817283
- 發(fā)送Email消息123@qq.com
5. 工廠(chǎng)模式
5.1 業(yè)務(wù)場(chǎng)景
工廠(chǎng)模式一般配合策略模式一起使用。用來(lái)去優(yōu)化大量的if...else...或switch...case...條件語(yǔ)句。
我們就取第一小節(jié)中策略模式那個(gè)例子吧。根據(jù)不同的文件解析類(lèi)型,創(chuàng)建不同的解析對(duì)象
- IFileStrategy getFileStrategy(FileTypeResolveEnum fileType){
- IFileStrategy fileStrategy ;
- if(fileType=FileTypeResolveEnum.File_A_RESOLVE){
- fileStrategy = new AFileResolve();
- }else if(fileType=FileTypeResolveEnum.File_A_RESOLV){
- fileStrategy = new BFileResolve();
- }else{
- fileStrategy = new DefaultFileResolve();
- }
- return fileStrategy;
- }
其實(shí)這就是工廠(chǎng)模式,定義一個(gè)創(chuàng)建對(duì)象的接口,讓其子類(lèi)自己決定實(shí)例化哪一個(gè)工廠(chǎng)類(lèi),工廠(chǎng)模式使其創(chuàng)建過(guò)程延遲到子類(lèi)進(jìn)行。
策略模式的例子,沒(méi)有使用上一段代碼,而是借助spring的特性,搞了一個(gè)工廠(chǎng)模式,哈哈,小伙伴們可以回去那個(gè)例子細(xì)品一下,我把代碼再搬下來(lái),小伙伴們?cè)倨芬幌掳桑?/p>
- /**
- * @author 公眾號(hào):撿田螺的小男孩
- */
- @Component
- public class StrategyUseService implements ApplicationContextAware{
- private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
- //把所有的文件類(lèi)型解析的對(duì)象,放到map,需要使用時(shí),信手拈來(lái)即可。這就是工廠(chǎng)模式的一種體現(xiàn)啦
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
- tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
- }
- }
5.2 使用工廠(chǎng)模式
定義工廠(chǎng)模式也是比較簡(jiǎn)單的:
一個(gè)工廠(chǎng)接口,提供一個(gè)創(chuàng)建不同對(duì)象的方法。
其子類(lèi)實(shí)現(xiàn)工廠(chǎng)接口,構(gòu)造不同對(duì)象
使用工廠(chǎng)模式
5.3.1 一個(gè)工廠(chǎng)接口
- interface IFileResolveFactory{
- void resolve();
- }
5.3.2 不同子類(lèi)實(shí)現(xiàn)工廠(chǎng)接口
- class AFileResolve implements IFileResolveFactory{
- void resolve(){
- System.out.println("文件A類(lèi)型解析");
- }
- }
- class BFileResolve implements IFileResolveFactory{
- void resolve(){
- System.out.println("文件B類(lèi)型解析");
- }
- }
- class DefaultFileResolve implements IFileResolveFactory{
- void resolve(){
- System.out.println("默認(rèn)文件類(lèi)型解析");
- }
- }
5.3.3 使用工廠(chǎng)模式
- //構(gòu)造不同的工廠(chǎng)對(duì)象
- IFileResolveFactory fileResolveFactory;
- if(fileType=“A”){
- fileResolveFactory = new AFileResolve();
- }else if(fileType=“B”){
- fileResolveFactory = new BFileResolve();
- }else{
- fileResolveFactory = new DefaultFileResolve();
- }
- fileResolveFactory.resolve();
一般情況下,對(duì)于工廠(chǎng)模式,你不會(huì)看到以上的代碼。工廠(chǎng)模式會(huì)跟配合其他設(shè)計(jì)模式如策略模式一起出現(xiàn)的。
6. 單例模式
6.1 業(yè)務(wù)場(chǎng)景
單例模式,保證一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)訪(fǎng)問(wèn)它的全局訪(fǎng)問(wèn)點(diǎn)。I/O與數(shù)據(jù)庫(kù)的連接,一般就用單例模式實(shí)現(xiàn)de的。Windows里面的Task Manager(任務(wù)管理器)也是很典型的單例模式。
來(lái)看一個(gè)單例模式的例子
- /**
- * 公眾號(hào):撿田螺的小男孩
- */
- public class LanHanSingleton {
- private static LanHanSingleton instance;
- private LanHanSingleton(){
- }
- public static LanHanSingleton getInstance(){
- if (instance == null) {
- instance = new LanHanSingleton();
- }
- return instance;
- }
- }
以上的例子,就是懶漢式的單例實(shí)現(xiàn)。實(shí)例在需要用到的時(shí)候,才去創(chuàng)建,就比較懶。如果有則返回,沒(méi)有則新建,需要加下 synchronized關(guān)鍵字,要不然可能存在線(xiàn)性安全問(wèn)題。
6.2 單例模式的經(jīng)典寫(xiě)法
其實(shí)單例模式還有有好幾種實(shí)現(xiàn)方式,如餓漢模式,雙重校驗(yàn)鎖,靜態(tài)內(nèi)部類(lèi),枚舉等實(shí)現(xiàn)方式。
6.2.1 餓漢模式
- public class EHanSingleton {
- private static EHanSingleton instance = new EHanSingleton();
- private EHanSingleton(){
- }
- public static EHanSingleton getInstance() {
- return instance;
- }
- }
餓漢模式,它比較饑餓、比較勤奮,實(shí)例在初始化的時(shí)候就已經(jīng)建好了,不管你后面有沒(méi)有用到,都先新建好實(shí)例再說(shuō)。這個(gè)就沒(méi)有線(xiàn)程安全的問(wèn)題,但是呢,浪費(fèi)內(nèi)存空間呀。
6.2.2 雙重校驗(yàn)鎖
- public class DoubleCheckSingleton {
- private static DoubleCheckSingleton instance;
- private DoubleCheckSingleton() { }
- public static DoubleCheckSingleton getInstance(){
- if (instance == null) {
- synchronized (DoubleCheckSingleton.class) {
- if (instance == null) {
- instance = new DoubleCheckSingleton();
- }
- }
- }
- return instance;
- }
- }
雙重校驗(yàn)鎖實(shí)現(xiàn)的單例模式,綜合了懶漢式和餓漢式兩者的優(yōu)缺點(diǎn)。以上代碼例子中,在synchronized關(guān)鍵字內(nèi)外都加了一層 if條件判斷,這樣既保證了線(xiàn)程安全,又比直接上鎖提高了執(zhí)行效率,還節(jié)省了內(nèi)存空間。
6.2.3 靜態(tài)內(nèi)部類(lèi)
- public class InnerClassSingleton {
- private static class InnerClassSingletonHolder{
- private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
- }
- private InnerClassSingleton(){}
- public static final InnerClassSingleton getInstance(){
- return InnerClassSingletonHolder.INSTANCE;
- }
- }
靜態(tài)內(nèi)部類(lèi)的實(shí)現(xiàn)方式,效果有點(diǎn)類(lèi)似雙重校驗(yàn)鎖。但這種方式只適用于靜態(tài)域場(chǎng)景,雙重校驗(yàn)鎖方式可在實(shí)例域需要延遲初始化時(shí)使用。
6.2.4 枚舉
- public enum SingletonEnum {
- INSTANCE;
- public SingletonEnum getInstance(){
- return INSTANCE;
- }
- }
枚舉實(shí)現(xiàn)的單例,代碼簡(jiǎn)潔清晰。并且它還自動(dòng)支持序列化機(jī)制,絕對(duì)防止多次實(shí)例化。
本文轉(zhuǎn)載自微信公眾號(hào)「撿田螺的小男孩」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系撿田螺的小男孩公眾號(hào)。