Блог им. WinterMute
Привет всем! В предыдущих статьях я описывал свой тестер, разработанный на C#, и, несколько раз подчёркивал, что переключение между двумя режимами (тестирование/торговля) может быть простым. Код стратегий не должен зависеть от того, кто поставщик маркет-даты и куда уходят заявки – в тестовую базу или на сервер брокера. Конечно, это лишь один из подходов, и кому-то он покажется странным, но, главное его достоинство заключается в том, что тестирование приближается к реальности, что даёт более достоверные результаты. Вопрос в следующем: как, имея один и тот же код, получать разные по функциональности программы? Один из вариантов – использовать инверсию управления и внедрение зависимостей! Об этом сегодня и пойдёт речь.
Приведу пример нехорошего (иногда, говорят – с запашком) кода:
class Strategy { public Strategy() { var mgr = new TestOrderManadger(); mgr.PlaceOrder(...); } }
Здесь плохо то, что класс Strategy зависит от класса TestOrderManadger. В такой реализации нельзя начать использовать какой-нибудь другой менеджер заявок (AnotherOrderManadger) без перекомпиляции библиотеки с классом Strategy. Тем более тут нарушается принцип единства ответственности – класс Strategy, помимо своей прямой обязанности, также, создаёт внутри себя зависимости. Чтобы исправить ситуацию, можно использовать интерфейсы:
interface IOrderMandger { void PlaceOrder(); } class TestOrderManadger : IOrderMandger { public void PlaceOrder(){} } class Strategy { public Strategy(IOrderMandger orderMandger) { var mgr = orderMandger; mgr.PlaceOrder(...); } }
И, где-то далее в коде:
Strategy mySuperStrategy = new Strategy(new TestOrderManadger());
В этом варианте Strategy зависит не от конкретного класса TestOrderManadger, а от абстракции (от интерфейса IOrderMandger), и не важно, какая будет реализация. Класс Strategy больше не занимается построением зависимостей (больше не создаёт внутри себя объекты). Это и есть инверсия управления (Inversion of Control, IoC) – модули верхнего уровня не должны зависеть от модулей нижнего уровня. Но всё-таки у такого кода ещё есть “запашок”. Проблема в строчке:
Strategy mySuperStrategy = new Strategy(new TestOrderManadger());
Точнее, в операторе new. Используя оператор new, мы всё ещё явно создаём конкретные объекты. Хорошо бы кому-то делегировать, всё, что связанно с построением зависимостей и с созданием объектов. В идеале, в коде, вообще не должно быть оператора new (за исключением простых, скалярных типов). Механизм, реализующий это, и называется “внедрение зависимостей” (Dependency Injection, DI). Где-то, в самом начале программы (или даже во внешнем файле) регистрируются все интерфейсы и их реализации, а дальше, разрешение зависимостей и создание объектов берёт на себя IoC контейнер.
Под .Net есть целая куча IoC контейнеров, я использую Autofac, но принципы регистрации и применения у всех схожи. Сначала, нужно настроить контейнер – указать, какой класс реализует тот или иной интерфейс. Вот, примерно так выглядит настройка:
static class AutofacContainer { public static IContainer ConfigureContainer( bool isTestMode) { ContainerBuilder builder = GetContainerBuilder(); if (isTestMode) { builder .RegisterType<TestOrderManadger() .As<IOrderManadger>() .SingleInstance(); ... } else { builder .RegisterType<AnotherOrderManadger() .As<IOrderManadger>() .SingleInstance(); ... } return builder.Build(); } }
Смысл в том, что от переданного в конфигуратор аргумента (isTestMode) зависит, будет ли программа работать в режиме бэктеста или в режиме реальных торгов. Таким же образом регистрируются все необходимые классы: сервисы, репозитории и пр., и даже здесь не используется оператор new, и нигде в коде. В контейнере можно провести дополнительные настройки, связанные с механизмом созданием объекта и его жизненным циклом. Например, в коде выше, команда SingleInstance(), указывает, что объект должен быть создан в единственном экземпляре.
Теперь, после настройки контейнера, код, приведенный чуть выше:
Strategy mySuperStrategy = new Strategy(new TestOrderManadger());
Можно заменить на:
var container = AutofacContainer .ConfigureContainer(isTestMode: false); Strategy mySuperStrategy = container.Resolve<Strategy>();
Контейнер сам подставит в конструктор класса Strategy нужные зависимости (нужную реализацию интерфейса IOrderMandger).
В предыдущих постах, я рассказывал, что использую фабрику стратегий – класс, который по некоему ID стратегии выдаёт ту или иную реализацию интерфейса IStrategy. Чтобы это заработало, в IoC контейнере, нужно зарегистрировать все стратегии и присвоить им уникальный идентификатор:
builder.RegisterType<MySuperStrategy>() .Keyed<IStrategy>(1); builder.RegisterType<AnoterSimpleStrategy>() .Keyed<IStrategy>(2); builder.RegisterType<MagicStrategy>() .Keyed<IStrategy>(3); ...
И, код самой фабрики:
public class StrategyFactory { private IComponentContext container; ... public IStrategy CreateStrategy( int strategyID) { IStrategy strategy = container .ResolveKeyed<IStrategy>(strategyID); return strategy; } }
Фабрика берёт на себя ответственность за создание объектов стратегий. По сути, это небольшая обёртка над контейнером. Тут же могут быть какие-то дополнительные настройки стратегии.
Сам контейнер инициализируется в момент старта программы, и, в зависимости от значения переменной isTestMode, будут использоваться те или иные объекты, и от этого будет зависеть режим работы – тестовый или торговый.
Теперь, хотел сказать по поводу логгирования. По сути, это протоколирование того, что программа делает. В простейшем случае можно было бы выводить сообщения в консоль. Но, если использовать отдельную библиотеку можно получить ряд преимуществ: настраиваемый формат лога, вывод в отдельный фай (или архив, БД), настраиваемые уровни лога (критическая ошибка, просто сообщение и т.п.), поддержка многопоточности. Я использую NLog под .Net, но вариантов много (вплоть до аспектно-ориентированных логгеров, реализующих сквозную функциональность).
Вот так может выглядеть настройка логгера внутри IoC контейнера:
var fileName = @"C:\Logfile\Log.txt"; var config = new LoggingConfiguration(); var fileTarget = new FileTarget(); config.AddTarget("file", fileTarget); fileTarget.FileName = fileName; fileTarget.Layout = @"${newline}${level} ${longdate} ${callsite} ${message} ${exception:format=tostring} ${newline}"; var rule = new LoggingRule("*", LogLevel.Debug, fileTarget); config.LoggingRules.Add(rule); LogManager.Configuration = config; builder .RegisterInstance<ILogger(LogManager .GetLogger("Logger")) .SingleInstance();
Далее, в коде программы, можно осуществлять логгирование:
try { ... } catch (Exception e) { logger.Error(e, "Ошибка работы программы"); }
При этом, если произойдёт ошибка, и программа попадёт в блок catch, в лог запишется подробная информация об этой ошибке: когда она произошла, код ошибки, в каком модуле и т.д.
Контроль за ботами и за работой программы в целом – это важная составляющая архитектуры системы. На первых парах лучше перестараться, чем потерять деньги на какой-нибудь ерунде. Важно использовать блоки try..catch и логгирование. Также, я думаю, в серьёзных системах есть независимая от ботов программа, которая может быть даже запускается на другом терминале. Эта программа контролирует работу ботов, и у неё есть возможность отключить их, или даже вырубить сеть, если что-то пошло не так: полилось слишком много заявок, или сильные изменения в портфеле, или пришла аномальная марке-дата. В любом случае, своим ботам надо доверять, а это достигается, в том числе, за счёт необходимого программного контроля за ними.
На этом всё! Всем спасибо! ;-)
Для разнообразия
Определитесь кем вы хотите быть — дизайнером, администратором БД, верстальщиком, серверным программистом, тимлидом… Это как в rpg игре — вначале надо выбрать себе класс персонажа. Также, надо понимать, что базовые конструкции любого языка, правила ООП и паттерны — это только начало, знать только это — ничего не даст. Каждый соверменный язык — это в первую очередь стек технологий — набор фреймворков и библиоте — умение пользоваться ими даёт главное конкурентное преимущество программиста.
Чтобы научиться — надо иметь громадную силу воли, с ноля в одиночку — это практически подвиг. Выбор языка не так важен — c#, java — по большому счёту одно и тоже. python, js — другие, там по другому, я бы не начинал с них.
Вам нужно приготовиться много читать и пробовать, если выберете C# — начните с основ .Net, с паттернов проектирования, с WPF, можно ASP MVC. Если выберете Java — то однозначно Spring, это в первую очередь DI, IoC, ORM, и весь стек J2EE -EJB, JNDI, JPA, JSF или SWING и куча всего прочего..
Верить в себя и не отчаиваться… Если будут вопросы — пишите!
Есть хабр: я частенько, если что-то ищу, пишу в поисковике (гугл или яндекс), например: «хабр c# события», или «хабр c# лямбды» или «хабр c# ООП» и т.п. Если c# — то нужна среда разработки, например бесплатная visual studion community. Может, сперва, стоит немного освоиться, написать несколько простых проектиков (обычно, хорошие находки в коде, кочуют из одного проекта в другой) — это подготовит, немного, к написанию бота.