詳解Java如何優(yōu)雅的使用裝飾器模式
什么是裝飾器模式
裝飾器模式(Decorator Pattern): 在不改變對(duì)象自身的基礎(chǔ)上,在程序運(yùn)行期間給對(duì)象動(dòng)態(tài)的添加職責(zé);
感覺和繼承如出一轍,不改變父類,子類可拓展功能;
優(yōu)點(diǎn)
裝飾類和被裝飾類可以獨(dú)立發(fā)展,不會(huì)相互耦合
相比于繼承,更加的輕便、靈活
可以動(dòng)態(tài)擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能,不必修改原本代碼
缺點(diǎn)
會(huì)產(chǎn)生很多的裝飾類,增加了系統(tǒng)的復(fù)雜性。
這種比繼承更加靈活機(jī)動(dòng)的特性,也同時(shí)意味著裝飾模式比繼承易于出錯(cuò),排錯(cuò)也很困難,對(duì)于多次裝飾的對(duì)象,調(diào)試時(shí)尋找錯(cuò)誤可能需要逐級(jí)排查,較為繁瑣。
使用場(chǎng)景
對(duì)已有的目標(biāo)功能存在不足,需要增強(qiáng)時(shí),擴(kuò)展類的功能。
動(dòng)態(tài)增加功能,動(dòng)態(tài)撤銷
裝飾器模式和代理模式的區(qū)別
代理是全權(quán)代理,目標(biāo)根本不對(duì)外,全部由代理類來完成;裝飾是增強(qiáng),是輔助,目標(biāo)仍然可以自行對(duì)外提供服務(wù),裝飾器只起增強(qiáng)作用。
裝飾器模式強(qiáng)調(diào)的是:增強(qiáng)、新增行為;代理模式強(qiáng)調(diào)的是:對(duì)代理的對(duì)象施加控制,但不對(duì)對(duì)象本身的功能進(jìn)行增強(qiáng)。
裝飾器模式:生效的對(duì)象還是原本的對(duì)象;代理模式:生效的是新的對(duì)象(代理對(duì)象)
裝飾器和代理的區(qū)別
裝飾器的簡(jiǎn)單實(shí)現(xiàn)
場(chǎng)景:天氣太熱了,喝點(diǎn)兒冰水解解暑;加點(diǎn)兒檸檬片,讓果汁好喝點(diǎn)兒
先定義一個(gè)喝水的接口
public?interface?Drink?{ ????/** ?????*?喝水 ?????*/ ????void?drink(); }
寫一個(gè)接口的實(shí)現(xiàn)
public?class?DrinkWater?implements?Drink?{ ????@Override ????public?void?drink()?{ ????????System.out.println("喝水"); ????} }
一個(gè)簡(jiǎn)單的裝飾器
public?class?DrinkDecorator?implements?Drink?{ ????private?final?Drink?drink; ????public?DrinkDecorator(Drink?drink)?{ ????????this.drink?=?drink; ????} ????@Override ????public?void?drink()?{ ????????System.out.println("先加點(diǎn)兒檸檬片"); ????????drink.drink(); ????} }
開始測(cè)試
public?class?DrinkMain?{ ????public?static?void?main(String[]?args)?{ ????????Drink?drink?=?new?DrinkWater(); ????????drink?=?new?DrinkDecorator(drink); ????????drink.drink(); ????} }
運(yùn)行結(jié)果
先加點(diǎn)兒檸檬片
喝水
一個(gè)簡(jiǎn)單的裝飾器模式例子就寫完了;當(dāng)然這種例子在實(shí)際項(xiàng)目中肯定是用不到的,這里只是先了解一下裝飾器模式
裝飾器模式實(shí)戰(zhàn)
場(chǎng)景: 項(xiàng)目一期開發(fā)的時(shí)候,并沒有給鑒權(quán)部分設(shè)置緩存;二期開發(fā)考慮到性能問題,想要給鑒權(quán)部分加上緩存,這里就選擇了使用裝飾器模式進(jìn)行處理;
這里使用的緩存是spring的 spring-cache,不了解沒關(guān)系,知道幾個(gè)注解什么意思就行
@Cacheable
表示要對(duì)方法返回值進(jìn)行緩存
@CacheEvict
刪除緩存注解
為了簡(jiǎn)潔,以下代碼均為偽代碼
首先,需要一個(gè)權(quán)限的接口和實(shí)現(xiàn)類
public?interface?IDataAccessor?{ ????/** ?????*?根據(jù)部門上級(jí)?id?獲取所有子集部門 ?????*/ ????Set<Long>?deptFindAllChildrenByParentIds(Collection<Long>?parentIds); ????/** ?????*?獲取數(shù)據(jù)范圍內(nèi)的部門 ?????*/ ????Set<Long>?deptFindScopeById(Long?userId);
實(shí)現(xiàn)類(注意這里加了@Service
, 交給spring處理)
@Service public?class?ScopeDataAccessorImpl?implements?IDataAccessor?{ ????@Autowired ????private?IDepartmentService?departmentService; ???? ????@Autowired ????private?INodeScopeService?nodeScopeService; ????@Override ????public?Set<Long>?deptFindAllChildrenByParentIds(Collection<Long>?parentIds)?{ ????????Set<Long>?result?=?new?HashSet<>(); ????????departmentService.departmentChildren(parentIds,?result); ????????return?result; ????} ???? ????@Override ????public?Set<Long>?deptFindScopeById(Long?userId)?{ ????????return?nodeScopeService.deptFindScopeById(userId); ????} }
接下來就是對(duì)之前的代碼進(jìn)行裝飾,定義一個(gè)裝飾器的實(shí)現(xiàn)類
(這個(gè)類沒有 @Component
, 沒有直接交給spring管理;加了注解會(huì)報(bào)錯(cuò):找到了2個(gè)bean)
public?class?DataAccessorDecorator?implements?IDataAccessor?{ ????private?final?IDataAccessor?iDataAccessor; ????public?DataAccessorDecorator(IDataAccessor?iDataAccessor)?{ ????????this.iDataAccessor?=?iDataAccessor; ????} ????@Cacheable(cacheNames?=?"dept:parentId",?key?=?"#p0",?sync?=?true) ????@Override ????public?Set<Long>?deptFindAllChildrenByParentIds(Collection<Long>?parentIds)?{ ????????return?iDataAccessor.deptFindAllChildrenByParentIds(parentIds); ????} ????@Cacheable(cacheNames?=?"dept:scope:userId",?key?=?"#p0",?sync?=?true) ????@Override ????public?Set<Long>?deptFindScopeById(Long?userId)?{ ????????return?iDataAccessor.deptFindScopeById(nodeId,userId); ????} }
接下來還需要將這個(gè)裝飾器的類注冊(cè)到spring中
@Configuration @ConditionalOnBean({IDataAccessor.class}) public?class?Config?{ ???? ????@Bean ????@ConditionalOnBean({IDataAccessor.class}) ????public?DataAccessorDecorator?dataAccessorDecorator(IDataAccessor?iDataAccessor)?{ ????????return?new?DataAccessorDecorator(iDataAccessor); ????} }
根據(jù)業(yè)務(wù),維護(hù)緩存更新;這里使用的監(jiān)聽部門和員工的變更事件
@Component public?class?DataScopeEvict?{ ????/** ?????*?清空部門相關(guān)緩存 ?????*/ ????@CacheEvict(cacheNames?=?{"dept:parentId"},?allEntries?=?true) ????public?void?department()?{ ????} ????/** ?????*?清空用戶相關(guān)緩存 ?????*/ ????@CacheEvict(cacheNames?=?{"dept:scope:userId"},?allEntries?=?true) ????public?void?user()?{ ????} }
@Component public?class?ScopeDataEventListener?{ ????@Autowired ????private?DataScopeEvict?evict; ???? ?/** ?????*?監(jiān)聽部門變更事件 ?????*/ ????@EventListener ????public?void?departmentEvent(DepartmentChangeEvent?event)?{ ????????//?1?增加?2?刪除?3?上級(jí)部門變更 ????????evict.department(); ????} ????/** ?????*?監(jiān)聽user變更事件 ?????*/ ????@EventListener ????public?void?userEvent(UserChangeEvent?event)?{ ????????//?2?刪除?3?主部門變更 ????????if?(event.getType().equals(2)?||?event.getType().equals(3))?{ ????????????evict.user(); ????????} ????} }
一切準(zhǔn)備就緒,使用的時(shí)候直接使用裝飾器類就好了
@Service public?class?UserService?{ ???? ????@Autowired ????DataAccessorDecorator?scopeDataAccessor; ???? ????public?Set<Long>?deptFindAllChildrenByParentIds(Collection<Long>?parentIds)?{ ????????return?scopeDataAccessor.deptFindAllChildrenByParentIds(parentIds); ????} ???? ???? ????public?Set<Long>?deptFindScopeById(Long?userId)?{ ????????return?scopeDataAccessor.deptFindScopeById(userId); ????} ???? }
以上就是一個(gè)將裝飾器模式應(yīng)用到實(shí)際項(xiàng)目的例子;
在這個(gè)例子中,使用裝飾器模式增強(qiáng)了原本的代碼,不修改原本的代碼,原本的代碼也能正確提供服務(wù),只不過沒有使用緩存;只要方法名命名一致,只需修改注入的字段就可以升級(jí)完成,升級(jí)成本還是很低的。
這波使用裝飾器模式加緩存的操作寫到項(xiàng)目中,直接讓你的代碼 B ge pull full
小結(jié)
雖然使用裝飾器模式看起來B格高,但還是要注意自己項(xiàng)目的場(chǎng)景,選擇適合的方式解決問題。
以上就是詳解Java如何優(yōu)雅的使用裝飾器模式的詳細(xì)內(nèi)容,更多關(guān)于Java裝飾器模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java實(shí)現(xiàn)批量下載 多文件打包成zip格式下載
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)批量下載、將多文件打包成zip格式下載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07Java代理的幾種實(shí)現(xiàn)方式總結(jié)
本文將通過例子說明java代理的幾種實(shí)現(xiàn)方式,并比較它們之間的差異,文中通過代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的參考價(jià)值,需要的朋友可以參考下2023-12-12SpringBoot連接MySql數(shù)據(jù)庫(kù)的原理及代碼示例
SpringBoot是一款流行的Java開發(fā)框架,它可以輕松地連接各種類型的數(shù)據(jù)庫(kù),包括關(guān)系型數(shù)據(jù)庫(kù)和非關(guān)系型數(shù)據(jù)庫(kù),本文將介紹SpringBoot是如何連接數(shù)據(jù)庫(kù)的,包括其原理和代碼示例,需要的朋友可以參考下2023-07-07Spring MVC的文件上傳和下載以及攔截器的使用實(shí)例
這篇文章主要介紹了Spring MVC的文件上傳和下載以及攔截器的使用實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08如何利用Java使用AOP實(shí)現(xiàn)數(shù)據(jù)字典轉(zhuǎn)換
這篇文章主要介紹了如何利用Java使用AOP實(shí)現(xiàn)數(shù)據(jù)字典轉(zhuǎn)換,AOP也是我們常說的面向切面編程,AOP在我們開發(fā)過程中應(yīng)用也比較多,在這里我們就基于AOP來實(shí)現(xiàn)一個(gè)數(shù)據(jù)字典轉(zhuǎn)換的案例2022-06-06