PHP: держать объекты отдельно? - PullRequest
3 голосов
/ 07 июля 2011

Я работаю над сложным проектом на PHP и продолжаю сталкиваться с одной и той же проблемой: как разделить отдельные объекты?

Идея ОО-программирования заключается в том, что ни одному из объектов не нужно знать, какдругой объект работает внутри.Но с третьей нормальной базой данных все находится в отдельной таблице, и все таблицы взаимосвязаны.

Допустим, у меня есть несколько объектов.Объект Order (с классом Order и несколькими таблицами базы данных, относящимися к Order), Объекты Address с классом Address и таблицами Address), Items (таблицы и класс) и Customers (таблица и класс).

Многие задачиНапример, при отправке заказа требуются данные из всех вышеперечисленных таблиц, и все данные связаны между собой посредством внешних ключей.Итак, каков самый элегантный / эффективный способ загрузки всех необходимых объектов / данных, без превращения кода в катастрофу или нарушения принципов ОО?

Подход A : один класс выполняет одинзапрашивать и загружать другие классы в себя.

class Order {

   public function LoadFromID($OrderID)
   {
      SELECT * FROM Orders
      LEFT JOIN Customers
      LEFT JOIN Addresses
      LEFT JOIN Items

      $this->oCustomer = new Customer();
      $this->oCustomer->SetName($W->CustomerName);

      $this->oAddress = new Address();
      $this->oAddress->SetCity($W->City);

      $this->oaItems = array();
      foreach($w->Items AS $Item)
      {
         $oItem = new Item();
         $oItem->SetName($Item->ItemName);
         $this->oaItems[] = $oItem;
      }
   }
}

Проблема заключается в том, что классу Order необходимо знать, как устроены таблицы Customers, Items и Addresses.Что если мы хотим пойти другим путем и заставить адресный объект найти Приказы и Продавцов, которые к нему прикреплены?Поскольку все взаимосвязано, каждый объект должен знать, как структурированы таблицы каждого другого объекта.Это означает, что если вы хотите добавить новое поле в таблицу, вам придется находить и редактировать эти запросы внутри каждого отдельного объекта.Это создает огромную проблему обслуживания.

Подход B : Один класс создает себя и указывает другим классам создавать себя внутри себя.

class Order {

   public function LoadFromID($OrderID)
   {
      SELECT * FROM Orders
      $W = mysql_row();

      // A customer object is instantated and passed
      // a customer ID to load from.
      $this->oCustomer = new Customer($W->CustomerID); 
      $this->oShipAddress = new Address($W->ShipAddressID);
      $this->oBillAddress = new Address($W->BillAddressID);

      $this->oaItems = array();
      foreach($w->Items AS $Item)
      {
         $this->oaItems[] = new Item($Item->ItemID);
      }
   }
}

class Customer{
   public function __construct($CustID)
   {
      SELECT * FROM Customers WHERE $CustID;
      $W = mysql_row();

      $this->CustomerName = $W->Name;
      ...
   }
}

Этот метод разделяет объекты, где только класс может получить доступ к своим собственным таблицам, но создает новые проблемы.Во-первых, это неэффективно с точки зрения MySQL, он выполняет много запросов, где можно получить все необходимые данные.Не уверен, что производительность незначительна или нет.Кроме того, объект никогда не знает, был ли он создан контроллером или другим объектом, поскольку объекты могут быть созданы где угодно.Это особенно проблематично, когда вам нужно использовать транзакции, которые оборачиваются вокруг нескольких объектов, делая несколько вещей.Куда идет вызов «begin» и «commit»?Как объект, я уже нахожусь в транзакции, или мне нужно начать ее самостоятельно?Это несоответствие может конфликтовать, так как метод используется для более чем одной конкретной задачи.Иногда метод должен создать транзакцию, а иногда он уже внутри нее.

Подход C : контроллер создает все объекты.

class Controller {

   public function DoSomething()
   {
      $DB->Begin();
      $oOrder=new Order();
      $aAddressIDs = $oOrder->GetAddressIDs();
      $CustomerID = $oOrder->GetCustomerID();

      foreach($aAddressIDs AS $ID)
      {
         // Take the order's address IDs 
         // and turn them into adress objects then 
         // pass them back to the order.
         $oAddress = new Address($ID);
         $oOrder->AddAddress($oAddress);
      }

      // Create a customer ID for the order
      // and pass the built object back into the order.
      $oCustomer = new Customer($CustomerID);
      $oOrder->AddCustomer($oCustomer);

      if($oOrder->DoSomethingComplex())
         $DB->Commit();
      else
         $DB->Rollback();
   }
}

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

Есть ли какой-нибудь чистый способ сделать это?

1 Ответ

2 голосов
/ 07 июля 2011

Извините, вы попали в хорошо известную ловушку.
Объекты не должны отражать БД . Объект заказа должен получить все соответствующие данные для обработки заказа в себя.
Он, безусловно, может использовать другие классы / объекты (объект дерева, чтобы правильно упорядочить элементы порядка, класс соединения БД, класс абстракции Sql и т. Д.)
Если вы можете, вы должны загрузить данные в одном запросе. Не один запрос на таблицу.
Если вы хотите использовать парадигму один запрос на один источник данных , я бы сказал, что реляционная БД - это не то, что вам нужно, а скорее БД NoSQL (например, MongoDB), но и там. вам нужно будет использовать только один объект для обработки заказа, поскольку MongoDB в значительной степени обеспечивает это.

...