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


  本文為原創博文,轉載請注明出處,侵權必究!

  1、初識裝飾器模式

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

      

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

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

//基礎接口
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

  3、為何使用裝飾器模式

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

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

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

  4、實戰中使用裝飾器模式

    寫這篇博客的初衷也是恰好在工作中使用到了這個模式,覺得非常好用。需求大致是這樣:采用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,這樣的實現,也會被更快更方便的被其他開發者接受和習慣。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM