Давайте зробимо крок назад і подивимось на більшу картину тут.
Що таке IDatabase
відповідальність?
У ньому є кілька різних операцій:
- Розбираємо рядок з'єднання
- Відкрити з'єднання з базою даних (зовнішня система)
- Надсилання повідомлень у базу даних; повідомлення командують базу даних, щоб змінити стан
- Отримуйте відповіді з бази даних та перетворюйте їх у формат, який може використовувати абонент
- Закрийте з'єднання
Дивлячись на цей список, ви можете думати: "Чи це не порушує SRP?" Але я не думаю, що це робить. Усі операції є частиною єдиної, згуртованої концепції: керуйте стаціонарним з'єднанням із базою даних (зовнішньою системою) . Він встановлює з'єднання, він відслідковує поточний стан з'єднання (стосовно операцій, які виконуються на інших з'єднаннях, зокрема), він сигналізує, коли здійснити поточний стан з'єднання тощо. У цьому сенсі він виступає як API що приховує безліч деталей реалізації, про які більшість абонентів не піклуються. Наприклад, чи використовує він HTTP, розетки, труби, користувацькі TCP, HTTPS? Код виклику не має значення; він просто хоче надсилати повідомлення та отримувати відповіді. Це хороший приклад інкапсуляції.
Ми впевнені? Не могли б ми розділити деякі з цих операцій? Можливо, але користі немає. Якщо ви спробуєте їх розділити, вам все одно знадобиться центральний об'єкт, який підтримує з'єднання відкритим та / або керує тим, що є поточним станом. Усі інші операції сильно поєднані з тим самим станом, і якщо ви спробуєте їх відокремити, вони все одно в остаточному підсумку делегують назад до об’єкта з'єднання. Ці операції природно і логічно пов'язані з державою, і немає можливості розділити їх. Розв’язка - це чудово, коли ми можемо це зробити, але в цьому випадку ми насправді не можемо. Принаймні, не без зовсім іншого протоколу без громадянства для розмови з БД, і це насправді значно ускладнить такі важливі проблеми, як дотримання ACID. Крім того, під час спроби від'єднати ці операції від з'єднання, ви будете змушені розкривати деталі про протокол, який не викликає побоювань, оскільки вам знадобиться спосіб надсилання якогось "довільного" повідомлення до бази даних.
Зауважте, що факт, з яким ми маємо справу з протоколом стану, досить твердо виключає вашу останню альтернативу (передаючи рядок з'єднання як параметр).
Чи дійсно нам потрібно встановити рядок з'єднання?
Так. Ви не можете відкрити з'єднання, поки у вас немає рядка з'єднання, і ви не можете нічого робити з протоколом, поки не відкриєте з'єднання. Тому безглуздо мати об’єкт з'єднання без одного.
Як ми вирішуємо проблему вимагання рядка з'єднання?
Проблема, яку ми намагаємося вирішити, полягає в тому, що ми хочемо, щоб об'єкт знаходився у зручному для нього режимі. Яка сутність використовується для управління державою на мовах ОО? Об'єкти , а не інтерфейси. Інтерфейси не мають стану управління. Оскільки проблема, яку ви намагаєтеся вирішити, - це проблема управління державою, тут інтерфейс насправді не підходить. Абстрактний клас набагато природніше. Тому використовуйте абстрактний клас з конструктором.
Ви також можете розглянути можливість фактичного відкриття з'єднання під час конструктора, оскільки це з'єднання також марне до його відкриття. Це вимагатиме абстрактного protected Open
методу, оскільки процес відкриття з'єднання може бути специфічним для бази даних. Також було б непогано зробити ConnectionString
властивість читати лише в цьому випадку, оскільки зміна рядка з'єднання після відкриття з'єднання буде безглуздим. (Чесно кажучи, я змусив би його прочитати лише в будь-якому випадку. Якщо ви хочете з'єднатись з іншим рядком, зробіть інший об’єкт.)
Чи потрібен взагалі інтерфейс?
Інтерфейс, який визначає доступні повідомлення, які ви можете надсилати через з'єднання, та типи відповідей, які ви можете отримати назад, може бути корисним. Це дозволить нам написати код, який виконує ці операції, але не пов'язаний з логікою відкриття з'єднання. Але в цьому справа: керування з'єднанням не є частиною інтерфейсу "Які повідомлення я можу надсилати та які повідомлення я можу повернути до / з бази даних?", Тому рядок з'єднання навіть не повинен бути частиною цього інтерфейс.
Якщо ми підемо цим маршрутом, наш код може виглядати приблизно так:
interface IDatabase {
void ExecuteNoQuery(string sql);
void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}
abstract class ConnectionStringDatabase : IDatabase {
public string ConnectionString { get; }
public Database(string connectionString) {
this.ConnectionString = connectionString;
this.Open();
}
protected abstract void Open();
public abstract void ExecuteNoQuery(string sql);
public abstract void ExecuteNoQuery(string[] sql);
//Various other methods all requiring ConnectionString to be set
}