Разрешение зависимостей

При вызове конструктора нам нужно передать ему аргументы. Параметры конструктора формируют зависимости, которые нужное разрешить. Так, например, команда ShowCommands для своего выполнение требует ссылку на роутер, который предоставит ей список всех доступных команд для последующего вывода.

Листинг WendingMachine/CommandsSystem/ShowCommands.cs

class ShowCommands : ICommand
{
    private Router _router;

    public ShowCommands(Router router)
    {
        _router = router;
    }

    public void Execute()
    {
        foreach(var command in _router.GetCommands())
        {
            Console.WriteLine(command);
        }
    }
}

Сам метод GetCommands находится в классе Router и не представляет ничего интересного. Он просто преобразует типы всех доступных команд в строку (по их имени) и выдает под интерфейсов IEnumerable<string>

Фрагмент 2.37

public IEnumerable<string> GetCommands()
{
    return GetCommandsTypes().Select(type => type.Name);
}

Интересует нас другое. Наш роутер может найти любой тип, который отвечает определённым требования. И у нас нет четких требований  от чего может зависеть команда. Ей, для выполнения, может потребоваться самый широкий набор сущностей. Единственное, что мы знаем, это то, что все зависимости будут отражены через параметры конструктора. На самом деле это не обязательно, но то, что мы будем делать, основывается на этом факте.

Итак, у нас есть конструктор, который декларирует то, что нужно команде через параметры. Параметры могут быть любого типа и нам нужно предоставить аргументы под эти параметры. Нам нужно выполнить следующие действия:

  1. Если параметр типа int, то мы делаем аргументом значение из запроса (Request) по порядку;
  2. Если параметр другого типа, отличного от примитивного (не string, byte, etc.), то мы разрешаем зависимость на основе таблицы, которая есть в роутере;

За это всё ответственен метод ResolveDependeciesAndMerge, который используется в методе CreateInstance. На вход этот метод получает конструктор и запрос, а далее пытается предоставить аргументы к каждому параметру. Если это удаётся – он их возвращает, если нет – он возвращает null.

Фрагмент 2.38  

private object[] ResolveDependenciesAndMerge(ConstructorInfo constructor, Request request)
{
    List<object> args = new List<object>();
    Queue<int> requestArgs = new Queue<int>(request.Values);

    foreach (var parameter in constructor.GetParameters())
    {
        if (_dependecies.TryGetValue(parameter.ParameterType, out object value))
        {
            args.Add(value);
        }
        else
        {
            if (requestArgs.Count == 0) return null;

            args.Add(requestArgs.Dequeue());
        }
    }

    if (args.Count == constructor.GetParameters().Length)
    {
        return args.ToArray();
    }
    else
    {
        return null;
    }
}

Этому методу для работы также нужна таблица, которая тоже находится в классе Request.

Фрагмент 2.39  

private Dictionary<Type, object> _dependecies;

public Router(WendingMachine machine)
{
    ...

    _dependecies = new Dictionary<Type, object>()
                    {
                        { typeof(WendingMachine), _machine},
                        { typeof(Router), this }
                    };
}

В ResolveDependeciesAndMerge мы имеем последовательность параметров и очередь аргументов из запроса. А также список аргументов, которые можно отправить в конструктор. Точнее, этот список нам нужно получить в результате.

Таблица нам помогает разрешать зависимость от определённого типа. Так, если параметр определён с типом WengingMachine и мы дадим ему ссылку на конкретный объект этого типа. Расширяя таблицу, мы расширяем спектр зависимостей, которые можем передавать нашим командам.

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

Фрагмент 2.40  

public AddMoney(WendingMachine machine, int money)
{
    _machine = machine;
    _money = money;
}

...

public AddMoney(int money, WendingMachine machine)
{
    _machine = machine;
    _money = money;
}

Замечу, что мы говорим не про одновременное существование этих двух конструкторов (такого быть не может), а про независимость от последовательности параметров.

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

Вот полный листинг нового роутера.

Листинг WendingMachine/CommandsSystem/Router.cs   

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace CommandsSystem
{
    class Router
    {
        private readonly Type _commandBaseType = typeof(ICommand);

        private WendingMachine _machine;
        private RouterState _state;
        private Dictionary<Type, object> _dependecies;

        public Router(WendingMachine machine)
        {
            _machine = machine;
            _state = new DefaultState(this);

            _dependecies = new Dictionary<Type, object>()
                            {
                                { typeof(WendingMachine), _machine},
                                { typeof(Router), this }
                            };
        }

        public ICommand CreateCommand(Request request)
        {
            var commandType = GetCommandTypeByName(request.Command);
            if (commandType != null)
            {
                var instance = CreateInstance(commandType, request);
                return instance;
            }
            else
            {
                return null;
            }
        }

        public void Login()
        {
            _state = new AdminState(this);
        }

        public void Logout()
        {
            _state = new DefaultState(this);
        }

        public IEnumerable<string> GetCommands()
        {
            return GetCommandsTypes().Select(type => type.Name);
        }

        private object[] ResolveDependeciesAndMerge(ConstructorInfo constructor, Request request)
        {
            List<object> args = new List<object>();
            Queue<int> requestArgs = new Queue<int>(request.Values);

            foreach (var parameter in constructor.GetParameters())
            {
                if (_dependecies.TryGetValue(parameter.ParameterType, out object value))
                {
                    args.Add(value);
                }
                else
                {
                    if (requestArgs.Count == 0) return null;

                    args.Add(requestArgs.Dequeue());
                }
            }

            if (args.Count == constructor.GetParameters().Length)
            {
                return args.ToArray();
            }
            else
            {
                return null;
            }
        }

        private IEnumerable<Type> GetCommandsTypes()
        {
            return AppDomain
                .CurrentDomain
                .GetAssemblies()
                .SelectMany(assembly => assembly.GetTypes())
                .Where(type => _commandBaseType.IsAssignableFrom(type))
                .Where(type => IsRealClass(type));
        }

        private Type GetCommandTypeByName(string name)
        {
            return GetCommandsTypes()
                .Where(type => type.Name == name)
                .FirstOrDefault();
        }

        private bool IsRealClass(Type testType)
        {
            return testType.IsAbstract == false
                    && testType.IsGenericTypeDefinition == false
                    && testType.IsInterface == false;
        }

        private ICommand CreateInstance(Type type, Request request)
        {
            ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public);

            foreach (var ctor in constructors)
            {
                var args = ResolveDependeciesAndMerge(ctor, request);
                if (args != null)
                {
                    return (ICommand)ctor.Invoke(args);
                }
            }

            return null;
        }

        abstract class RouterState
        {
            protected readonly Router Router;

            public RouterState(Router router)
            {
                Router = router;
            }

            public abstract IOrder MakeOrder(Request request);
        }

        class DefaultState : RouterState
        {
            public DefaultState(Router router) : base(router)
            {
            }

            public override IOrder MakeOrder(Request request)
            {
                return new PayableOrder(Router._machine.GetFromId(request.Values[0]), request.Values[1]);     
            }
        }

        class AdminState : RouterState
        {
            public AdminState(Router router) : base(router)
            {
            }

            public override IOrder MakeOrder(Request request)
            {
                return new FreeOrder(Router._machine.GetFromId(request.Values[0]), request.Values[1]);
            }
        }
    }
}

Если вы нашли ошибку, пожалуйста выделите её и нажмите Ctrl+Enter.


Leave a Reply

Your email address will not be published. Required fields are marked *