2012-03-16 103 views
5

我是reading关于单身模式的缺点。 Logging应用程序在许多论坛中建议使用有效的单例。我想知道为什么这是该模式的有效用法。我们不是在整个应用程序中维护内存中的状态信息吗?为什么使用singleton进行应用程序日志记录?

为什么不直接使用的功能:

class Logger 
{ 
    public static void Log(string message) 
    { 
     //Append to file 

    } 
} 

回答

2

这是更好地宣告接口:

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()); 

你在不同的场景不同的注射记录器。有更好的方法来处理注射,但你必须做一些阅读。

编辑

记住,你仍然可以使用单在你的代码,为其他人则建议,但你应该躲在接口其使用松动类和具体实施采伐之间的依赖关系。

+0

我不明白的注入部分。你能否详细说明一下? – Nemo 2012-03-16 02:42:52

+0

一个需要记录器但不创建记录器的类有一个在类的构造过程中(或通常通过构造器)传入的类被称为将记录器*注入到类中。 http://en.wikipedia.org/wiki/Dependency_injection – 2012-03-16 02:54:38

1

我不知道你是指当你问的状态信息留在记忆什么,但一个理由看好单在静态的记录是单仍然可以让你既
(1)程序抽象(ILogger)和
(2)通过实践依赖注入来坚持依赖倒置原则

你不能注入你的静态测井方法作为一个依赖(除非你想传递什么样Action<string>无处不在),但你可以传递一个单身的对象,你可以编写单元测试时,通过不同的实现像NullLogger

3

要回答“为什么不只是使用函数”:此代码在多线程日志记录中工作不正确。如果两个线程尝试写入相同的文件,则会抛出异常。这就是为什么使用singleton进行日志记录的原因。在这个解决方案中,我们有一个线程安全的单例容器,其他线程安全地将消息(日志)推入容器。容器(总是线程安全队列)将消息/日志逐个写入文件/ db/etc中。

0

单例记录器实现允许您轻松控制记录刷新到磁盘或db的频率。如果您有多个记录器实例,那么它们都可能试图同时写入,这可能会导致冲突或性能问题。单身人士允许这种管理方式,以便您在平静的时间内只需刷新店面,并保持所有消息的顺序。

+0

我同意@Jay和LukLed,您应该将您的记录器定义为允许控制反转的接口。它会让你的测试更容易。 – 2012-03-16 02:52:09

1

在大多数情况下,不推荐使用Singleton设计模式,因为它是一种全局状态,隐藏了依赖关系(使API不太明显),也很难测试。

日志记录不是这些情况之一。这是因为日志记录不会影响代码的执行。也就是说,如这里说明:http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html

您的应用程序不表现任何不同给定 logger是否被启用。这里的信息以一种方式流动:从您的 应用程序进入记录器。

虽然你可能还是不想使用Singleton模式。至少不是。这是因为没有理由强制记录器的单个实例。如果你想拥有两个日志文件,或者两个记录器的行为不同并且用于不同的目的?

因此,您真正想要的记录器就是在任何需要它的地方都能轻松访问。基本上,伐木是一种特殊的情况,最好的办法是让它可以在全球范围内使用。

  1. 最简单的方法是简单地在你的应用程序包含记录器的实例的静态字段:

    public final static LOGGER = new Logger(); 
    
  2. 或者,如果你的记录是由工厂创建:

    public final static LOGGER = new LoggerFactory().getLogger("myLogger"); 
    
  3. 或者如果您的记录器是由DI容器创建的:

    public final static LOGGER = Container.getInstance("myLogger"); 
    
  4. 您可以通过配置文件来配置您的记录器实现,您可以在进行测试时将其设置为“mode = test”,以便这些情况下的记录器可以相应地执行操作, ,或者登录到控制台。

    public final static LOGGER = new Logger("logConfig.cfg"); 
    
  5. 您也可以使记录的行为在运行时配置。因此,在运行测试时,您可以简单地将其设置为:LOGGER.setMode(“test”);或者,如果您不进行静态最终测试,您可以简单地在测试设置中用测试记录器或模拟记录器替换静态记录器。

  6. 东西稍微爱好者,你可以做到这一点是接近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 
    } 
    
相关问题