詳解Java如何優(yōu)雅的使用裝飾器模式
什么是裝飾器模式
裝飾器模式(Decorator Pattern): 在不改變對象自身的基礎(chǔ)上,在程序運行期間給對象動態(tài)的添加職責;
感覺和繼承如出一轍,不改變父類,子類可拓展功能;
優(yōu)點
裝飾類和被裝飾類可以獨立發(fā)展,不會相互耦合
相比于繼承,更加的輕便、靈活
可以動態(tài)擴展一個實現(xiàn)類的功能,不必修改原本代碼
缺點
會產(chǎn)生很多的裝飾類,增加了系統(tǒng)的復雜性。
這種比繼承更加靈活機動的特性,也同時意味著裝飾模式比繼承易于出錯,排錯也很困難,對于多次裝飾的對象,調(diào)試時尋找錯誤可能需要逐級排查,較為繁瑣。
使用場景
對已有的目標功能存在不足,需要增強時,擴展類的功能。
動態(tài)增加功能,動態(tài)撤銷
裝飾器模式和代理模式的區(qū)別
代理是全權(quán)代理,目標根本不對外,全部由代理類來完成;裝飾是增強,是輔助,目標仍然可以自行對外提供服務(wù),裝飾器只起增強作用。
裝飾器模式強調(diào)的是:增強、新增行為;代理模式強調(diào)的是:對代理的對象施加控制,但不對對象本身的功能進行增強。
裝飾器模式:生效的對象還是原本的對象;代理模式:生效的是新的對象(代理對象)

裝飾器和代理的區(qū)別
裝飾器的簡單實現(xiàn)
場景:天氣太熱了,喝點兒冰水解解暑;加點兒檸檬片,讓果汁好喝點兒
先定義一個喝水的接口
public?interface?Drink?{
????/**
?????*?喝水
?????*/
????void?drink();
}
寫一個接口的實現(xiàn)
public?class?DrinkWater?implements?Drink?{
????@Override
????public?void?drink()?{
????????System.out.println("喝水");
????}
}
一個簡單的裝飾器
public?class?DrinkDecorator?implements?Drink?{
????private?final?Drink?drink;
????public?DrinkDecorator(Drink?drink)?{
????????this.drink?=?drink;
????}
????@Override
????public?void?drink()?{
????????System.out.println("先加點兒檸檬片");
????????drink.drink();
????}
}
開始測試
public?class?DrinkMain?{
????public?static?void?main(String[]?args)?{
????????Drink?drink?=?new?DrinkWater();
????????drink?=?new?DrinkDecorator(drink);
????????drink.drink();
????}
}
運行結(jié)果
先加點兒檸檬片
喝水
一個簡單的裝飾器模式例子就寫完了;當然這種例子在實際項目中肯定是用不到的,這里只是先了解一下裝飾器模式
裝飾器模式實戰(zhàn)
場景: 項目一期開發(fā)的時候,并沒有給鑒權(quán)部分設(shè)置緩存;二期開發(fā)考慮到性能問題,想要給鑒權(quán)部分加上緩存,這里就選擇了使用裝飾器模式進行處理;
這里使用的緩存是spring的 spring-cache,不了解沒關(guān)系,知道幾個注解什么意思就行
@Cacheable 表示要對方法返回值進行緩存
@CacheEvict 刪除緩存注解
為了簡潔,以下代碼均為偽代碼
首先,需要一個權(quán)限的接口和實現(xiàn)類
public?interface?IDataAccessor?{
????/**
?????*?根據(jù)部門上級?id?獲取所有子集部門
?????*/
????Set<Long>?deptFindAllChildrenByParentIds(Collection<Long>?parentIds);
????/**
?????*?獲取數(shù)據(jù)范圍內(nèi)的部門
?????*/
????Set<Long>?deptFindScopeById(Long?userId);
實現(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);
????}
}
接下來就是對之前的代碼進行裝飾,定義一個裝飾器的實現(xiàn)類
(這個類沒有 @Component, 沒有直接交給spring管理;加了注解會報錯:找到了2個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);
????}
}
接下來還需要將這個裝飾器的類注冊到spring中
@Configuration
@ConditionalOnBean({IDataAccessor.class})
public?class?Config?{
????
????@Bean
????@ConditionalOnBean({IDataAccessor.class})
????public?DataAccessorDecorator?dataAccessorDecorator(IDataAccessor?iDataAccessor)?{
????????return?new?DataAccessorDecorator(iDataAccessor);
????}
}
根據(jù)業(yè)務(wù),維護緩存更新;這里使用的監(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?上級部門變更
????????evict.department();
????}
????/**
?????*?監(jiān)聽user變更事件
?????*/
????@EventListener
????public?void?userEvent(UserChangeEvent?event)?{
????????//?2?刪除?3?主部門變更
????????if?(event.getType().equals(2)?||?event.getType().equals(3))?{
????????????evict.user();
????????}
????}
}
一切準備就緒,使用的時候直接使用裝飾器類就好了
@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);
????}
????
}
以上就是一個將裝飾器模式應用到實際項目的例子;
在這個例子中,使用裝飾器模式增強了原本的代碼,不修改原本的代碼,原本的代碼也能正確提供服務(wù),只不過沒有使用緩存;只要方法名命名一致,只需修改注入的字段就可以升級完成,升級成本還是很低的。
這波使用裝飾器模式加緩存的操作寫到項目中,直接讓你的代碼 B ge pull full
小結(jié)
雖然使用裝飾器模式看起來B格高,但還是要注意自己項目的場景,選擇適合的方式解決問題。
以上就是詳解Java如何優(yōu)雅的使用裝飾器模式的詳細內(nèi)容,更多關(guān)于Java裝飾器模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot中的FailureAnalyzer使用詳解
這篇文章主要介紹了SpringBoot中的FailureAnalyzer使用詳解,Spring Boot的FailureAnalyzer是一個接口,它用于在Spring Boot應用啟動失敗時提供有關(guān)錯誤的詳細信息,這對于開發(fā)者來說非常有用,因為它可以幫助我們快速識別問題并找到解決方案,需要的朋友可以參考下2023-12-12
springboot跨域過濾器fetch react Response to p
這篇文章主要介紹了springboot跨域過濾器fetch react Response to preflight request doesn‘t pass access control check問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
Spring?BeanDefinition父子關(guān)系示例解析
這篇文章主要為大家介紹了Spring?BeanDefinition父子關(guān)系示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
Java依賴倒轉(zhuǎn)原則_動力節(jié)點Java學院整理
這篇文章主要介紹了Java依賴倒轉(zhuǎn)原則的定義及問題由來解決方案,感興趣的朋友一起看看吧2017-08-08
java?使用BeanFactory實現(xiàn)service與dao層解耦合詳解
這篇文章主要介紹了java?使用BeanFactory實現(xiàn)service與dao層解耦合詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java調(diào)用構(gòu)造函數(shù)和方法及使用詳解
在Java編程中,構(gòu)造函數(shù)用于初始化新創(chuàng)建的對象,而方法則用于執(zhí)行對象的行為,構(gòu)造函數(shù)在使用new關(guān)鍵字創(chuàng)建類實例時自動調(diào)用,沒有返回類型,并且名稱與類名相同,本文通過示例詳細介紹了如何在Java中使用構(gòu)造函數(shù)和方法,感興趣的朋友一起看看吧2024-10-10

