Эти 2 шаблона проектирования описаны в книге Мартина Фаулера «Шаблоны корпоративных приложений» и представляют собой способы работы с сохранением данных в объектно-ориентированном программировании.
Пример шаблона Active Record
class Foo
{
protected $db;
public $id;
public $bar;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function do_something()
{
$this->bar .= uniqid();
}
public function save()
{
if ($this->id) {
$sql = "UPDATE foo SET bar = :bar WHERE id = :id";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $this->bar);
$statement->bindParam("id", $this->id);
$statement->execute();
}
else {
$sql = "INSERT INTO foo (bar) VALUES (:bar)";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $this->bar);
$statement->execute();
$this->id = $this->db->lastInsertId();
}
}
}
//Insert
$foo = new Foo($db);
$foo->bar = 'baz';
$foo->save();
В этом упрощенном примере, дескриптор базы данных вводится в конструкторе Foo (Использование инъекции зависимостей здесь позволяет тестировать объект без использования реальной базы данных), и Foo использует его, чтобы сохранять свои данные. Do_something — просто метод-заглушка, заменяющий бизнес логику.
Преимущества Active Record
- Писать код с Active Record получается быстро и легко, в том случае, когда свойства объекта прямо соотносятся с колонками в базе данных.
- Сохранение происходит в одном месте, что позволяет легко изучить, как это работает.
Недостатки Active Record
- Модели Active Record нарушаю принципы SOLID. В частности, принцип единой ответственности (SRP — «S» в принципах SOLID). Согласно принципу, доменный объект должен иметь только одну зону ответственности, то есть только свою бизнес-логику. Вызывая его для сохранения данных, вы добавляете ему дополнительную зону ответственности, увеличивая сложность объекта, что усложняет его поддержку и тестирование.
- Реализации сохранения данных тесно связана с бизнес-логикой, а это означает, что если вы позже захотите использовать другую абстракцию для сохранения данных (например для хранения данных в XML-файле, а не в базе данных), то вам придется делать рефакторинг кода.
Пример Data Mapper-а
class Foo
{
public $id;
public $bar;
public function do_something()
{
$this->bar .= uniqid();
}
}
class FooMapper
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function saveFoo(Foo &$foo)
{
if ($foo->id) {
$sql = "UPDATE foo SET bar = :bar WHERE id = :id";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $foo->bar);
$statement->bindParam("id", $foo->id);
$statement->execute();
}
else {
$sql = "INSERT INTO foo (bar) VALUES (:bar)";
$statement = $this->db->prepare($sql);
$statement->bindParam("bar", $foo->bar);
$statement->execute();
$foo->id = $this->db->lastInsertId();
}
}
}
//Insert
$foo = new Foo();
$foo->bar = 'baz';
$mapper = new FooMapper($db);
$mapper->saveFoo($foo);
В данном случае, класс Foo намного проще и должен беспокоиться только о своей бизнес-логике. Он не только не должен сохранять собственные данные, он даже не знает и не заботится о том, все ли его данные были сохранены.
Преимущества Data Mapper-а
- Каждый объект имеет свою зону ответственности, тем самым следую принципам SOLID и сохраняя каждый объект простым и по существу.
- Бизнес-логика и сохранение данных связаны слабо, и если вы хотите сохранять данные в XML-файл или какой-нибудь другой формат, вы можете просто написать новый Mapper, не притрагиваясь к доменному объекту.
Недостатки Data Mapper-а
- Вам придется гораздо больше думать, перед тем как написать код.
- В итоге у вас больше объектов в управлении, что немного усложняет код и его отладку.
Сервис-объекты
При использовании шаблона проектирования Data Mapper, вызывающий код должен выбрать Mapper и бизнес-объект и связать их вместе. Если это код вызова в контроллере, то в конечном счете ваша модель «утекает» в контроллер, что может вызвать большие проблемы при поддержке и юнит-тестировании. Эта проблема может быть решена путем введения объекта-сервиса. Сервис является воротами между контроллером и моделью и связывает доменный объект с Mapper-ом по мере необходимости.
Следует помнить, что M в MVC, представляет собой слой абстракции модели, а не объект модели. Так может быть несколько типов объектов в одной модели (в приведенном выше примере, у вас может быть объект сервиса, доменный объект и объект Mapper-а, выступающие в роли единой модели). С другой стороны, если вы используете модели Active Record, ваша модель может быть представлена лишь одним объектом.
Варианты использования
Объекты Active Record исторически были очень популярны из-за того, что они проще, легче в понимании и быстрее в написании, поэтому многие фреймворки и ORM используют Active Record по умолчанию.
Если вы уверены, что вам никогда не понадобиться менять слой сохранения данных (если вы имеете дело с объектом, который представляет из себя INI-файл, например), или вы имеете дело с очень простыми объектами, в которых не так много бизнес-логики, или просто предпочитаете держать все в небольшом количестве классов, тогда шаблон Active Record это то, что вам нужно.
Использование Data Mapper-а хотя и ведет к более чистому, простому в тестировании и поддержке коду, и обеспечивает большую гибкость, — цена этому, — повышение сложности. Если вы еще не пробовали его использовать, то дайте ему шанс, — вам должно понравиться.
Это перевод статьи Рассела Волкера.
Автор: Corpsee