本文為原創博文,轉載請注明出處,侵權必究!
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,這樣的實現,也會被更快更方便的被其他開發者接受和習慣。
