Что является более злым: ненужный синглтон или объект Бога?
Вот ситуация: у меня есть класс, который делает слишком много. Это в основном для доступа к информации о конфигурации, но также имеет подключение к базе данных. Он реализован как одноэлементный, поэтому это также затрудняет модульное тестирование, так как большая часть кода довольно тесно связана с ним. Это еще более проблематично, поскольку создает зависимость от времени импорта (мы делаем это в Python), что означает, что определенные модули должны быть импортированы в определенном порядке. В идеале я хотел бы разделить это на два класса и сделать его не одноэлементным.
К счастью, мой работодатель осознал тот факт, что этот вид тестирования хорош, и готов разрешить мне вносить подобные изменения, если это делает код более тестируемым. Однако я сомневаюсь, что они захотят позволить мне тратить на это слишком много времени. И я предпочел бы исправить это постепенно, а не пытаться быть слишком радикальным.
Итак, я вижу три варианта:
- Разбейте объект конфигурации на (одноэлементный) объект конфигурации и (не одноэлементный) объект базы данных. Это, по крайней мере, позволило бы мне удалить базу данных как зависимость от времени импорта.
- Сделайте объект конфигурации не одноэлементным и передайте его нужным объектам. Я чувствую, что это лучше отвечает нашим краткосрочным потребностям, но я думаю, что это займет значительно больше времени.
- Сделай то, о чем я не подумал, что ты предлагаешь в своем ответе.:-)
Так что мне делать?
4 ответа
Я думаю, что вы на пути к разделению на два класса. Возможно, вы захотите использовать фабрику для создания контекста / соединения вашей базы данных по мере необходимости. Таким образом, вы можете рассматривать соединение как единицу работы, которая создается или удаляется по мере необходимости, а не сохраняя единственное соединение в течение всего срока службы объекта. YMMV, хотя.
Что касается конфигурации, это один из случаев, когда я считаю, что синглтон может быть правильным выбором. Я не обязательно выбрасываю это только потому, что это сложно для модульного тестирования. Возможно, вы захотите создать его, чтобы реализовать интерфейс. Затем вы можете использовать внедрение зависимостей для предоставления имитационного экземпляра интерфейса во время тестирования. Ваш производственный код будет создан либо для использования экземпляра singleton, если введенное значение равно нулю, либо для внедрения экземпляра singleton. В качестве альтернативы, вы можете создать класс, чтобы разрешить повторную инициализацию с помощью частного метода, и вызвать его в ваших методах настройки / тестирования, чтобы убедиться, что он имеет правильную конфигурацию для ваших тестов. Я предпочитаю первую реализацию второй, хотя я также использовал ее, когда у меня нет прямого контроля над интерфейсом.
Внесение изменений постепенно - это определенно путь. Если возможно, оберните текущую функциональность тестами и убедитесь, что эти тесты все еще проходят после ваших модификаций (хотя, конечно, не те, которые непосредственно касаются ваших модификаций), это хороший способ убедиться, что вы не нарушаете другой код,
Трудно понять, не видя ваш код, но почему бы не сделать то, что вы говорите - делать это постепенно? Сначала выполните шаг 1, разделите базу данных.
Если это быстро, вернитесь назад, и теперь у вас есть только 1 меньший объект, чтобы перестать быть одиночным, а не 2. Так что шаг 2 должен быть быстрее. Или на этом этапе вы можете увидеть какой-то другой код, который может быть переработан из синглтона.
Надеемся, что вы можете постепенно уменьшать содержимое синглтона до тех пор, пока оно не исчезнет, не платя при этом огромный налог на время за один шаг.
Например, может быть, одна часть конфигурации может быть выполнена по одному, если части конфигурации независимы. Так, может быть, конфигурация графического интерфейса оставила синглтон, в то время как конфигурация файла была изменена, или что-то подобное?
Извините за то, что я не знаю Python, поэтому я надеюсь, что любой псевдокод имеет смысл...
Сначала я разделил бы объект на две части, чтобы обязанности вашего синглтона были меньше, а затем взял бы оставшийся синглтон конфигурации и превратил его в обычный класс (как будто вы собираетесь выполнить второе предложение).
Как только я доберусь до этого, я создам новый синглтон-оболочку, который предоставляет методы класса конфигурации:
class ConfigurationWrapper : IConfigurationClass
{
public static property ConfigurationWrapper Instance;
public property IConfigurationClass InnerClass;
public method GetDefaultWindowWidth()
{
return InnerClass.GetDefaultWindowWidth();
}
etc...
}
Теперь самое первое, что делает приложение, - это вставляет экземпляр класса ConfigurationClass в оболочку.
ConfigurationClass config = new ConfigurationClass()
ConfigurationWrapper.Instance.InnerClass = config;
Наконец, вы можете переместить классы, которые в настоящее время полагаются на синглтон, на оболочку синглтона (которая должна быть быстро найдена и заменена). Теперь вы можете преобразовывать классы по одному, чтобы передать объект конфигурации через их конструкторы для завершения этапа 2. Любой, на что у вас нет времени, может использовать оболочку-синглтон. После того, как вы переместили их, вы можете избавиться от обертки.
С другой стороны, вы можете игнорировать рефакторинг использований и просто внедрить проверенный класс конфигурации в одноэлементную оболочку для тестирования - по сути, инъекция зависимостей бедного человека.
Вариант 1 - это то, что я делаю во всех своих приложениях: объект конфигурации singleton и объекты базы данных, созданные по требованию или внедренные.
Наличие объекта конфигурации в качестве синглтона всегда было для меня идеальным вариантом. Существует только один файл конфигурации, и я всегда хочу прочитать его при запуске приложения.