我正在用 PHP 开发一个复杂的项目,但一直遇到同样的问题:如何将单独的对象分开?
面向对象编程背后的思想是,任何对象都不需要知道任何其他对象的内部工作方式。但是对于第三个普通数据库,所有内容都在一个单独的表中,并且所有表都是相互关联的。
假设我有几个对象。 Order 对象(带有 Order 类和几个 Order 相关的数据库表)、带有 Address Class 和 Address 表的 Address 对象)、Items(表和类)和 Customers(表和类)。
许多任务(例如运送订单)需要来自上述所有表的数据,并且所有数据都通过外键互连。那么,在不使代码成为灾难或违反 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 的角度来看,它效率低下,需要执行许多查询才能获得所有需要的数据。不确定性能是否可以忽略不计。此外,一个对象永远不知道它是由 Controller 还是另一个对象创建的,因为对象可以在任何地方生成。当您需要使用围绕多个对象执行多个操作的事务时,这尤其成问题。 “开始”和“提交”的调用在哪里?作为一个对象,我是否已经在交易中,或者我需要自己开始交易?当方法用于多个特定任务时,这种不一致可能会发生冲突。有时一个方法需要创建事务,有时它已经在其中。
方法 C: Controller 实例化所有对象。
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();
}
}
这解决了有关启动和完成数据库事务的一致性问题,方法是将它们放在对象之外。此外,不会混淆对象的实例化位置,因为所有对象都必须由 Controller 创建。但这看起来真的很草率,并且需要每个 Controller 方法都知道 Order 对象需要什么才能运行。
有什么干净的方法可以做到这一点吗?
最佳答案
抱歉,您掉进了一个众所周知的陷阱。
对象不应镜像数据库。
订单对象需要获取所有相关数据,才能将订单处理到自己身上。
他绝对可以使用其他类/对象(一个树对象来正确排序订单项目,一个数据库连接类,一个 Sql 抽象类等)
如果可以,您应该在一次查询中加载数据。不是每个表一个查询。
如果您想采用每个数据源一个查询的范例,我会说关系数据库不是您想要的,而是 NoSQL 数据库(例如 MongoDB),但是同样,您将只需要使用一个对象来处理订单,因为 MongoDB 几乎强制执行了这一点。
关于PHP:保持对象分离?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6604774/
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调
我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser
我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的rubyyaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir
假设我有一个FireNinja我的数据库中的对象,使用单表继承存储。后来才知道他真的是WaterNinja.将他更改为不同的子类的最干净的方法是什么?更好的是,我很想创建一个新的WaterNinja对象并替换旧的FireNinja在数据库中,保留ID。编辑我知道如何创建新的WaterNinja来self现有FireNinja的对象,我也知道我可以删除旧的并保存新的。我想做的是改变现有项目的类别。我是通过创建一个新对象并执行一些ActiveRecord魔法来替换行,还是通过对对象本身做一些疯狂的事情,或者甚至通过删除它并使用相同的ID重新插入来做到这一点,这是问题的一部分。
根据ActiveRecord::Base的文档:==(comparison_object)Returnstrueifcomparison_objectisthesameexactobject,orcomparison_objectisofthesametypeandselfhasanIDanditisequaltocomparison_object.id.Notethatnewrecordsaredifferentfromanyotherrecordbydefinition,unlesstheotherrecordisthereceiveritself.Besides,ifyoufet