2009-11-25 50 views
3

這更像是一個理論問題。如果日誌記錄駐留在主要目的不在日誌中的類中,如果日誌記錄駐留在主要目的不在記錄的類中?

這是一個簡單的接口,用於對數字進行預計算。

public interface ICalculation { 
    public int calculate(int number); 
} 

這裏是ICalculation接口的實現,該接口執行計算並執行一些日誌記錄。我相信這是一個非常實用的方法。除了構造函數接受我們通常不希望在計算域中看到的內容外,內聯日誌記錄可以說是非侵入性的。

public class ReallyIntenseCalculation : ICalculation { 
    private readonly ILogger log; 

    public ReallyIntenseCalculation() : this(new DefaultLogger()) { 
    } 

    public ReallyIntenseCalculation(ILogger log) { 
     this.log = log; 
     log.Debug("Instantiated a ReallyIntenseCalculation."); 
    } 

    public int calculate(int number) { 
     log.Debug("Some debug logging.") 
     var answer = DoTheDirtyWork(number); 
     log.Info(number + " resulted in " + answer); 
     return answer; 
    } 

    private int DoTheDirtyWork(int number) { 
     // crazy math happens here 
     log.Debug("A little bit of granular logging sprinkled in here."); 
    } 
} 

從ReallyIntenseCalculation刪除所有日誌代碼後,該代碼現在有什麼似乎是一個明確的單一職責

public class ReallyIntenseCalculation : ICalculation { 

    public int calculate(int number) { 
     return DoTheDirtyWork(number); 
    } 

    private int DoTheDirtyWork(int number) { 
     // crazy math happens here 
    } 
} 

好吧,所以我們已經刪除了ReallyIntenseCalculation記錄其內部的能力。我們如何找到一種外部化該功能的方法。輸入裝飾者模式。

通過創建一個裝飾ICalculation的類,我們可以將日誌記錄添加到混合中,但這樣做會影響ReallyIncencalCalculation的私有方法中發生的一些更細化的日誌記錄。

public class CalculationLoggingDecorator : ICalculation { 
    private readonly ICalculation calculation; 
    private readonly ILogger log; 

    public CalculationLoggingDecorator(ICalculation calculation, ILogger log) { 
     this.calculation = calculation; 
     this.log = log; 
     log.Debug("Instantiated a CalculationLoggingDecorator using " + calculation.ToString()); 
    } 

    public int calculate(int number) { 
     log.Debug("Some debug logging.") 
     var answer = calculation.calculate(number); 
     log.Info(number + " resulted in " + answer); 
    } 
} 

有一個記錄裝飾者的其他可能的利弊有哪些?

回答

3

我認爲這是值得商榷的。可以做出這樣一個例子,即伐木是單一責任的一部分。

這就是說,我認爲你在考慮cross-cutting concerns,這是aspect-oriented programming處理的東西。事實上,日誌代碼是AOP的典型例子。您可能需要考慮像AOP框架aspect#

這樣做的好處當然是可分解性,重用性和問題分離。

0

你有多少個記錄器?我會讓計算器的構造函數的實現使用Logger單例或Logger工廠方法(這樣記錄或不記錄不是計算器公共API的一部分,但是計算器實現的一部分)私有實例化記錄器實例。

+0

如果它是實現的一部分,那麼它應該是一個構造函數參數。否則,如何看待API的人意圖知道以及如果用戶不想使用不同的記錄器。 – 2011-05-24 01:01:20

3

我同意Jason的看法,這更像是一個橫切關注。

一個函數只應該只執行一個函數,因爲這樣可以使代碼更清晰易讀,並且測試變得更加容易。例如,如果由於日誌文件已滿而導致我的單元測試失敗,這會造成混淆,因爲我沒有在我的方法中測試日誌記錄。

AOP是這裏的最佳選擇,對於.NET PostSharp可能是最佳選擇。

如果AOP不是一個好選擇,那麼你可能想要使用DI,並且你可以注入一個擁有日誌記錄的類的版本來測試流程,但是如果你打算這樣做,那麼你應該確保注入的類只是執行日誌記錄,然後調用沒有日誌記錄的相同函數,所以你在你的類中放置了一個包裝器。

public class ReallyIntenseCalculation : ICalculation { 

    public int calculate(int number) { 
     return DoTheDirtyWork(number); 
    } 

    private int DoTheDirtyWork(int number) { 
     // crazy math happens here 
    } 
} 

public class CalculationLoggingDecorator : ICalculation { 
    ICalculation calculation; 
    ILogger log; 
    public CalculationLogging() { 
     this.calculation = new ReallyIntenseCalculation() ; 
     this.log = SomeLogger(...); 
     log.Debug("Initialized a CalculationLoggingDecorator using " + calculation.ToString()); 
    } 

    public int calculate(int number) { 
     log.Debug("Some debug logging.") 
     var answer = calculation.calculate(number); 
     log.Info(number + " resulted in " + answer); 
    } 
} 

這類似於你的裝飾,但是當你換出的非日誌版本記錄的版本,你已經拋棄了所有的多餘的代碼,並通過測試您可以確保正在使用ReallyIntenseCalculation記錄版本而且這些方法只能定義一次。

這是更多的工作,AOP更可取,但DI可能是一種替代方案。

更新:根據評論。

如果你有多個類擴展這個接口,那麼你可能會有類爆炸,但設計。

AOP將是最好的方法,但這個概念對於一些公司來說是一種難以賣得的東西,而不是DI。

對於每個實現,您最終可能會有兩個類,但您的日誌信息仍然會從這些其他類中移除,每個類都通過DI注入,因此您可以擁有兩個app.config文件,一個包含所有設置日誌類和生產日誌類,以簡化您的生活。第二類只是有日誌記錄和設置信息,所以我相信這不是太多的額外工作,但是你已經失去了精細的日誌記錄。

+0

如果我要引入更多實現ICalculations的類,例如ReallySimpleCalculation和AbsurdlyComplexCalculation,該怎麼辦?上面的代碼不會容易發生類爆炸嗎? – JaredCacurak 2009-11-25 18:47:49