Класс контроллера с собственным классом конфигурации

У меня есть несколько классов контроллеров, которые используют свои собственные классы конфигурации, которые устанавливают поведение классов контроллеров.

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

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

Теперь у меня есть класс конфигурации, вложенный в частичный класс с тем же именем, что и класс контроллера, поэтому преобразователь может однозначно определить класс конфигурации, но я не очень доволен таким чудовищным солотингом, потому что частичный класс, содержащий только вложенную конфигурацию класс должен иметь то же пространство имен, что и класс контроллера, что меня больше всего беспокоит из-за несоответствия его имени папке, в которой этот класс физически расположен, поскольку классы контроллера находятся в папке «Контроллер», а их пространства имен «Something.Controller» и конфигурация классы находятся в папке «Конфигурация», но их пространства имен также должны быть «Something.Controller».

//Path: .\Controller
namespace Something.Controller
{
    public partial class MyController : IController
    {
    }
}

//Path: .\Configuration
namespace Something.Controller
{
    public partial class MyController
    {
        public class MyControllerConfiguration : IConfiguration
        {
        }
    }
}

И распознаватели (без проверки типов, как это было сделано ранее, и единственные типы, которые могут быть переданы этим методам, уже реализуют IController):

public class Resolver
{
    public IController GetController(Type controllerType)
    {
        return (IController)Activator.CreateInstance(controllerType);
    }
    public IConfiguration GetControllerConfiguration(Type controllerType)
    {
        var configurationType = controllerType.GetNestedTypes().FirstOrDefault(t => typeof(IConfiguration).IsAssignableFrom(t));
        if (configurationType != null)
            return (IConfiguration)Activator.CreateInstance(configurationType);
        else
            return null;
    }
}

Итак, как я могу связать класс контроллера с его классом конфигурации более понятным способом, чем вложение класса конфигурации в соответствующий класс контроллера?

РЕДАКТИРОВАТЬ:

Ниже приведен пример использования:

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


person Szybki    schedule 10.05.2017    source источник
comment
как класс конфигурации контролирует поведение класса контроллера?   -  person    schedule 10.05.2017
comment
@Orangesandlemons Устанавливает расположение файлового контроллера, над которым работает, количество одновременных потоков и т. д.   -  person Szybki    schedule 10.05.2017


Ответы (2)


Это предложение основано на принципе инверсии зависимостей. (Пример в посте на самом деле тесно связан с этой проблемой.)

Идея состоит в том, что ваш контроллер не должен нести ответственность за определение того, откуда берутся значения конфигурации. Это должно зависеть от абстракции, такой как интерфейс.

Первое, что я бы сделал, это отделил ваши настройки в отдельный интерфейс, например: (это не плагиат — это мой пост в блоге.)

public interface IConfiguration
{
    string SomeSetting { get; }
    int OtherSetting { get; }
}

Затем в своем классе контроллера сделайте следующее:

public class MyController
{
    private readonly IConfiguration _configuration;

    public MyController(IConfiguration configuration)
    {
        _configuration = configuration;
    }
}

Теперь, с точки зрения самого контроллера, работа сделана. В конструкторе требуется IConfiguration, поэтому невозможно создать экземпляр MyController, не предоставив что-то, реализующее IConfiguration.

Это полезно, потому что побуждает вас закончить написание вашего контроллера, не решая проблему, откуда возьмутся настройки конфигурации. Вы откладываете это. Вы можете закончить поставленную задачу, а затем решить, как вы хотите реализовать IConfiguration. Это может помешать тривиальной задаче отвлечь вас от более важной.

И по замыслу MyController никогда не узнает, что это за реализация. Это не волнует. Это зависит только от интерфейса. Вы можете изменить источник настроек, не меняя MyController, просто используя другую реализацию IConfiguration. Если вы хотите написать модульные тесты для MyController и протестировать его с различными настройками конфигурации, вы можете просто написать короткие «фиктивные» реализации IConfiguration с жестко заданными значениями.

Это оставляет вопрос о том, как IConfiguration передается в конструктор. Это часто (или обычно) делается с помощью контейнера внедрения зависимостей, такого как Unity, Autofac или Windsor. Если вы используете ASP.NET Core, у вас есть встроенный контейнер. (Я предполагаю, что это контроллер MVC, но, возможно, это не так - это не имеет значения.)

Чтобы найти инструкции по настройке приложения для использования контейнера внедрения зависимостей, введите в Google название используемой технологии + «внедрение зависимостей», например «внедрение зависимостей ASP.NET MVC». Или, если вы упомянете, что вы используете, я могу указать вам на статью.

Внедрение зависимостей не панацея, но для любого сложного приложения, включая приложения MVC, оно может быть чрезвычайно полезным. Я обнаружил, что это был шаг к пониманию и применению других принципов, а также к обучению написанию модульных тестов. Это также помогло мне не отвлекаться. Если класс, который я пишу, должен зависеть от чего-то еще, я просто пишу интерфейс для этой зависимости и продолжаю работать над классом, над которым работаю. Затем я могу вернуться и решить, как реализовать эту зависимость.

person Scott Hannen    schedule 10.05.2017
comment
Это то, что я ищу. Но главная проблема в том, что каждый контроллер имеет свой класс конфигурации. И из этих классов в интерфейс особо нечего извлекать, кроме самого интерфейса, который определяет, что класс, реализующий его, является классом конфигурации. Пожалуйста, смотрите редактирование вопроса, я добавил некоторые пояснения. - person Szybki; 11.05.2017
comment
Даже если у каждого контроллера будет свой собственный класс конфигурации, я не уверен, что это изменит. Если вы используете контейнер DI, вы собираетесь сообщить контейнеру, какую реализацию любого данного интерфейса создать. Но объединение двух классов невыгодно. Если у вас есть два класса, которые намеренно связаны и каждый работает только с другим, зачем вообще иметь два класса? - person Scott Hannen; 11.05.2017
comment
На самом деле единственная причина наличия двух классов заключается в том, что один класс (который реализует IController) — это класс, который делает что-то, а другой (который реализует IConfiguration) — это набор свойств, которые устанавливаются пользователем и сохраняются/вызываются на диск. - person Szybki; 11.05.2017
comment
Исходя из этого... Я до сих пор не уверен, что вижу преимущество наследования в объединении их вместе. Одна из причин, по которой следует избегать связывания, заключается в том, что тесно связанные классы работают до тех пор, пока вам не нужно что-то изменить. Я не уверен, где вписывается модульное тестирование, но зависимость от абстракций также упрощает модульное тестирование. Например, если ваши настройки всегда берутся с жесткого диска, как вы будете проверять, как контроллер работает с одним параметром и как он работает с другим? Вам действительно придется записывать значения на жесткий диск, чтобы протестировать контроллер. - person Scott Hannen; 11.05.2017
comment
На самом деле да. Я должен писать их и читать с жесткого диска. Или установите значения на лету для целей тестирования. Во всяком случае, предположим, что я отделил бы конфигурацию от контроллера. Контроллеру по-прежнему нужны его настройки, поэтому ему потребуется выполнить дополнительную проверку, является ли переданная конфигурация той конфигурацией, которую он ожидает. И вообще не будет развязываться. Или мне следует перепроектировать контроллеры, чтобы использовать только уровень абстракции? - person Szybki; 11.05.2017
comment
Проблема в том, что даже если бы у меня был один контроллер, ему все равно пришлось бы использовать какой-то другой уровень, например, для записи обработанных данных. И классы этого слоя по-прежнему нуждаются в своих настройках, например. url/номер порта, имя порта/скорость передачи данных, имя файла и т. д. И я не хочу сериализовать эти классы, поскольку мне это кажется неправильным. - person Szybki; 11.05.2017

Есть так много способов сделать это.

Например, в среде Spring конфигурация может быть выполнена в отдельном файле XML, который заполняет свойства каждого контроллера с помощью методов «set».

Другой распространенный подход заключается в том, чтобы заставить Controller искать свои свойства в некотором типе Singleton PropertyManager, который может иметь реализацию в файле, базе данных или в памяти.

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

person tsolakp    schedule 22.05.2017