通过名称获取 class 的实例

Get an instance of a class by its name

我在 appsettings.json 中设置了 StrategyName,它表示策略的名称 class。我需要获取它的一个实例。

ITradingStrategy _tradingStrategy = StrategyUtils.GetStrategyInstance(logger, _tradeOptions.StrategyName)

等于

ITradingStrategy _tradingStrategy = new RsiStrategy(logger);

是否可以用更好的方式制作?它有效但看起来很难看。由于我们一开始就知道策略名称(来自appsettings.json),所以应该有一种更好的ASP.NET核心方式来获取它。也许一些很酷的扩展方法,我不知道。

appsettings.json

{
  "TradeConfiguration": {
    "StrategyName": "RsiStrategy",
    ...
  }
}

代码

public class LiveTradeManager : ITradeManager
{
    private readonly ILogger _logger;
    private readonly IExchangeClient _exchangeClient;
    private readonly ITradingStrategy _tradingStrategy;
    private readonly ExchangeOptions _exchangeOptions;
    private readonly TradeOptions _tradeOptions;

    public LiveTradeManager(ILogger logger, IConfiguration configuration, IExchangeClient exchangeClient)
    {
        _logger = logger;
        _exchangeClient = exchangeClient;
        _exchangeOptions = configuration.GetSection("ExchangeConfiguration").Get<ExchangeOptions>();
        _tradeOptions = configuration.GetSection("TradeConfiguration").Get<TradeOptions>();
        _tradingStrategy = StrategyUtils.GetStrategyInstance(logger, _tradeOptions.StrategyName); // This is the questioned line
    }
}

public static ITradingStrategy GetStrategyInstance(ILogger logger, string strategyName)
{
    var strategyType = Assembly.GetAssembly(typeof(StrategyBase))
        .GetTypes().FirstOrDefault(type => type.IsSubclassOf(typeof(StrategyBase)) && type.Name.Equals(strategyName));

    if (strategyType == null)
    {
        throw new ArgumentException($"The strategy \"{strategyName}\" could not be found.", nameof(strategyName));
    }

    var strategy = Activator.CreateInstance(strategyType, logger) as ITradingStrategy;

    return strategy;
}

// Strategies
public interface ITradingStrategy
{
    IReadOnlyList<TradeAdvice> Prepare(IReadOnlyList<OHLCV> candles);
}

public abstract class StrategyBase : ITradingStrategy
{
    private readonly ILogger _logger;

    protected StrategyBase(ILogger logger)
    {
        _logger = logger;
    }
    
    public abstract IReadOnlyList<TradeAdvice> Prepare(IReadOnlyList<OHLCV> candles);
}

public class RsiStrategy : StrategyBase
{
    private readonly ILogger _logger;

    public RsiStrategy(ILogger logger) : base(logger)
    {
        _logger = logger;
    }
    
    public override IReadOnlyList<TradeAdvice> Prepare(IReadOnlyList<OHLCV> candles)
    {
        ... _logger.Information("Test");
    }
}

// Main
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config
                .AddJsonFile("appsettings.json")
                .AddEnvironmentVariables();
            })
            .ConfigureServices((hostingContext, services) =>
            {
                services.AddSingleton(
                    Log.Logger = new LoggerConfiguration()
                        .ReadFrom.Configuration(hostingContext.Configuration)
                        .CreateLogger());

                services.AddSingleton<ITradeManager, LiveTradeManager>();
                services.AddSingleton<IExchangeClient, BinanceSpotClient>();
                
                services.AddHostedService<LifetimeEventsHostedService>();
            })
            .UseSerilog();
}

您的问题可以通过多种方式解决,使用反射将是最后一种。

根据你的问题陈述,我认为你有多个策略 classed 实现 ITradingStrategy 接口,你来自 appsettings.json 文件的配置值决定使用哪个策略。

您可以在此处使用的一种方法是使用工厂根据配置值初始化适当的策略class。

以下是工厂 class 和接口,它们将根据传递给它的策略名称创建 Strategy class 对象。

public interface IStrategyFactory
{
    ITradingStrategy GetStrategy(string strategyName);
}

public class StrategyFactory : IStrategyFactory
{
    private IServiceProvider _serviceProvider;
    public StrategyFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;

    }
    public ITradingStrategy GetStrategy(string strategyName)
    {
        switch (strategyName)
        {
            case "Rsi":
                // Resolve RsiStrategy object from the serviceProvider.
                return _serviceProvider.GetService<RsiStrategy>();
            case "Dmi":
                // Resolve DmiStrategy object from the serviceProvider.
                return _serviceProvider.GetService<DmiStrategy>();
            default:
                return null;
        }
    }
}

现在可以在控制器中使用此策略并通过传递从配置中检索的策略名称来调用其 GetStrategy 方法。

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    // Strategy factory.
    private IStrategyFactory _strategyFactory;
    // Configuration
    private IConfiguration _configuration;

    public HomeController(ILogger<HomeController> logger, IConfiguration configuration, IStrategyFactory strategyFactory)
    {
        _logger = logger;
        _strategyFactory = strategyFactory;
        _configuration = configuration;
    }

    public IActionResult Index()
    {
        // Get Configuration value "StrategyName" from configuration.
        // In your case this will be your own custom configuration.
        var strategyName = _configuration.GetValue<string>("StrategyName");

        // Pass strategyName to GetStrategy Method.
        var strategy = _strategyFactory.GetStrategy(strategyName);

        // Call Prepare method on the retrieved strategy object.
        ViewBag.PreparedList = strategy.Prepare(new List<OHLCV>());
        return View();
    }
}

要使上述代码正常工作,您需要将策略class注册到 serviceCollection。

services.AddSingleton<RsiStrategy>();
services.AddSingleton<DmiStrategy>();

还有 StrategyFactory。

services.AddSingleton<IStrategyFactory, StrategyFactory>();

编辑

根据您在下面的评论,您需要能够解析策略类型,而无需像创建新类型时那样在 DI 中注册它们的额外开销,也无需在工厂中进行更改。

你需要为此使用反射。使用反射,您可以确定要在 DI 中注册的类型。如下

//Get all the types which are inheriting from StrategyBase class from the assembly.
var strategyTypes = Assembly.GetAssembly(typeof(StrategyBase))
    ?.GetTypes()
    .Where(type => type.IsSubclassOf(typeof(StrategyBase)));

if (strategyTypes != null)
{
    //Loop thru the types collection and register them in serviceCollection.
    foreach (var type in strategyTypes)
    {
        services.Add(new ServiceDescriptor(typeof(StrategyBase), type, ServiceLifetime.Singleton));
    }
}

通过上面的代码,所有继承自StrategyBase的类型都注册到了serviceCollection中。现在使用 serivceProvider 我们可以获得所有已注册的实例并查找具有正确 strategyName 的实例。

因此工厂的 GetStrategy 方法将如下所示。

public ITradingStrategy GetStrategy(string strategyName)
{
    var strategies = _serviceProvider.GetServices<StrategyBase>();

    var strategy = strategies.FirstOrDefault(s => s.GetType().Name == strategyName);

    if (strategy == null)
    {
        throw new ArgumentException($"The strategy \"{strategyName}\" could not be found.", nameof(strategyName));
    }

    return strategy;
}

希望本文能帮助您解决问题。