這兩種模式的相似度極高,作用也類似,都是對已有的類進行包裝,以添加新的控制(代理模式)和功能(裝飾者模式),其實這兩點也沒有嚴格區分。
兩種設計模式的重點在於,已有的類(被代理、被裝飾)與新類(代理類、裝飾類)都實現同一接口,通過接口調用新類會和調用已有的類相同。
設計模式中常說使用“組合”優先於“繼承”。對於想要改變一個寫好的類中的某些功能,一般情況下使用繼承的靈活性不如組合。繼承的某些缺點:單繼承(多繼承也面臨一些問題)、破壞封裝(子類可能改變某些細節),父類的改變對子類可能有影響。“組合”的方式將需要被修改或加強的類作為新類的類成員,可以通過添加多個類成員以得到組合多種功能的效果。
靜態代理模式 (static proxy)
靜態代理的思想:將被代理類作為代理類的成員,通過代理類調用被代理類的函數,並添加新的控制。包裝類與被包裝類實現同一接口,使得使用時的代碼一致。
應用:已經有一個日志記錄器LoggerSubject,需要對writeLog()函數的前后進行某些操作(如初始化、異常處理等),使用Proxy類間接調用LoggerSubject.writeLog()實現新控制操作的添加。
實現如下
interface Logger {
void writeLog();
}
// 被代理類 class LoggerSubject implements Logger{
@Override
public void writeLog(){
System.out.println("writeLog by LoggerSubject");
}
}
// 代理類 class Proxy implements Logger{
Logger logger;
// 與裝飾者模式的主要區別位置 // 代理模式一般要求和原來的類行為一致,因此構造函數不傳入對象 Proxy(){
this.logger = new LoggerSubject();
}
@Override
public void writeLog(){
System.out.println("logger write before");
logger.writeLog();
System.out.println("logger write after");
}
}
public class StaticProxy {
private static void write(Logger logger){
logger.writeLog();
}
public static void main(String []argvs){
Logger logger = new Proxy();
// 還可能出現下面的嵌套 //Logger logger = new Logger3(new Proxy(new LoggerSubject())); write(logger);
}
}
裝飾者模式
主要用於給一個類添加新功能
主要思想:被裝飾類作為類成員被調用,為了使裝飾類能和被裝飾類一樣的使用,兩者實現相同的接口。
通過構造函數傳入被包裝類,能夠自由組合裝飾,如下面的最后的使用。
interface Logger {
public void writeLog();
}
class BaseLogger implements Logger {
public void writeLog(){
System.out.println("writeLog");
}
}
class DecorationLogger implements Logger{
private Logger logger;
DecorationLogger(Logger logger){
this.logger = logger;
}
@Override
public void writeLog(){
logger.writeLog();
System.out.println("Decoration");
}
}
class DecorationLogger2 implements Logger{
private Logger logger;
DecorationLogger2(Logger logger){
this.logger = logger;
}
@Override
public void writeLog(){
logger.writeLog();
System.out.println("Decoration2");
}
}
public class Decoration {
public static void main(String []argvs){
Logger logger = new DecorationLogger2(new DecorationLogger(new BaseLogger()));
logger.writeLog();
Logger logger1 = new DecorationLogger(new DecorationLogger2(new BaseLogger()));
logger1.writeLog();
Logger logger2 = new DecorationLogger(new BaseLogger());
logger2.writeLog();
}
}
缺點和注意:包裝的自由組合的靈活性可能導致測試的困難,注意組合可能帶來的bug。
靜態代理與裝飾者模式的主要區別
- 原則上的區別,代理為了控制對某個函數前后的操作,而裝飾着模式是為了添加某一操作(其實目標沒差太遠)
- 實現上的區別,代理模式的類一般和被代理類的操作一致,因此構造函數一般不傳入類對象,使用時的不同如下: Logger logger = new Proxy(); // 代理模式 (為了讓Proxy的行為像Logger) Logger logger = new DecorateLogger(new Logger()); // 裝飾者模式,還可以有更多層
個人吐槽
很多博客里再提高一點深度的說法:靜態代理在編譯時已經確定代理的具體對象,裝飾模式是在運行動態的構造。(聽起來有道理,其實就是要不要在構造函數中傳入對象的問題)
如果需要對一個類的眾多派生類做代理,按照標准的說法豈不是對每一個派生類都需要寫一個靜態代理類?? 感覺上如果不要求代理類和被代理類在構建對象時一致(或者也給被代理類一個構造函數傳入),從構造函數傳入被代理類能讓代理類更加靈活的處理實現接口的各種類。因此,此處還是建議根據具體情況活用;當然,更建議直接用動態代理。