У меня есть StrategyName, установленная в appsettings.json, которая представляет имя класса стратегии. Мне нужно получить его экземпляр.

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

Который равен

ITradingStrategy _tradingStrategy = new RsiStrategy(logger);

Можно ли сделать лучше? Работает, но выглядит некрасиво. Поскольку мы знаем название стратегии с самого начала (из appsettings.json), вероятно, должен быть способ получить его более эффективным способом ASP.NET Core. Может, какой-нибудь классный метод расширения, не знаю.

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();
}
-3
nop 22 Фев 2021 в 02:48

1 ответ

Лучший ответ

Ваша проблема может быть решена несколькими способами, и использование отражения будет последним.

Из вашей постановки задачи я полагаю, что у вас есть несколько классифицированных стратегий, реализующих интерфейс ITradingStrategy, и значение конфигурации из файла appsettings.json определяет, какую стратегию использовать.

Один из подходов, которые вы можете использовать здесь, - использовать factory для инициализации соответствующего класса стратегии на основе значения конфигурации.

Ниже приведены фабричный класс и интерфейс, которые будут создавать объект класса стратегии на основе переданного ему имени стратегии.

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

Чтобы приведенный выше код работал, вам необходимо зарегистрировать стратегию, относящуюся к 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, мы можем получить все зарегистрированные экземпляры и найти экземпляр, который имеет правильное имя стратегии.

Итак, метод фабрики 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;
}

Надеюсь, это поможет вам решить вашу проблему.

1
Chetan Ranpariya 22 Фев 2021 в 09:52