設計模式--裝飾者模式

裝飾者模式

定義

動態地給一個對象添加一些額外的職責。就增長功能來講,裝飾模式相比生成子類更爲靈活。前端

通用類圖

意圖

動態地給一個對象添加一些額外的職責。就增長功能來講,裝飾模式相比生成子類更爲靈活。java

優勢

  1. 裝飾類和被裝飾類能夠獨立發展,而不會相互耦合。
  2. 裝飾模式是繼承關係的一個替代方案。
  3. 裝飾模式能夠動態地擴展一個實現類的功能。

缺點

多層裝飾容易致使問題,儘可能減小裝飾類的數量,以便下降系統的複雜度。web

應用場景

  1. 須要擴展一個類的功能,或給一個類增長附加功能。
  2. 須要動態地給一個對象增長功能,這些功能能夠再動態地撤銷。
  3. 須要爲一批的兄弟類進行改裝或加裝功能。

擴展

說明

裝飾模式是對繼承的有力補充。要知道繼承不是萬能的,繼承能夠解決實際的問題,可是在項目中你要考慮諸如易維護、易擴展、易複用等,並且在一些狀況下要是用繼承就會增長不少子類,並且靈活性很是差,那固然維護也不容易,也就是說裝飾模式能夠替代繼承,解決咱們類膨脹的問題。同時,繼承是靜態地給類增長功能,而裝飾模式則是動態地增長功能。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 調用便可。

相關文章
相關標籤/搜索