Использование интерфейсов

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

Сегодня я хочу поговорить об использовании интерфейсов с целью абстрагирования. Я уже говорил, что нужно писать код так, чтобы любой класс можно было легко подменять другим. Это позволит вам легко и без проблемно подменять одни классы другими и мигрировать между технологиями и библиотеками. Хочется использовать BDE, пожалуйста. Хочется использовать ADO, вперед к победе коммунизма. Просто нужно написать два разных движка, каждый из которых будет оптимально использовать свою технологию, и предоставлять всем вашим остальным классам унифицированный доступ к функциям базы данных.

Одним из вариантов абстрагирования являются интерфейсы. Нужно просто объявить интерфейс вида:

// Класс будет предоставлять данные одной строки
class MyDataRow { 
  public List<object> data;
}

interface MyMegaDBInterface {
   // возвращает одну строку
   public MyDataRow FindFirst(string sql, object[] params); 
   // возвращает все строки
   public  List<MyDataRow> FindAll(string sql, object[] params);
   bool DeleteRow(object Key);
   bool DeleteRows(string sql, object[] params);
   bool Insert(...); 
  . . .
  . . .
}

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

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

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

public class Person {
 MyMegaDBInterface db;
 public Person (MyMegaDBInterface db) {
  this.db = db;
  public PersonData GetPersonByName(string firstname, string lastname) {
    MyDataRow  row = db.FindFirst(
             "Select * from Person where FirstName = ? and LastName = ?", 
             firstname, lastname);    
    PersonData personData = new PersonData();
    . . .
    // здесь может быть преобразование объектов MyDataRow в более 
    // читабельный PersonData
    . . .
    return personData;
  }
  . . .
  . . . 
}

Класс получает в качестве параметра конструктора интерфейс, через который происходит доступ к базе данных. Как вы реализуете этот интерфейс, класс Person знать не должен и не будет. Вы можете создать несколько реализаций интерфейса MyMegaDBInterface, оптимизированные под разные базы данных и под разные библиотеки доступа к данным. Потом создаете один такой объект и передаете его всем своим классам движка. Классы движка используют данные методы MyMegaDBInterface для получения данных, формируют удобные для работы объекты с данными и передают их уровню представления – формам WinForms, WPF или даже Web формам.

Теперь нам нужна реализация интерфейса, чтобы все заработало. Создайте класс MyMegaDBSqlServer, который и будет реально соединятся с базой данных и выполнять все методы. Потом создайте статичную переменную (для Delphi это будет глобальная):

MyMegaDBInterface dbImplementation = bew MyMegaDBSqlServer ();

Теперь, при создании любого класса, работающего с базой, просто передавайте ему этот интерфейс и вы в шоколаде. Захотите мигрировать на другую базу данных, просто создадите еще одну реализацию MyMegaDBOracle и измените одну строку коды и вперед к победе коммунизма над глупостью и рукоблудием. Но для этого в ваших формах никогда не должно быть компонентов доступа к базам данных. Я понимаю, что у .NET есть такие чудо распрекрасные компоненты для доступа к данным, но как показывает практика, MS с большим удовольствием любит менять их. То, что последние 5 или около того лет все выглядит стабильно, не значит, что чуда не произойдет завтра.

Уже видно, что кода в таком случае придется писать очень и очень много, потому что очень много вспомогательного кода. Если вы пишите программу всего из одной или двух форм, то на такое издевательство можно забить. Но если вы пишите большой проект класса управления предприятием, который будет существовать не один год, то лучше все же заморочиться. ИТ технологии меняются очень быстро и не зря я привел пример с базами данных. Только у Microsoft было уже около пяти различных библиотек (ODBC, DAO, ADO, ADO.NET, что-то еще там было) и у Delphi были неудачные библиотеки в стиле BDE. Если подвязаться напрямую к одной из них, то переход на следующее поколение в очень крупном проекте без нормального абстрагирвоания может превратиться в ад.

В больших проектах иногда встает вопрос о том, чтобы проекты могли работать одновременно с разными базами данных. Сегодня заказчик хочет MS SQl Server, а завтра он понимает, что нужно Oracle. А кто-то может не захотеть платить деньги и затребует MySQL. Использование интерфейсов позволит без проблем подменять классы доступа к данным и прогрессировать с минимальными потерями.

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

Наследование классов

Не хотите заморачиваться с интерфейсами? В случае с базами данных, на мой взгляд с ними лучше и не заморачиваться. Можно создать базовый класс вместо интерфейса. Этот базовый класс будет реализовывать базовые возможности работы с базами данных, а методы, которые специфичны для баз могут быть объявлены абстрактными и переопределены в потомках.

Такие функции как поиск строки, обновление и удаление выглядят во всех базах данных одинаково, потому что SELECT он и в Африке SELECT. Поэтому такие функции как FindRow, DeleteRow могут быть описаны прямо в базовом классе. Если же у вас какая-то специфичная база, то потомок может и переопределить нафиг любой метод предка, это не запрещено.

Ну а такие специфичные методы как соединение с базой или пейджинг могут быть просто объявлены как абстрактные. А раз класс содержит абстрактные методы, то он должен быть абстрактным, и так мы защитимся от прямого создания недоделанного класса. Именно так я поступил в проекте Database Modeller, исходники которого можно скачать с моего англоязычного блога Database Modeller Source Codes. Этот пример я набросал за несколько дней и просто для интереса, когда пытался приучить себя писать на Java, но так и не смог :( перейти на джаву.



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

Комментарии

Паника, что-то случилось!!! Ничего не найдено в комментариях. Срочно нужно что-то добавить, чтобы это место не оставалось пустым.

Добавить Комментарий

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне