我是reading關於單身模式的缺點。 Logging應用程序在許多論壇中建議使用有效的單例。我想知道爲什麼這是該模式的有效用法。我們不是在整個應用程序中維護內存中的狀態信息嗎?爲什麼使用singleton進行應用程序日誌記錄?
爲什麼不直接使用的功能:
class Logger
{
public static void Log(string message)
{
//Append to file
}
}
我是reading關於單身模式的缺點。 Logging應用程序在許多論壇中建議使用有效的單例。我想知道爲什麼這是該模式的有效用法。我們不是在整個應用程序中維護內存中的狀態信息嗎?爲什麼使用singleton進行應用程序日誌記錄?
爲什麼不直接使用的功能:
class Logger
{
public static void Log(string message)
{
//Append to file
}
}
這是更好地宣告接口:
interface ILogger
{
public void Log(string message);
}
然後實現特定類型的記錄
class FileLogger : ILogger
{
public void Log(string message)
{
//Append to file
}
}
class EmptyLogger : ILogger
{
public void Log(string message)
{
//Do nothing
}
}
,並在必要時注入。您將在測試中注入EmptyLogger
。使用單例會使測試變得更加困難,因爲您也必須將測試結果保存到文件中。如果您想測試班級是否製作正確的日誌條目,則可以使用模擬並定義期望值。
關於注射:
public class ClassThatUsesLogger
{
private ILogger Logger { get; set; }
public ClassThatUsesLogger(ILogger logger) { Logger = logger }
}
ClassThatUsesLogger需要FileLogger在生產代碼:
classThatUsesLogger = new ClassThatUsesLogger(new FileLogger());
在測試中,它需要EmptyLogger:
classThatUsesLogger = new ClassThatUsesLogger(new EmptyLogger());
你在不同的場景不同的注射記錄器。有更好的方法來處理注射,但你必須做一些閱讀。
編輯
記住,你仍然可以使用單在你的代碼,爲其他人則建議,但你應該躲在接口其使用鬆動類和具體實施採伐之間的依賴關係。
我不知道你是指當你問的狀態信息留在記憶什麼,但一個理由看好單在靜態的記錄是單仍然可以讓你既
(1)程序抽象(ILogger)和
(2)通過實踐依賴注入來堅持依賴倒置原則。
你不能注入你的靜態測井方法作爲一個依賴(除非你想傳遞什麼樣Action<string>
無處不在),但你可以傳遞一個單身的對象,你可以編寫單元測試時,通過不同的實現像NullLogger
。
要回答「爲什麼不只是使用函數」:此代碼在多線程日誌記錄中工作不正確。如果兩個線程嘗試寫入相同的文件,則會拋出異常。這就是爲什麼使用singleton進行日誌記錄的原因。在這個解決方案中,我們有一個線程安全的單例容器,其他線程安全地將消息(日誌)推入容器。容器(總是線程安全隊列)將消息/日誌逐個寫入文件/ db/etc中。
單例記錄器實現允許您輕鬆控制記錄刷新到磁盤或db的頻率。如果您有多個記錄器實例,那麼它們都可能試圖同時寫入,這可能會導致衝突或性能問題。單身人士允許這種管理方式,以便您在平靜的時間內只需刷新店面,並保持所有消息的順序。
我同意@Jay和LukLed,您應該將您的記錄器定義爲允許控制反轉的接口。它會讓你的測試更容易。 – 2012-03-16 02:52:09
在大多數情況下,不推薦使用Singleton設計模式,因爲它是一種全局狀態,隱藏了依賴關係(使API不太明顯),也很難測試。
日誌記錄不是這些情況之一。這是因爲日誌記錄不會影響代碼的執行。也就是說,如這裏說明:http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:
您的應用程序不表現任何不同給定 logger是否被啓用。這裏的信息以一種方式流動:從您的 應用程序進入記錄器。
雖然你可能還是不想使用Singleton模式。至少不是。這是因爲沒有理由強制記錄器的單個實例。如果你想擁有兩個日誌文件,或者兩個記錄器的行爲不同並且用於不同的目的?
因此,您真正想要的記錄器就是在任何需要它的地方都能輕鬆訪問。基本上,伐木是一種特殊的情況,最好的辦法是讓它可以在全球範圍內使用。
最簡單的方法是簡單地在你的應用程序包含記錄器的實例的靜態字段:
public final static LOGGER = new Logger();
或者,如果你的記錄是由工廠創建:
public final static LOGGER = new LoggerFactory().getLogger("myLogger");
或者如果您的記錄器是由DI容器創建的:
public final static LOGGER = Container.getInstance("myLogger");
您可以通過配置文件來配置您的記錄器實現,您可以在進行測試時將其設置爲「mode = test」,以便這些情況下的記錄器可以相應地執行操作, ,或者登錄到控制檯。
public final static LOGGER = new Logger("logConfig.cfg");
您也可以使記錄的行爲在運行時配置。因此,在運行測試時,您可以簡單地將其設置爲:LOGGER.setMode(「test」);或者,如果您不進行靜態最終測試,您可以簡單地在測試設置中用測試記錄器或模擬記錄器替換靜態記錄器。
東西稍微愛好者,你可以做到這一點是接近Singleton模式,但並不完全是:
public class Logger
{
private static Logger default;
public static getDefault()
{
if(default == null)
{
throw new RuntimeException("No default logger was specified.");
}
return default;
}
public static void setDefault(Logger logger)
{
if(default != null)
{
throw new RuntimeException("Default logger already specified.");
}
default = logger;
}
public Logger()
{
}
}
public static void main(String [] args)
{
Logger.setDefault(new Logger());
}
@Test
public void myTest()
{
Logger.setDefault(new MockedLogger());
// ... test stuff
}
我不明白的注入部分。你能否詳細說明一下? – Nemo 2012-03-16 02:42:52
一個需要記錄器但不創建記錄器的類有一個在類的構造過程中(或通常通過構造器)傳入的類被稱爲將記錄器*注入到類中。 http://en.wikipedia.org/wiki/Dependency_injection – 2012-03-16 02:54:38