裝飾器模式(Decorator)——深刻理解與實戰應用

  本文爲原創博文,轉載請註明出處,侵權必究!json

  一、初識裝飾器模式設計模式

    裝飾器模式,顧名思義,就是對已經存在的某些類進行裝飾,以此來擴展一些功能。其結構圖以下:框架

      

    • Component爲統一接口,也是裝飾類和被裝飾類的基本類型。
    • ConcreteComponent爲具體實現類,也是被裝飾類,他自己是個具備一些功能的完整的類。
    • Decorator是裝飾類,實現了Component接口的同時還在內部維護了一個ConcreteComponent的實例,並能夠經過構造函數初始化。而Decorator自己,一般採用默認實現,他的存在僅僅是一個聲明:我要生產出一些用於裝飾的子類了。而其子類纔是賦有具體裝飾效果的裝飾產品類。
    • ConcreteDecorator是具體的裝飾產品類,每一種裝飾產品都具備特定的裝飾效果。能夠經過構造器聲明裝飾哪一種類型的ConcreteComponent,從而對其進行裝飾。

  二、最簡單的代碼實現裝飾器模式ide

//基礎接口
public interface Component {
    
    public void biu();
}
//具體實現類
public class ConcretComponent implements Component {

    public void biu() {
        
        System.out.println("biubiubiu");
    }
}
//裝飾類
public class Decorator implements Component {

    public Component component;
    
    public Decorator(Component component) {
        
        this.component = component;
    }
    
    public void biu() {
        
        this.component.biu();
    }
}
//具體裝飾類
public class ConcreteDecorator extends Decorator {

    public ConcreteDecorator(Component component) {

        super(component);
    }

    public void biu() {
        
        System.out.println("ready?go!");
        this.component.biu();
    }
}

    這樣一個基本的裝飾器體系就出來了,當咱們想讓Component在打印以前都有一個ready?go!的提示時,就可使用ConcreteDecorator類了。具體方式以下:函數

  //使用裝飾器
  Component component = new ConcreteDecorator(new ConcretComponent());
  component.biu();

  //console:
  ready?go!
  biubiubiu

  三、爲什麼使用裝飾器模式this

    一個設計模式的出現必定有他特殊的價值。僅僅看見上面的結構圖你可能會想,爲什麼要兜這麼一圈來實現?僅僅是想要多一行輸出,我直接繼承ConcretComponent,或者直接在另外一個Component的實現類中實現不是同樣嗎?spa

    首先,裝飾器的價值在於裝飾,他並不影響被裝飾類自己的核心功能。在一個繼承的體系中,子類一般是互斥的。好比一輛車,品牌只能要麼是奧迪、要麼是寶馬,不可能同時屬於奧迪和寶馬,而品牌也是一輛車自己的重要屬性特徵。但當你想要給汽車噴漆,換坐墊,或者更換音響時,這些功能是互相可能兼容的,而且他們的存在不會影響車的核心屬性:那就是他是一輛什麼車。這時你就能夠定義一個裝飾器:噴了漆的車。無論他裝飾的車是寶馬仍是奧迪,他的噴漆效果均可以實現。設計

    再回到這個例子中,咱們看到的僅僅是一個ConcreteComponent類。在複雜的大型項目中,同一級下的兄弟類一般有不少。當你有五個甚至十個ConcreteComponent時,再想要爲每一個類都加上「ready?go!」的效果,就要寫出五個子類了。毫無疑問這是不合理的。裝飾器模式在不影響各個ConcreteComponent核心價值的同時,添加了他特有的裝飾效果,具有很是好的通用性,這也是他存在的最大價值。日誌

  四、實戰中使用裝飾器模式code

    寫這篇博客的初衷也是剛好在工做中使用到了這個模式,以爲很是好用。需求大體是這樣:採用sls服務監控項目日誌,以Json的格式解析,因此須要將項目中的日誌封裝成json格式再打印。現有的日誌體系採用了log4j + slf4j框架搭建而成。調用起來是這樣的:

  private static final Logger logger = LoggerFactory.getLogger(Component.class);
  logger.error(string);

    這樣打印出來的是毫無規範的一行行字符串。在考慮將其轉換成json格式時,我採用了裝飾器模式。目前有的是統一接口Logger和其具體實現類,我要加的就是一個裝飾類和真正封裝成Json格式的裝飾產品類。具體實現代碼以下:

/**
 * logger decorator for other extension 
 * this class have no specific implementation
 * just for a decorator definition
 * @author jzb
 *
 */
public class DecoratorLogger implements Logger {

public Logger logger;

public DecoratorLogger(Logger logger) {

this.logger = logger;
}
     @Override
public void error(String str) {} @Override public void info(String str) {} //省略其餘默認實現 }
/**
 * json logger for formatted output 
 * @author jzb
 *
 */
public class JsonLogger extends DecoratorLogger {
public JsonLogger(Logger logger) {
        
        super(logger);
    }
        
    @Override
    public void info(String msg) {

        JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.info(result.toString());
    }
    
    @Override
    public void error(String msg) {
        
        JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.error(result.toString());
    }
    
    public void error(Exception e) {

        JSONObject result = composeBasicJsonResult();
        result.put("EXCEPTION", e.getClass().getName());
        String exceptionStackTrace = ExceptionUtils.getStackTrace(e);    
        result.put("STACKTRACE", exceptionStackTrace);
        logger.error(result.toString());
    }
    
    public static class JsonLoggerFactory {
        
        @SuppressWarnings("rawtypes")
        public static JsonLogger getLogger(Class clazz) {

            Logger logger = LoggerFactory.getLogger(clazz);
            return new JsonLogger(logger);
        }
    }
    
    private JSONObject composeBasicJsonResult() {
        //拼裝了一些運行時信息
    }
}

    能夠看到,在JsonLogger中,對於Logger的各類接口,我都用JsonObject對象進行一層封裝。在打印的時候,最終仍是調用原生接口logger.error(string),只是這個string參數已經被咱們裝飾過了。若是有額外的需求,咱們也能夠再寫一個函數去實現。好比error(Exception e),只傳入一個異常對象,這樣在調用時就很是方便了。

    另外,爲了在新老交替的過程當中儘可能不改變太多的代碼和使用方式。我又在JsonLogger中加入了一個內部的工廠類JsonLoggerFactory(這個類轉移到DecoratorLogger中可能更好一些),他包含一個靜態方法,用於提供對應的JsonLogger實例。最終在新的日誌體系中,使用方式以下:

    private static final Logger logger = JsonLoggerFactory.getLogger(Component.class);
    logger.error(string);

    他惟一與原先不一樣的地方,就是LoggerFactory -> JsonLoggerFactory,這樣的實現,也會被更快更方便的被其餘開發者接受和習慣。

相關文章
相關標籤/搜索