#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 IReadOnlyCollectionReadAllCommentsByPostId(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. За это придётся расплачиваться изменением архитектуры программы и применением того самого внедрения зависимостей. О нём очень хорошую книгу написал Марк Симан.
Комментариев нет:
Отправить комментарий