@KislyFan
инженер, связист и просто любитель выпить

Паттерн фабрики, и передача параметров в выпускаемый объект. Как?

Столкнулся с тем, что не могу понять как реализовать в программе один момент с передачей параметров через DI.
Пытаюсь реализовать подобие паттерна фабрики, и если мой пример максимально упростить, то интерфейсы выглядят так.

public interface ISession
{
        bool Login();
        object Execute(string command);
}

public interface ISessionFactory
{
        ISession Create(string name)
}

И их реализация. Набирал прямо тут, мог ошибиться.. прошу понять и простить.
public class Session : ISession
{
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ILogger<Session> _logger;

        private HttpClient _httpClient;

        private string _clientname;
        private string _clientname;
        private string _password;

        public Session(ILogger<Session> logger, IHttpClientFactory httpClientFactory)
        {
                _logger = logger;
                _httpClientFactory = httpClientFactory;
        }

        public Init(string clientname, string username, string password)
        {
                _clientname=clientname;
                _clientname=username;
                _password=password;
        }

        bool Login()
        {
                _httpClient = _httpClientFactory.CreateClient(_clientname);
                var response = await _httpClient.PostAsync($"login?usr={_username}&pwd={_password}", null);
                // do etc
        }

        public object Execute(string command)
        {
                // do
        }
}

public class SessionFactory : ISessionFactory
{
        public SessionFactory(ILogger<Fabric> logger, IOptions<MyOptions> myOptions, IFabric<ISession> sessionFabric)
        {
                _logger = logger;
                _myOptions = myOptions.Value;
                _sessionFabric = sessionFabric;
        }

        ISession Create(string name)
        {
                var session = _sessionFabric.Create();
                session.Init(name, _myOptions[name].Username, _myOptions[name].Password);
                session.Login();
                return session;
        }
}


public class Factory<T> : IFactory<T>
    {
        private readonly Func<T> _initFunc;

        public Factory(Func<T> initFunc)
        {
            _initFunc = initFunc;
        }

        public T Create()
        {
            return _initFunc();
        }
    }

    public interface IFactory<T>
    {
        T Create();
    }

    ***

        public static void AddFactory<TService, TImplementation>(this IServiceCollection services)
            where TService : class
            where TImplementation : class, TService
        {
            services.AddTransient<TService, TImplementation>();
            services.AddSingleton<Func<TService>>(x => () => x.GetService<TService>());
            services.AddSingleton<IFactory<TService>, Factory<TService>>();
        }


Во первых у меня получается какая-то фабрика в фабрике. Первая фабрика создает обьект сессии, вторая настраивает. Чутье подсказывает мне, что так быть не должно.

Во-вторых, я не могу понять, как мне передать параметры clientname, username, password через DI так, чтобы их нельзя было сменить во время сессии и нарваться на исключение от httpClient. Защита от дурака. Ну кроме очевидных решений вроде хранения булевой переменной вроде alreadyInit.
  • Вопрос задан
  • 155 просмотров
Пригласить эксперта
Ответы на вопрос 1
gdt
@gdt
Программист
Чутьё вас не обманывает, мне кажется с текущим интерфейсом/реализацией IFactory{T} вы можете их убрать (IFactory{T}/Factory{T}) и инжектить напрямую Func{T} - т. к. всё, что делает Factory{T} - вызывает внедрённый Func{T}.

Далее, если вам нужно передать что-то через DI - это что-то в первую очередь нужно в DI контейнере зарегистрировать. Т. е. вам нужен по желанию интерфейс + реализация либо просто класс параметров, как-то так например:
public interface ISessionParameters
{
    string ClientName { get; }
    string UserName { get; }
    string Password { get; }
}

internal sealed class DefaultSessionParameters : ISessionParameters
{
    public string ClientName => "ClientName";
    public string UserName => "UserName";
    public string Password => "Password";
}


Реализацию нужно зарегистрировать в DI контейнере и куда-нибудь внедрить. С учётом того, что у вас сессия берётся напрямую из контейнера - параметры можно пробросить прямо через конструктор сессии.

Однако более правильный вариант - передавать параметры через фабрику сессий, что в принципе у вас и происходит. Если хочется именно так - есть вариант создавать сессию руками через конструктор, пробросив недостающие зависимости через конструктор фабрики. Ещё есть вариант инжектить не Func{ISession}, а Func{ISessionParameters, ISession}, и настроить контейнер соответствующим образом.

Ещё насчёт SessionFactory - возможно будет проще воспринимать его как SessionManager. Тогда SessionManager мог бы использовать что-то типа SessionFactory.Create(ISessionParameters) (или Func{ISessionParameters, ISession}), а SessionFactory мог бы создавать какой-то конкретный тип сессий с переданными параметрами и пробрасывать зависимости из контейнера в конструктор сессии. Т. е. вот так:

public interface ISessionFactory
{
    ISession Create(ISessionParameters parameters);
}

internal sealed class SessionFactory
{
    // Можно и здесь передать параметры - в зависимости от задачи
    public SessionFactory(ILogger<Session> logger, IHttpClientFactory httpClientFactory)
    {
        // Запоминаем
    }

    public ISession Create(ISessionParameters parameters)
    {
        // В сессию передаём также параметры.
        return new Session(_logger, _httpClientFactory, parameters);
    }
}
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы