Глобальный против Синглтона в .NET - PullRequest
5 голосов
/ 02 февраля 2009

У меня здесь очень распространенная ситуация. И в течение многих лет я не обнаружил, что то, что я делаю, ПРАВИЛЬНО по отраслевым стандартам. Рассмотрите приложение, которое подключается к базе данных, но где строка подключения вместо сохранения в каком-либо файле / параметре передается в качестве параметра командной строки при запуске ИЛИ база данных просматривается во время запуска приложения.

Что ж, становится необходимым сохранить эту строку подключения где-нибудь в рамках приложения. Наиболее распространенный способ, которым я видел это, - это модуль или глобальный класс с методами get / set для сохранения строки соединений. Еще один способ сделать это - использовать Singleton. Любой из вариантов, мой DAL может получить доступ к строке подключения, когда это необходимо через метод GetConnectionString.

Есть ли ЛУЧШИЙ способ сделать это?

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

Ответы [ 6 ]

8 голосов
/ 02 февраля 2009

В общем случае следует избегать глобального состояния, будь то глобальный класс или единичный объект.

Идеальным решением было бы заставить ваше приложение загрузить строку подключения из конфигурации и внедрить в любые классы, которые в ней нуждаются. В зависимости от размера вашего приложения может помочь IoC контейнер, такой как Unity или Castle Windsor , но, безусловно, не является обязательной частью решения.

Если это не вариант, и вы застряли в поддержании глобального состояния (из-за существующей кодовой базы или чего-то еще), я не знаю, есть ли огромная разница между двумя предложенными вами подходами.

Обновление: Просто чтобы прояснить, забудьте пока все о контейнерах IoC, «Inject» - это просто причудливый способ сказать «передать как параметр» (либо конструктору класса, либо через собственность или что-то еще).

Вместо того, чтобы иметь класс доступа к данным, нужно запросить строку подключения (из какого-то глобального или одиночного типа), передать ее через конструктор или свойство.

Обновление № 2: Я думаю, что все еще есть некоторое недопонимание относительно того, что влечет за собой этот подход.

В основном все сводится к тому, как выглядит ваш класс доступа к данным:

public class DataAccessClass
{
    public DataAccessClass()
    {
        _connString = SomeStaticThing.GetConnectionString();
    }
}

или

public class DataAccessClass
{
    public DataAccessClass(string connString)
    {
        _connString = connString;
    }
}

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

Да, в каком-то месте должен быть какой-то статичный парень, отвечающий за захват строки соединения, но дело в том, что ваши зависимости от статических методов ограничены этим spot (который, вероятно, будет вашим методом Main в процессе начальной загрузки вашего приложения), а не разбросан по всей вашей кодовой базе.

5 голосов
/ 02 февраля 2009

приятно видеть столько креативных решений такой простой проблемы; -)

существенные факты, из вопроса ОП:

  1. строка подключения передается в командной строке
  2. многим другим компонентам может потребоваться использовать строку подключения

итак, нет способа обойти использование статического элемента ; будь то глобальная переменная (трудно сделать в .NET, действительно, без включающего класса), статический класс или одиночный код, на самом деле не имеет значения. Решением с кратчайшим путем будет статический класс, инициализированный классом Program, который обрабатывает командную строку.

Любое другое решение по-прежнему требует статического доступа к переданной соединительной строке , хотя они могут скрывать это за одним или несколькими уровнями косвенного обращения.

Я не говорю, что вы не хотите наряжать базовое решение чем-то более причудливым, но в этом нет необходимости, и это не устраняет принципиально статичную / глобальную природу строки соединения описано .

4 голосов
/ 02 февраля 2009

Если все, что вы храните - это строка (возможно, вместе с кучей других глобальных настроек приложения), я бы просто использовал статический класс с кучей свойств для хранения всего этого. Singleton - это больше кода и работы по обслуживанию, но в этом случае это ненужная работа, так как ваш класс свойств ничего не будет делать; просто держите вещи.

2 голосов
/ 02 февраля 2009

Конечно, есть способ. Прежде всего, ознакомьтесь с внедрением зависимостей и подобными методами и прочитайте о сервисном слое , поскольку это то, что вам нужно здесь.

Что касается уровня сервиса, вам нужен какой-то сервис конфигурации, который будет модулем, который части вашего приложения будут запрашивать информацию о конфигурации - строки подключения, URI и т. Д .:

interface IConfigurationService
    string ConnectionString
    bool IntegratedSecurity
    bool EncryptProfiles

Должно быть довольно просто, что в вашей системе есть только один экземпляр IConfigurationService, но это достигается с помощью выбранного DI-контейнера, а не шаблона Singleton.

Далее все ваши службы DAL получат ссылку на IConfigurationService:

class FooDal : IFooDal
    IConfigurationService ConfigurationService

так что теперь они могут использовать IConfigurationService.ConnectionString, чтобы завладеть строкой соединения.

1 голос
/ 02 февраля 2009

Это зависит.

Имейте в виду, что синглтон:

  • обеспечивает существование только одного экземпляра класса, а
  • обеспечивает глобальный доступ

Глобальный обеспечивает только глобальный доступ, но не дает никаких гарантий относительно количества экземпляров.

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

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

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

Выбор между локальным и глобальным сложнее. Глобальное изменчивое состояние обычно лучше избегать. Неизменное состояние представляет меньшую проблему и не приведет к тем же проблемам синхронизации / параллелизма / масштабируемости, что и глобальное изменяемое состояние.

Но часто бывает предпочтительнее иметь ее в качестве локальной переменной, которую вы передаете нужным компонентам. Это можно сделать вручную, просто передав его конструктору объекта, которому требуется доступ к БД, или это можно в некоторой степени автоматизировать с помощью контейнера IoC.

Но в любом случае, если он не является глобальным, ваш код становится более универсальным и более переносимым. Если у него есть скрытые зависимости от классов и глобальных данных, которые должны существовать, чтобы код работал, то он не может быть легко использован в других проектах, или если вы слишком много реорганизуете всю кодовую базу.

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

0 голосов
/ 02 февраля 2009

как сказал @Anton, вам нужно иметь интерфейс, который предоставляет что-то вроде

interface IConfigurationService
    string ConnectionString

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

Даже если это может показаться трудоемким по сравнению с одноэлементным или глобальным, это снизит сцепление в вашем коде, а значит, улучшит возможность повторного использования и упростит модульное тестирование, и это довольно просто, если вы убедите себя, что глобальные (в основном) злые )

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

...