Дизайн класса: Деметра против времени жизни соединения - PullRequest
0 голосов
/ 21 июля 2009

Хорошо, вот проблема, с которой я сталкиваюсь.

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

  1. Предоставить свойство для соединения, которое устанавливается вызывающей стороной до вызова метода. Это имеет несколько недостатков.

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

    • Если свойство соединения неожиданно закрыто, все методы должны либо (1.) вызвать исключение, либо (2.) принудительно открыть его. В зависимости от того, какой уровень надежности вы хотите, подходит любой из этих вариантов. (Обратите внимание, что это отличается от соединения, которое передается методу тем, что ссылка на соединение существует в течение всего времени жизни объекта, а не просто в течение времени жизни вызова метода. Следовательно, изменчивость соединения просто кажется выше для меня.)

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

    С другой стороны, Microsoft, похоже, поддерживает всю парадигму установки и запуска.

  2. Требовать, чтобы соединение передавалось в качестве аргумента методу. Это имеет несколько преимуществ и недостатков:

    • Список параметров, естественно, больше. Это утомительно для меня, в первую очередь в точке вызова.

    • Хотя соединение (и транзакция) все еще должны быть проверены перед использованием, ссылка на него существует только на время вызова метода.

    • Точка вызова, однако, совершенно ясна. Совершенно очевидно, что вы должны предоставить соединение, и что метод не будет автоматически создавать его за вашей спиной.

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

    • Если для метода требуется транзакция, очень сбрасывается из-за сигнатуры метода. Опять же, нет недостатка в ясности.

    • Поскольку класс не предоставляет свойства Connection или Transaction, у вызывающих абонентов нет никаких попыток развернуть их до свойств и методов, что обеспечит соблюдение закона Деметры.

Я знаю, это много. Но, с одной стороны, есть Microsoft Way: предоставить свойства, позволить вызывающей стороне установить свойства, а затем вызвать методы. Таким образом, вам не нужно создавать сложные конструкторы или фабричные методы и тому подобное. Также избегайте методов с большим количеством аргументов.

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

Если бы вы были на моем месте, что бы вы делали?

Ответы [ 2 ]

1 голос
/ 21 июля 2009

Вот третий шаблон для рассмотрения:

  • Создайте класс с именем ConnectionScope, который обеспечивает доступ к соединению
  • Любой класс в любое время, можно создать ConnectionScope
  • ConnectionScope имеет свойство Connection, которое всегда возвращает действительное соединение
  • Любой (и каждый) ConnectionScope предоставляет доступ к тому же базовому объекту соединения (в некоторой области, может быть, в том же потоке или процессе)

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

Подробнее:

  • В C # я бы порекомендовал ConnectionScope реализовать IDisposable, чтобы ваши классы могли писать код типа «using (var scope = new ConnectionScope ())», а затем ConnectionScope может освободить соединение (если это уместно) при его разрушении 1020 *
  • Если вы можете ограничить себя одним соединением на поток (или процесс), то вы можете легко установить строку подключения в статической переменной [thread] в ConnectionScope
  • Затем вы можете использовать подсчет ссылок, чтобы убедиться, что ваше отдельное соединение используется повторно, когда оно уже открыто, и соединения освобождаются, когда их никто не использует

Обновлено: вот несколько упрощенных примеров кода:

public class ConnectionScope : IDisposable
{
   private static Connection m_Connection;
   private static int m_ReferenceCount;

   public Connection Connection
   {
      get
      {
          return m_Connection;
      }
   }

   public ConnectionScope()
   {
      if ( m_Connection == null )
      {
          m_Connection = OpenConnection();
      }
      m_ReferenceCount++;
   }

   public void Dispose()
   {
      m_ReferenceCount--;
      if ( m_ReferenceCount == 0 )
      {
         m_Connection.Dispose();
         m_Connection = null;
      }
   }
}

Пример кода того, как один (любой) из ваших классов будет использовать его:

using ( var scope = new ConnectionScope() )
{
   scope.Connection.ExecuteCommand( ... )
}
0 голосов
/ 21 июля 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...