2011-07-07 44 views
3

我正在研究PHP中的一个复杂项目,并继续遇到同样的问题:如何将单独的对象分开?PHP:保持对象分离?

OO编程背后的想法是没有任何对象需要知道任何其他对象如何在内部工作。但是对于第三个正常的数据库,所有内容都在一个单独的表中,并且所有表都是相互关联的。

比方说,我有几个对象。一个订单对象(带有订单类和几个订单相关的数据库表),具有地址类和地址表的地址对象),项目(表和类)和客户(表和类)。

许多任务(如发送订单)需要来自所有上述表的数据,并且所有数据都通过外键互连。那么加载所需的所有对象/数据的最优雅/有效的方式是什么,而不会使代码成为灾难或违反面向对象的原则?

解决方法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的角度来看,它效率低下,做很多查询,可以获得所有需要的数据。不确定性能是否可以忽略不计。另外,对象永远不知道它是由控制器还是其他对象创建的,因为对象可以在任何地方生成。当你需要使用包含多个对象的事务处理多个事务时,这是特别有问题的。呼叫“开始”和“提交”的地方去了哪里?作为一个对象,我已经在一个事务中,还是我需要自己创建一个?由于方法用于多个特定任务,因此这种不一致可能会发生冲突。有时候一种方法需要创建交易,有时候它已经在交易中了。

方法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(); 
    } 
} 

这解决了通过将数据库事务置于对象之外来启动和完成数据库事务的一致性问题。此外,对于实例化对象的位置不存在混淆,因为所有对象都必须由控制器创建。但是这看起来很sl,,并且需要每个控制器方法都知道Order对象需要什么才能工作。

有没有干净的方法来做到这一点?

回答

2

对不起,你已经陷入了一个众所周知的陷阱。
对象不应该镜像DB。 订单对象需要取得全部相关数据才能将订单处理成自己。
他绝对可以使用其他类/对象(树对象来获得正确订购的订单项目,数据库连接类,Sql抽象类等)
如果可以,应该将数据加载到一个查询中。每个表格没有一个查询。
如果你想采用的范例,每个数据源一个查询我会说关系数据库不是你想要的,而是一个NoSQL DB(比如MongoDB),但是你也可以将只需要使用一个对象来处理该命令,因为MongoDB非常强制执行此操作。

+0

但是如果地址对象需要某种处理才能正常工作呢?订单对象(以及任何其他使用地址对象的对象)需要知道这一点,并处理地址本身或调用特定的方法,这两种方法都需要外部对象知道地址对象的内部工作方式。 – Nick

+0

@Nick - 就是这样,在这里你确定了一个单独的进程(它应该从多个地方读取数据,可能是)在其他几个进程中使用 - >开头并将整个逻辑封装在一个对象中,然后移动周围的谁需要一个地址读取/解析/保存/搜索等没有什么错误的订单对象内部使用地址对象。 Address对象由Order对象实例化,Order对象应该具有原始地址数据,该数据在常规查询中获取。 –