動態地給一個對象添加一些額外的職責。就增長功能來講,裝飾模式相比生成子類更爲靈活。前端
動態地給一個對象添加一些額外的職責。就增長功能來講,裝飾模式相比生成子類更爲靈活。java
多層裝飾容易致使問題,儘可能減小裝飾類的數量,以便下降系統的複雜度。web
裝飾模式是對繼承的有力補充。要知道繼承不是萬能的,繼承能夠解決實際的問題,可是在項目中你要考慮諸如易維護、易擴展、易複用等,並且在一些狀況下要是用繼承就會增長不少子類,並且靈活性很是差,那固然維護也不容易,也就是說裝飾模式能夠替代繼承,解決咱們類膨脹的問題。同時,繼承是靜態地給類增長功能,而裝飾模式則是動態地增長功能。redis
下面結合spring項目對裝飾者模式舉一個簡單的例子。spring
1.Controller數據庫
@RestController public class HotelController { @Resource private HotelManager hotelManager; @GetMapping("/listHotel") public String listHotel() { return JSON.toJSONString(hotelManager.listHotel()); } }
有一個controller,接收一個 listHotel 的 get 請求,調用 hotelManager (service層)的 listHotel() 接口。緩存
2.Serviceapp
public interface HotelManager { List<Hotel> listHotel(); } @Service("hotelManager") public class HotelManagerImpl implements HotelManager { @Resource private HotelDao hotelDao; @Override public List<Hotel> listHotel() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return hotelDao.listHotel(); } }
hotelManager 的 listHotel 接口直接調用 hotelDao 的 listHotel() 接口,從數據庫中查詢數據。這裏 Thread.sleep(3000) 爲了模擬從數據庫查詢數據返回比較慢的狀況。ide
這是在java web應用開發中一個很簡單的,從前端接收一個請求到數據庫查詢數據返回給前端的一個動做。當這個查詢效率很是低,耗時很是多,可是數據又不會常常變的狀況下,咱們能夠經過把數據放到緩存(redis memcached等)裏面來提升查詢效率。memcached
若是在項目進度很是緊的狀況下,咱們極可能寫出下面的代碼
public List<Hotel> listHotel() { if (在redis中能夠查詢到結果) { return redis.get(結果) } else { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return hotelDao.listHotel(); } }
首先很是醜,其次多餘的代碼和業務邏輯混在一塊兒,讓人不能一眼就看清這個接口作了什麼事情。
接下來經過裝飾者模式重構一下
增長一個自定義註解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RedisCache { }
這是一個空的方法級別的註解,表示被該註解標示的方法返回的數據都應緩存在redis。
恢復 hotelManager.listHotel() 方法而且頭上加入自定義註解
@RedisCache @Override public List<Hotel> listHotel() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return hotelDao.listHotel(); }
新建一個 HotelManagerDecorate 類
public class HotelManagerDecorate { private HotelManager hotelManager; private RedisTemplate redisTemplate; public List<Hotel> listHotel() throws Exception{ Method method = Util.getTarget(hotelManager).getClass().getDeclaredMethod("listHotel", null); if (method.isAnnotationPresent(RedisCache.class)) { List<Hotel> redisHotelList = (List<Hotel>) redisTemplate.opsForList().range("a", 0, -1); return Optional.ofNullable(redisHotelList).filter(list -> list.size() > 0).orElseGet(() -> { List<Hotel> hotelList = hotelManager.listHotel(); redisTemplate.opsForList().leftPushAll("a", hotelList); redisTemplate.expire("a", 300, TimeUnit.SECONDS); return hotelList; }); } else { return hotelManager.listHotel(); } } }
這是一個裝飾者類,裝飾了 hotelManager 這個類,經過反射獲取到方法上面的註解,判斷是否存在 RedisCache 這個註解,若是存在,則去redis中取得數據,不然從數據查出數據放入redis再返回。
Optional類是 java8 新增的類,有興趣的能夠了解一下。
修改 HotelController 爲
@GetMapping("/listHotel") public String listHotel() throws Exception{ HotelManagerDecorate hotelManagerDecorate = new HotelManagerDecorate(hotelManager, redisTemplate); return JSON.toJSONString(hotelManagerDecorate.listHotel()); }
修改了調用方法,由原來調用hotelManager改成調用裝飾者類,其實裝飾者類最終仍是調用了hotelManager.listHotel() 方法。
這麼修改以後就能夠發現,再沒有修改原有代碼的基礎上,動態的給 hotelManager.listHotel() 方法增長了緩存功能,對方法的功能實現了加強,而且加強代碼與原來的業務邏輯是分離的。裝飾者只負責加強功能,業務代碼根本不知道裝飾者的存在,這樣的作法很是易於擴展和維護,具體如何調用只是由 controller 層(高層)決定。若是未來想修改一下 RedisCache 邏輯,直接在裝飾類中修改便可,根本不會影響到業務邏輯。若是未來想換一個加強的功能,直接新建一個裝飾者類,修改一下 controller 調用便可。