4

我对如何“正确”编写我的应用程序有疑问。

我想遵循 service/DAO/mapper/entity 模型,我认为我完全理解它,但我发现我想做的一些事情可能与这个模型的一般概念相冲突。

我有这些数据库表: 产品表及其相关表 - 制造商

我有简单的 ProductDbMapper ,它从数据库中获取产品的数据并将它们作为产品实体返回,所以我可以像这样使用它:

echo $productEntity->getName();
$productEntity->setPrice(29.9);

我认为能够像这样使用 somethink 会很棒:

echo $productEntity->getManufacturer()->getName();

“getManufacturer()”方法将简单地通过 id 查询制造商的另一个服务/DAO。我知道如何获得产品制造商的正确方法是:

$product = $productService->getProduct(5);
$manufacturer = $manufacturerService->getManufacturerByProduct($product);

但我认为“流畅”的解决方案要简单得多——它易于理解且使用起来很有趣。其实这很自然。我试图通过回调来实现这一点。我将调用制造商服务的回调传递给我的 ProductMapper 中的 Product 实体。

问题是看起来我正在尝试遵循 5 层模型,同时我正在尝试避免它。所以我的问题是:这看起来像一个好的解决方案吗?是否有意义?我怎样才能以更好的方式实现相同的(魔术)?

4

3 回答 3

9

如果您想坚持使用 Data Mapper 模式,您可以从数据库中加载产品及其所有依赖项(制造商、库存、税收),也可以使用延迟加载。第一个选项不是一个好习惯。但是要使用延迟加载,您需要通过虚拟代理获得一个额外的层。

为什么?因为否则你将不得不在你的实体中放置一些数据库代码,而关于这些层的整个想法就是解耦。

那么,什么是虚拟代理?

根据 Martin Fowler (2003) 的说法:

虚拟代理是一个看起来像应该在字段中的对象,但实际上不包含任何内容的对象。只有当它的方法之一被调用时,它才会从数据库中加载正确的对象。

例子

该接口定义了将由真实实体和虚拟代理实现的方法:

// The interface
interface ManufacturerInterface
{
    public function getName();
}

这是实体,您可能也在扩展一些通用模型类:

// The concrete Manufacturer class
class Manufacturer implements ManufacturerInterface
{
    private $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

这是代理:

// The proxy
class ManufacturerProxy implements ManufacturerInterface
{
    private $dao;
    private $manufacturer;
    private $manufacturerId;

    public function __construct($dao, $manufacturerId)
    {
        $this->dao = $dao;

        // set manufacturer to NULL to indicate we haven't loaded yet
        $this->manufacturer = null;
    }

    public function getName()
    {
        if ($this->manufacturer === null)
        {
            $this->lazyLoad();
        }
        return $this->manufacturer->getName();
    }

    private function lazyLoad()
    {
        $this->manufacturer = $this->dao->getById($this->manufacturerId);
    }
}

当实例化与制造商有某种关系的其他类(如产品)时,数据映射器将使用代理。因此,当一个产品被实例化时,它的字段被填充,并且制造商字段接收到 ManufacturerProxy 的实例而不是 Manufacturer。

Data Mapper 的实现会有所不同,所以我举一个简单的例子:

class ProductMapper {
    private $dao;
    private $manufacturerDao;

    // .......

    public function find($id) {
        // call the DAO to fetch the product from database (or other source)
        // returns an associative array
        $product = $this->dao->find($id);
        
        // instantiate the class with the associative array
        $obj = new Product($product);
        
        // create the virtual proxy
        $obj->manufacturer = new ManufacturerProxy($this->manufacturerDao, $product['manufacturer_id']);
        
        return $obj;
    }
}

正如我所说,上面的例子非常简单。在您使用 DAO 时,我已将其包含在内,但像 Martin Fowler 这样的作者将处理 SQL 查询或任何其他底层技术的任务交给了 Data Mapper。但是也有像 Nock (2004) 这样的作者同时使用了 Data Mapper 和 Data Accessor。

使用更复杂的 Data Mapper,您只能拥有 XML 或 YAML 等语言的文件,类似 Doctrine 或 Hibernate。

刚刚结束,服务:

class ProductService {
    private $productMapper;
    
    /**
     * Execute some service
     * @param {int} $productId The id of the product
     */
    public function someService($producId) {
        // get the product from the database
        // in this case the mapper is handling the DAO
        // maybe the data mapper shouldn't be aware of the DAO
        $product = $this->productMapper->find($productId);
        
        // now the manufacturer has been loaded from the database
        $manufacturerName = $product->manufacturer->getName();
    }
}

也许您可以让 DAO 调用 Data Mapper 来创建实体,然后在服务中使用 DAO。它实际上更简单,因为您不需要重复两次方法,但这取决于您。

替代方案

如果你不想实现虚拟代理,但仍然想要一个流畅的界面,你可以切换到 Active Record 模式。有一些库可以完成这项工作,例如PHP-ActiveRecordParis。或者如果你想自己做,你可以看看这里这里。Martin Fowler 的书也是一个好的开始。

参考

福勒,马丁。企业应用架构模式。艾迪生-韦斯利,2003 年。诺克,克利夫顿。数据访问模式:面向对象应用程序中的数据库交互。艾迪生-韦斯利,2004 年。

于 2012-12-01T20:22:42.577 回答
3

几天来我一直在做一些研究,我得出的结论是,实现我想要的最好方法是停止尝试自己编写代码,而只是学习使用现有的 ORM。因为我一直在尝试做的一切都是创建我自己的 ORM。我知道这是个坏主意。我会坚持教义2。

于 2012-12-07T18:07:51.457 回答
1

不要误会我的意思,我不想成为批评家。为什么不加入这两个表,然后只让一个类扩展另一个类?就像那样,您将拥有$this->manufacturer_name = $row['name']. 对于其他制造商行也是如此。

于 2012-12-01T18:45:49.847 回答