在不同程序集中重新实现一个类方法

huangapple go评论58阅读模式
英文:

Reimplement a class method in a different assembly

问题

我有一个配置类,它从不同的源加载配置属性值,并允许以编程方式设置属性值。

目前,它尝试按以下顺序加载配置值:

  • 环境变量
  • App.config 文件
  • settings.json 文件
  • 使用默认值并允许用户在他们自己的代码中更改属性

由于大多数用户都以编程方式设置配置,并且我也想尽量减少对 Microsoft.Extensions.Configuration 的依赖,我想将前三种情况移到一个不同的程序集中,以便用户明确依赖于它们,如果他们想从 App.config 文件中加载配置属性,例如。

理想情况下,我希望对用户来说这个更改是透明的,这样他们不必更改其现有的代码。

目前,我的配置类在其构造函数中执行类似以下的操作:

public Config()
{
    InitializeDefaults();
    ReadEnvironmentVariables();
    ReadAppConfig();
}

是否有一种方法可以在我的现有程序集中提供ReadEnvironmentVariablesReadAppConfig的空实现,并在新程序集中替换它为实际的实现,以便如果应用程序有新程序集作为依赖项,它将使用实际的实现,否则将默认为空方法?

我可能可以通过在新程序集中定义一个常量并执行以下操作来做到这一点:

#if CONFIG_EXTENSION
    ReadEnvironmentVariables();
    ReadAppConfig();
#endif

但我想知道是否有更好的方法来实现这一点。

编辑以澄清:我没有可用的依赖容器,因此无法使用依赖注入,因为该类是库的一部分,我不希望假设用户使用依赖容器。

英文:

I have a configuration class that loads configuration property values from different sources and allows to set property values programmatically.

At the moment, it tries loading configuration values in this order:

  • Environment variables
  • App.config file
  • settings.json file
  • Use the defaults and allow users to change properties in their own code

Since most users are setting the configuration programmatically and I also want to get rid of the Microsoft.Extensions.Configuration dependency as much as possible, I want to move the first three cases in a different assembly, to be depended on explicitly if users want to load configuration properties, for instance, from an App.config file.

Ideally, I'd like the change to be transparent for users, so that they don't have to change their existing code.

What my config class does at the moment is, in its constructor, something like:

public Config()
{
    InitializeDefaults();
    ReadEnvironmentVariables();
    ReadAppConfig();
}

Is there a way to provide an empty implementation of ReadEnvironmentVariables and ReadAppConfig in my existing assembly and replace it with the actual implementation in the new assembly, so that if the application has the new assembly in its dependencies it uses the actual implementation, otherwise it defaults to an empty method?

I can probably do this by #defining a constant in my new assembly and doing

#if CONFIG_EXTENSION
    ReadEnvironmentVariables();
    ReadAppConfig();
#endif

But i was wondering if there was a better way to achieve this.

Edit to clarify: I don't have a dependency container available, so I can't use dependency injection, because the class is part of a library and I don't want to assume users use a dependency container.

答案1

得分: 2

.NET依赖注入是解决这个问题的一个好方法。基本上你不会对一个类进行编程,而是对一个接口进行编程,并在你的类中注入一个实现。

public interface IConfigFactory
{
    IConfig GetConfiguration();
}

public interface IConfig
{
    bool TryGetValue<T>(string key, out T value);
}

然后在你的一个类中像这样注入它:

public class ExamplePrintService
{
    private readonly IConfig _config;

    public ExamplePrintService(IConfigFactory configFactory)
    {
        _config = configFactory.GetConfiguration();
    }

    public void Print(IDocument doc)
    {
        double leftMargin = _config.TryGetValue<double>("leftmargin", out var lm)
            ? lm
            : 9.0;
        ...
    }
}

如果你使用IoC容器,你可以将IConfigFactory的实现注册为单例。在应用程序启动时,你可以选择注册不同的实现。应用程序的其余部分不需要关心使用哪种实现或者如何检索配置。

正如@Ralf指出的,你可以在没有IoC容器的情况下使用这种模式。IoC容器可以在一个集中的地方配置,然后自动注入依赖项。没有它,你将不得不手动注入依赖项。

你也可以有不同层次的抽象。例如,一个IPrintConfig接口可以暴露具体的配置属性,并依赖于上面展示的抽象接口。

英文:

A good way to solve this problem is to use .NET dependency injection. Basically you do not program against a class, but against an interface and inject an implementation in your classes.

public interface IConfigFactory
{
    IConfig GetConfiguration();
}

public interface IConfig
{
    bool TryGetValue<T>(string key, out T value);
}

IConfigFactory.GetConfiguration() is then responsible to load the configuration only at the first call and return the same instance at subsequent calls. You could also add a Reset() method forcing to reload a fresh version of the config at the next call.

Then in one of your classes you inject it like this

public class ExamplePrintService
{
    private readonly IConfig _config;

    public ExamplePrintService (IConfigFactory configFactory)
    {
        _config = configFactory.GetConfiguration();
    }

    public void Print(IDocument doc)
    {
        double leftMargin = _config.TryGetValue<double>("leftmargin", out var lm)
            ? lm
            : 9.0;
        ...
    }
}

If you are using an IoC container, you would then register an implementation of IConfigFactory as singleton. At the start of the application you can then choose to register different implementations. The rest of the application does not need to care about which implementation is used or how the config is retrieved.

As @Ralf pointed out, you can use this pattern without an IoC container. An IoC container can be configured in a central place and then injects the dependencies automatically. Without it you will have to inject the dependencies manually.

You could also have different levels of abstraction. E.g. an IPrintConfig interface could expose concrete configuration properties an rely on the abstract interfaces shown above.

答案2

得分: 0

只翻译请求的部分,如下:

  • 你是否使用 DI 容器或任何形式的 DI(依赖注入)?
  • 消费者如何获取 Config 类的实例?
  • 如果你没有某种形式的 DI 支持,你可以考虑以下方式:
    • 在 Config 类中添加一个 Action 委托类型的私有字段
    • 初始化 Action 为默认实现,例如,可能调用现有的 ReadEnvironmentVariables() 和 ReadAppConfig() 方法
    • 替换对现有方法的调用,改为调用 Action,即添加了一层间接层
    • 添加一个接受 Action 的构造函数,并设置私有字段
    • 在新的程序集中创建一个静态方法,该方法通过注入不同的 Action 创建 Config 对象

具体细节可能有所不同,但希望这个示例展示了基本思想。

英文:

Are you using a DI container or any form of DI (Dependency Injection)?

How does a consumer get an instance of the Config class?

If you don't have some form of DI support you might consider the following:

  • Add a private field in the Config class that is of the Action delegate type
  • Initialize the Action to a default implementation, e.g. maybe calling the existing ReadEnvironmentVariables() and ReadAppConfig() methods
  • Replace calling the existing methods with invoking the Action, i.e. a level of indirection has been added
  • Add a constructor that accepts an Action and sets the private field
  • In the new assembly create a static method that returns a Config object created via the constructor that injects a different Action

The exact details can vary but hopefully this example shows the basic idea.

huangapple
  • 本文由 发表于 2023年4月6日 20:49:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/75949723.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定