Для примера, несколько экстремального по своей идеологической чистоте:
Во-первых, интерфейс для классов, который может извлекать объекты типа T из базы данных, используя их идентификатор:
interface IAdapter<T>
{
T Retrieve(int id);
}
Теперь класс Book
, который больше не предоставляет открытый конструктор, а вместо этого статический метод, который использует IAdapter<Book>
для извлечения книги из базы данных:
public class Book
{
public static IAdapter<Book> Adapter { get; set; }
public static Book Create(int id)
{
return Adapter.Retrieve(id);
}
// constructor is internal so that the Adapter can create Book objects
internal Book() { }
public int ID { get; internal set; }
public string Title { get; internal set; }
public bool AvailableForCheckout { get; internal set; }
}
Вы должны написать класс, реализующий IAdapter<Book>
самостоятельно, и назначить Book.Adapter
его экземпляру, чтобы Book.Create()
смог извлечь данные из базы данных.
Я говорю «идеологическая чистота», потому что этот дизайн обеспечивает довольно жесткое разделение интересов: в классе Book
нет ничего, что знает, как общаться с базой данных - или даже что является базой данных ,
Например, вот одна из возможных реализаций IAdapter<Book>
:
public class DataTableBookAdapter : IAdapter<Book>
{
public DataTable Table { get; set; }
private List<Book> Books = new List<Book>();
Book Retrieve(int id)
{
Book b = Books.Where(x => x.ID = id).FirstOrDefault();
if (b != null)
{
return b;
}
BookRow r = Table.Find(id);
b = new Book();
b.ID = r.Field<int>("ID");
b.Title = r.Field<string>("Title");
b.AvailableForCheckout = r.Field<bool>("AvailableForCheckout");
return b;
}
}
Какой-то другой класс отвечает за создание и заполнение DataTable
, который использует этот класс. Вы могли бы написать другую реализацию, которая использует SqlConnection
для непосредственного общения с базой данных.
Вы даже можете написать это:
public IAdapter<Book> TestBookAdapter : IAdapter<Book>
{
private List<Book> Books = new List<Book>();
public TestBookAdapter()
{
Books.Add(new Book { ID=1, Title="Test data", AvailableForCheckout=false };
Books.Add(new Book { ID=2, Title="Test data", AvailableForCheckout=true };
}
Book Retrieve(int id)
{
return Books.Where(x => x.ID == id);
}
}
Эта реализация вообще не использует базу данных - вы использовали бы ее при написании модульных тестов для класса Book
.
Обратите внимание, что оба этих класса поддерживают частное свойство List<Book>
. Это гарантирует, что каждый раз, когда вы звоните Book.Create()
с заданным идентификатором, вы получаете один и тот же экземпляр Book
. Вместо этого необходимо сделать аргумент для того, чтобы сделать эту функцию классом Book
- вы бы создали статическое приватное свойство List<Book>
в Book
и написали логику, чтобы метод Create
поддерживал его.
Вы используете тот же подход для отправки данных обратно в базу данных - добавьте методы Update
, Delete
и Insert
к IAdapter<T>
и внедрите их в свои классы адаптера, а Book
вызовите эти методы в соответствующее время.