Доступ к глобальным настройкам приложения
-
08-06-2019 - |
Вопрос
Приложение базы данных, над которым я сейчас работаю, хранит все виды настроек в базе данных.Большинство из этих настроек предназначены для настройки определенных бизнес-правил, но там есть и кое-что еще.
Приложение содержит объекты, которые специально выполняют определенную задачу, например, определенное сложное вычисление.Эти объекты, не относящиеся к пользовательскому интерфейсу, проходят модульное тестирование, но также нуждаются в доступе ко многим из этих глобальных настроек.Способ, которым мы реализовали это прямо сейчас, заключается в предоставлении объектам свойств, которые заполняются контроллером приложения во время выполнения.При тестировании мы создаем объекты в тесте и заполняем значения для тестирования (не из базы данных).
Это работает лучше, во всяком случае, намного лучше, чем если бы все эти объекты нуждались в каком-то глобальном Настройки object - это, конечно, фактически делает модульное тестирование невозможным :) Недостатком может быть то, что иногда вам нужно установить дюжину свойств или что вам нужно позволить этим свойствам "просачиваться" в подобъекты.
Итак, общий вопрос заключается в следующем:как вы предоставляете доступ к глобальным настройкам приложения в своих проектах без необходимости в глобальных переменных, сохраняя при этом возможность модульного тестирования вашего кода?Это, должно быть, проблема, которая решалась сотни раз...
(Примечание:Как вы, наверное, заметили, я не слишком опытный программист;но я люблю учиться!И, конечно, я уже провел исследование по этой теме, но мне действительно нужен опыт из первых рук)
Решение
Вы могли бы использовать шаблон ServiceLocator Мартина Фаулерса.В php это могло бы выглядеть примерно так:
class ServiceLocator {
private static $soleInstance;
private $globalSettings;
public static function load($locator) {
self::$soleInstance = $locator;
}
public static function globalSettings() {
if (!isset(self::$soleInstance->globalSettings)) {
self::$soleInstance->setGlobalSettings(new GlobalSettings());
}
return self::$soleInstance->globalSettings;
}
}
Затем ваш производственный код инициализирует service locator следующим образом:
ServiceLocator::load(new ServiceLocator());
В свой тестовый код вы вставляете свои фиктивные настройки следующим образом:
ServiceLocator s = new ServiceLocator();
s->setGlobalSettings(new MockGlobalSettings());
ServiceLocator::load(s);
Это хранилище для одиночек, которыми можно обмениваться в целях тестирования.
Другие советы
Мне нравится моделировать доступ к моей конфигурации на основе шаблона поиска служб.Это дает мне единственную возможность получить любое значение конфигурации, которое мне нужно, и, помещая его вне приложения в отдельную библиотеку, обеспечивает повторное использование и возможность тестирования.Вот несколько примеров кода, я не уверен, какой язык вы используете, но я написал его на C #.
Сначала я создаю универсальный класс, который будет моделировать мой ConfigurationItem.
public class ConfigurationItem<T>
{
private T item;
public ConfigurationItem(T item)
{
this.item = item;
}
public T GetValue()
{
return item;
}
}
Затем я создаю класс, который предоставляет общедоступные статические переменные, доступные только для чтения, для элемента конфигурации.Здесь я просто считываю ConnectionStringSettings из конфигурационного файла, который представляет собой просто xml.Конечно, для получения большего количества элементов вы можете прочитать значения из любого источника.
public class ConfigurationItems
{
public static ConfigurationItem<ConnectionStringSettings> ConnectionSettings = new ConfigurationItem<ConnectionStringSettings>(RetrieveConnectionString());
private static ConnectionStringSettings RetrieveConnectionString()
{
// In .Net, we store our connection string in the application/web config file.
// We can access those values through the ConfigurationManager class.
return ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["ConnectionKey"]];
}
}
Затем, когда мне нужен ConfigurationItem для использования, я вызываю его следующим образом:
ConfigurationItems.ConnectionSettings.GetValue();
И это вернет мне типобезопасное значение, которое я затем смогу кэшировать или делать с ним все, что захочу.
Вот примерный тест:
[TestFixture]
public class ConfigurationItemsTest
{
[Test]
public void ShouldBeAbleToAccessConnectionStringSettings()
{
ConnectionStringSettings item = ConfigurationItems.ConnectionSettings.GetValue();
Assert.IsNotNull(item);
}
}
Надеюсь, это поможет.
Обычно это обрабатывается с помощью ini-файла или XML-файла конфигурации.Тогда у вас просто есть класс, который считывает настройку при необходимости.
В .NET это встроено в классы ConfigurationManager, но реализовать это довольно просто, просто считайте текстовые файлы или загружайте XML в DOM или разбирайте их вручную в коде.
Наличие конфигурационных файлов в базе данных - это нормально, но это привязывает вас к базе данных и создает дополнительную зависимость для вашего приложения, которую решают файлы ini / xml.
Я сделал это:
public class MySettings
{
public static double Setting1
{ get { return SettingsCache.Instance.GetDouble("Setting1"); } }
public static string Setting2
{ get { return SettingsCache.Instance.GetString("Setting2"); } }
}
Я поместил это в отдельный инфраструктурный модуль, чтобы устранить любые проблемы с циклическими зависимостями.
Делая это, я не привязан к какому-либо конкретному методу настройки, и у меня нет строк, вызывающих хаос в коде моих приложений.