Страницы

Поиск по вопросам

суббота, 21 декабря 2019 г.

Как организовать класс с настройками программы?

#c_sharp #архитектура


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

    public static class Setting
{
    public static string HomePath { get; private set; }
    public static bool IsInitSetting { get; private set; }

    private class _Setting : IDisposable
    {
        public string HomePath;


        public _Setting() { }
        public void Dispose()
        {
            HomePath = null;

        }
    }

    public static void Init()
    {
        if (!IsInitSetting)
        {
            using (_Setting setting = JObject.Parse(File.ReadAllText("settings.json")).ToObject<_Setting>())
            {
                HomePath = setting.HomePath;
                IsInitSetting = true;
            }
        }
    }
}

    


Ответы

Ответ 1



Статический класс плох тем, что он привносит неявную зависимость. Если позже вы захотите часть своей программы перенести в другой проект, это будет сделать сложно, потому что вам придётся переносить и класс Settings, который, кстати, обладает двумя ответственностями в нарушения принципа единственной ответственности. Он а) умеет загружать настройки и б) предоставляет к ним доступ. В другом проекте настройки могут храниться по другому, не в JSON-файле, так что перенос одного класса может вылиться в большой рефакторинг. Чтобы избавиться от лишних зависимостей их надо инвертировать, в соответствии с принципом инверсии зависимостей. Класс, который использует настройки не зависит от класса настроек, вместо этого он говорит, что ему для работы потребуются вот такие параметры. В качестве примера давайте рассмотрим кусок движка блогов, а конкретно метод, постранично возвращающий комментарии к посту: public class CommentRepository { public IReadOnlyCollection ReadAllCommentsByPostId(int postId, int page = 1) { using (var dbContext = new DbContext(Settings.ConnectionString)) { int offset = Settings.PageSize * (page - 1); int count = Settings.PageSize; return dbContext.Comments .Where(x => x.PostId = PostId) .OrderBy(x => x.CreatedAt) .Skip(offset) .Take(count) .ToArray(); } } } В этом метод мы обращаемся к настройкам Settings.ConnectionString и Settings.PageSize. Эта зависимость неявная, поскольку нигде в интерфейсе класса мы не видим, что методы или конструкторы зависят от Settings. Сделаем эту зависимость явной: public class CommentRepository { private readonly string connectionString; private readonly int pageSize; public CommentRepository(string connectionString, int pageSize) { this.connectionString = connectionString; this.pageSize = pageSize; } public IReadOnlyCollection ReadAllCommentsByPostId(int postId, int page = 1) { using (var dbContext = new DbContext(connectionString)) { int offset = pageSize * (page - 1); int count = pageSize; return dbContext.Comments .Where(x => x.PostId = PostId) .OrderBy(x => x.CreatedAt) .Skip(offset) .Take(count) .ToArray(); } } } Чтобы работать с инверсией зависимостей полезно использовать какую-нибудь библиотеку. Их для C# существует не меньше десятка. Вот, как мы можем зарегистрировать класс CommentRepository в контейнере: var jsonSettings = File.ReadAllText("settings.json"); var settings = JsonConvert.DeserializeObject(jsonSettings); builder.Register() .As() .WithParameter("connectionString", settings.ConnectionString) .WithParameter("pageSize", settings.PageSize); Чем этот подход отличается от предыдущего? Тем, что если нам потребуется класс CommentRepository вынести в другой проект, это будет проще сделать. На самом деле он зависит от классов DbContext и Comment, но в данном случае это нормально: мы не может перенести репозиторий комментариев без самих комментариев. Но мы убрали зависимость от класса Settings. Теперь и класс Settings стал проще, это обычный DTO, не статический класс, не синглтон. Он просто описывает структуру файла настроек. Загрузка настроек вынесена туда, где классы регистрируются в контейнере Autofac. В другом проекте мы можем использовать другой контейнер, например, NInject, и настройки будем загружать не из JSON, а из переменных окружения, и при этом нам не придётся вносить ни одного изменения в CommentRepository, Comment и DbContext. За это придётся расплачиваться изменением архитектуры программы и применением того самого внедрения зависимостей. О нём очень хорошую книгу написал Марк Симан.

Комментариев нет:

Отправить комментарий