如何在C#中调用通过Structure Map实例化的class的构造函数

How to call constructor of class which are instantiated through Structure Map in C#

我有一个名为 ILogger 的接口,它基本上包含一些用于日志记录的方法。

Ilogger.cs

public interface ILogger
{   
    void LogError(string message, Exception exception = null);

    void LogMessage(string message);

    void LogValidationError(UploadResult uploadResult);

    void LogValidationError(ValidationResult validationResult);

    void LogProcessingError(string processingError);
}

我有一个实现此接口的 LogHelper class。 LogHelper class 是通过 StructureMap 实例化的

ObjectFactory.Initialize(
    request =>
    {
        request.For<ILogger>().Singleton().Use<LogHelper>();

    });

我有很多 classes 在其构造函数中我只是实例化这个 class 并调用方法来记录信息。 例如:我有一个 class say Dummy1 在其构造函数中我将 LogHelper 实例化为:

public Dummy1()
{
    this.logger = ObjectFactory.GetInstance<ILogger>();     
}

在 LogHelper 中,我有一个主要创建日志文件并将作为参数传递给它的消息写入的方法。

public void LogMessage(string message)
{
    using (var writer = this.GetTextWriter(this.messageFilename))
    {
        writer.WriteLine(message);
    }
}

目前文件名被硬编码到 LogHelper class 的常量 属性 中作为 private string messageFilename = "logs\UserCreationResult.log";

但我希望在实例化 LogHelper 时动态发送文件名。 我想有一个 class 属性 并在实例化 class 时在构造函数中定义 属性 。但是由于 LogHelper class 被实例化为 ObjectFactory.GetInstance<ILogger>()。我无法调用可以在其中传递文件名的构造函数。

您将记录器用作单例,因此您不会在每次调用 ObjectFactory.GetInstance<ILogger>(); 时都创建一个实例,您只是一直获取对同一个记录器实例的引用,该实例创建一次第一次使用。

如果要写入特定目标,那么最好的解决方案是在 Logging 方法中指定目标:

 void LogError(string message, 
               Exception exception = null,
               string destination = /*some adequate defualt value*/);

void LogMessage(string message,
                string destination = /*some adequate defualt value*/);

如果您从期望的方法中同时使用记录器并因此设置不同的目的地,则在具有特定目的地的记录器实例中创建状态信息可能很危险;您最终可能会在不应该记录的地方记录事情。

这提出了一个重要的问题;因为您正在跨应用程序(单例)共享记录器,所以如果有可能以这种方式调用它,请确保它的方法可以安全地并发调用。

不幸的是,您的处理方式有点 self-defeating。您的 class 只知道 ILogger,不知道 ILogger 的任何特定实现。这很好——这意味着该实现可以写入文件、SQL table 或任何内容。

但是如果您的 class 只知道 ILogger 而不知道实现,那么您的 class 如何知道记录器需要文件路径?如果您将 ILogger 中的方法签名更改为包含文件路径,则会发生两件事。

  1. 不可能有任何 ILogger 的实现 写入文件(除非它忽略文件路径,这真的很奇怪。 )
  2. 现在调用记录器的 class 必须知道文件路径。 class 从哪里获取文件路径?它会存储在class中吗?在那种情况下,您最终会得到一个 class,除非它是在可以写入该确切文件路径的计算机上执行的程序集的一部分,否则它不起作用。

相反,记录位置和方式的详细信息应该存在于您的 ILogger 实施中的某个地方。这更接近单一职责原则。调用 ILogger 的 class 不负责决定 ILogger 的工作方式。它不知道,也不想知道。它说 "Here, take this and log it." 记录器实现负责其余部分。

我建议完全废弃静态 ObjectFactory 并使用容器来解析和创建所有 classes,包括记录器和依赖于的 classes它,但它太宽泛了,并没有真正的帮助。 (它已被弃用,因为它是一个糟糕的模式。它甚至不在最新版本的 StructureMap 中。)

以上都是推荐。在此之后,我提供了一个不太值得推荐的选项,但需要较少的更改并使您的 classes 不知道文件路径,因为请永远不要这样做。

一个选择 - 折衷方案 - 可能是注册 ILogger 的不同命名实现。您可以将您的记录器 class 修改为如下所示:

public class FileLogger : ILogger
{
    private readonly string _filePath;

    public FileLogger(string filePath)
    {
        _filePath = filePath;
    }
}

现在您可以创建该记录器的多个实例 class,将不同的文件路径传递给每个实例。这样它就不是静态的 属性,它限制您只有一个文件路径。

然后你可以像这样注册你的实现。

ObjectFactory.Initialize(
    request =>
    {
        request.For<ILogger>().Singleton()
            .Use<FileLogger>(() => new FileLogger("some path")).Name = "LoggerOne";

        request.For<ILogger>().Singleton()
             .Use<FileLogger>(() => new FileLogger("some other path")).Name = "LoggerTwo";

    });

现在你的 class 可以说 它想要哪个 记录器,像这样:

var logger = ObjectFactory.GetNamedInstance<ILogger>("LoggerOne");

但也请不要真的那样做。这比我在这里能真正详细描述的要多得多,但是看看依赖注入,这样你的 classes 真的只知道 ILogger 而不知道或不关心他们得到和不使用哪个实现告诉它如何完成它的工作。