【问题标题】:PHP Domain ModelPHP 领域模型
【发布时间】:2012-09-04 14:07:52
【问题描述】:

我已经使用 PHP 编程多年,过去曾采用自己的方法来处理我的应用程序中的数据。

我过去构建了自己的 MVC,并且对 php 中的 OOP 有合理的理解,但我知道我的实现需要一些认真的工作。

过去我在模型和数据库表之间使用了 is-a 关系。经过一些研究,我现在知道这并不是最好的前进方式。 据我了解,我应该创建并不真正关心底层数据库(或要使用的任何存储机制)但只关心它们的操作和数据的模型。

据此,我已经确定我可以创建模型,例如 Person 这个人对象可能有一些孩子(人类孩子),它们也是保存在数组中的 Person 对象(使用 addPerson 和 removePerson 方法,接受一个 Person 对象)。

然后我可以创建一个 PersonMapper,我可以使用它来获取具有特定“id”的 Person,或保存一个 Person。

然后,这可以在查找表中查找关系数据,并为已请求的 Person(如果有)创建关联的子对象,并同样通过 save 命令将数据保存在查找表中。

这正在挑战我的知识极限.....

如果我想为具有不同楼层和这些楼层内不同房间的建筑物建模怎么办?如果我想在这些房间里放置一些物品怎么办?

我会为建筑、关卡、房间和物品创建一个类

具有以下结构。

建筑物可以有 1 个或多个级别的对象保存在一个数组中 level 可以将 1 个或多个房间对象保存在一个数组中 room 可以有 1 个或多个 item 对象保存在一个数组中

每个类的映射器都具有更高级别的映射器,使用子映射器来填充数组(根据顶级对象的请求或根据请求的延迟加载)

这似乎将不同的对象紧密地耦合在一起,尽管是在一个方向上(即楼层不需要在建筑物中,但建筑物可以有层次)

这是正确的处理方式吗?

在视图中,我想显示一个带有选择级别的选项的建筑物,然后显示带有选择房间等选项的级别。但我可能还想显示一个树状的项目结构建筑物以及他们所在的楼层和房间。

我希望这是有道理的。当 oop 的一般概念似乎是分离事物时,我只是在相互嵌套对象的概念上苦苦挣扎。

如果有人可以提供帮助,那将非常有用。

【问题讨论】:

  • 嵌套听起来不错。您可以尝试在 Programmers Site 上提出同样的问题
  • 抱歉签了帖,以后不签了。
  • 不太清楚代码会有什么帮助。我主要关心的是原理。
  • 接口是你的朋友。您的对象不需要直接耦合在一起,因此建筑物需要关卡,关卡需要房间等等。您希望专注于建筑物可能具有的此类事物的共同方面,然后将它们放入接口并让您的 Room/Level 类都实现它们。您的 Building 类现在所关心的是它可以具有此接口的对象(也可以具有相同接口的对象等......)。如果您想隔离常用功能,请随意使用基本抽象类。
  • 没有得到部分:“这似乎将不同的对象紧密耦合,尽管在一个方向上” 我不记得组合是一种紧密耦合。 .. 这就是构建对象图时的 OOP 特性。

标签: php oop domain-driven-design domain-model object-oriented-database


【解决方案1】:

假设您像这样组织对象:

为了初始化整个建筑对象(包括关卡、房间、物品),您必须提供数据库层类来完成这项工作。获取建筑物树视图所需的所有内容的一种方法是:

缩放浏览器以获得更好的视图

Building 将根据作为参数提供给 initializeById 方法的映射器使用适当的数据初始化自身。这种方法也可以在初始化关卡和房间时起作用。 (注意:在初始化整个建筑物时重用那些 initializeById 方法会导致大量的 db 查询,所以我使用了一点结果索引技巧和 SQL IN opetator)

class RoomMapper implements RoomMapperInterface {

    public function fetchByLevelIds(array $levelIds) {
        foreach ($levelIds as $levelId) {
            $indexedRooms[$levelId] = array();
        }

        //SELECT FROM room WHERE level_id IN (comma separated $levelIds)
        // ...
        //$roomsData = fetchAll();

        foreach ($roomsData as $roomData) {
            $indexedRooms[$roomData['level_id']][] = $roomData;
        }

        return $indexedRooms;
    }

}

现在假设我们有这个数据库架构

最后是一些代码。

建筑

class Building implements BuildingInterface {

    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $name;

    /**
     * @var LevelInterface[]
     */
    private $levels = array();

    private function setData(array $data) {
        $this->id = $data['id'];
        $this->name = $data['name'];
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    public function addLevel(LevelInterface $level) {
        $this->levels[$level->getId()] = $level;
    }

    /**
     * Initializes building data from the database. 
     * If all mappers are provided all data about levels, rooms and items 
     * will be initialized
     * 
     * @param BuildingMapperInterface $buildingMapper
     * @param LevelMapperInterface $levelMapper
     * @param RoomMapperInterface $roomMapper
     * @param ItemMapperInterface $itemMapper
     */
    public function initializeById(BuildingMapperInterface $buildingMapper, 
            LevelMapperInterface $levelMapper = NULL, 
            RoomMapperInterface $roomMapper = NULL, 
            ItemMapperInterface $itemMapper = NULL) {

        $buildingData = $buildingMapper->fetchById($this->id);

        $this->setData($buildingData);

        if (NULL !== $levelMapper) {
            //level mapper provided, fetching bulding levels data
            $levelsData = $levelMapper->fetchByBuildingId($this->id);

            //indexing levels by id
            foreach ($levelsData as $levelData) {
                $levels[$levelData['id']] = new Level($levelData);
            }

            //fetching room data for each level in the building
            if (NULL !== $roomMapper) {
                $levelIds = array_keys($levels);

                if (!empty($levelIds)) {
                    /**
                     * mapper will return an array level rooms 
                     * indexed by levelId
                     * array($levelId => array($room1Data, $room2Data, ...))
                     */
                    $indexedRooms = $roomMapper->fetchByLevelIds($levelIds);

                    $rooms = array();

                    foreach ($indexedRooms as $levelId => $levelRooms) {
                        //looping through rooms, key is level id
                        foreach ($levelRooms as $levelRoomData) {
                            $newRoom = new Room($levelRoomData);

                            //parent level easy to find
                            $levels[$levelId]->addRoom($newRoom);

                            //keeping track of all the rooms fetched 
                            //for easier association if item mapper provided
                            $rooms[$newRoom->getId()] = $newRoom;
                        }
                    }

                    if (NULL !== $itemMapper) {
                        $roomIds = array_keys($rooms);
                        $indexedItems = $itemMapper->fetchByRoomIds($roomIds);

                        foreach ($indexedItems as $roomId => $roomItems) {
                            foreach ($roomItems as $roomItemData) {
                                $newItem = new Item($roomItemData);
                                $rooms[$roomId]->addItem($newItem);
                            }
                        }
                    }
                }
            }

            $this->levels = $levels;
        }
    }

}

等级

class Level implements LevelInterface {

    private $id;
    private $buildingId;
    private $number;

    /**
     * @var RoomInterface[]
     */
    private $rooms;

    private function setData(array $data) {
        $this->id = $data['id'];
        $this->buildingId = $data['building_id'];
        $this->number = $data['number'];
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    public function getId() {
        return $this->id;
    }

    public function addRoom(RoomInterface $room) {
        $this->rooms[$room->getId()] = $room;
    }

}

房间

class Room implements RoomInterface {

    private $id;
    private $levelId;
    private $number;

    /**
     * Items in this room
     * @var ItemInterface[]
     */
    private $items;

    private function setData(array $roomData) {
        $this->id = $roomData['id'];
        $this->levelId = $roomData['level_id'];
        $this->number = $roomData['number'];
    }

    private function getData() {
        return array(
            'level_id' => $this->levelId,
            'number' => $this->number
        );
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    public function getId() {
        return $this->id;
    }

    public function addItem(ItemInterface $item) {
        $this->items[$item->getId()] = $item;
    }

    /**
     * Saves room in the databse, will do an update if room has an id
     * @param RoomMapperInterface $roomMapper
     */
    public function save(RoomMapperInterface $roomMapper) {
        if (NULL === $this->id) {
            //insert
            $roomMapper->insert($this->getData());
        } else {
            //update
            $where['id'] = $this->id;
            $roomMapper->update($this->getData(), $where);
        }
    }

}

物品

class Item implements ItemInterface {

    private $id;
    private $roomId;
    private $name;

    private function setData(array $data) {
        $this->id = $data['id'];
        $this->roomId = $data['room_id'];
        $this->name = $data['name'];
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    /**
     * Returns room id (needed for indexing)
     * @return int
     */
    public function getId() {
        return $this->id;
    }

}

【讨论】:

    【解决方案2】:

    这正在挑战我的知识极限.....

    您描述的建筑物/关卡/房间/物品结构对我来说听起来非常好。领域驱动设计就是要理解你的领域,然后将概念建模为对象——如果你能用简单的语言描述你想要的东西,你就已经完成了你的任务。在设计域时,不要考虑其他所有内容(例如持久性),这样跟踪事情会变得更加简单。

    这似乎将不同的对象紧密耦合在一起,尽管是在一个方向上

    这并没有错。现实世界中的建筑物确实有楼层、房间等,而您只是在模拟这个事实。

    每个类的映射器使用子映射器具有更高级别的映射器

    在 DDD 术语中,这些“映射器”称为“存储库”。此外,如果您的 Building 对象拥有其中的所有楼层/房间/项目,并且如果在没有建筑物的情况下单独加载 Room 没有意义,则它可能被视为“聚合”。在这种情况下,您只需要一个可以加载整个建筑树的BuildingRepository。如果您使用任何现代 ORM 库,它应该会自动为您完成所有映射工作(包括加载子对象)。

    【讨论】:

    • 一个小的修正,聚合是有地板的建筑物等等。建筑物本身至少在某些情况下是聚合根(“主要”对象,它将充当整个聚合的外观)。此外,存储库有点像映射器,但不是 ORM 意义上的。存储库可以使用一个或多个 ORM。顺便说一句,ORM 的实体不是域实体,它们是持久性对象。
    【解决方案3】:

    如果我理解你的问题,你的主要问题是你没有正确使用抽象类。基本上,您应该为每个建筑物、级别、房间等设置不同的类。例如,您应该有一个抽象类 Building,一个由 Building 扩展的抽象类 Levels 等等,这取决于您想要拥有的确切内容,并且就像你有一个树建筑->关卡->房间,但它更像是一个双链表,因为每个建筑都有一个关卡对象数组,每个关卡都有一个建筑对象的父级。您还应该使用界面,因为许多人会忽略它们,它们将在未来对您和您的团队有很大帮助。

    关于以更通用的方式构建模型,我认为最好的方法是创建一个类,该类为您使用的每种数据库或其他存储方法实现相同的方法。例如,您有一个 mongo 数据库和一个 mysql 数据库,您将为每个数据库创建一个类,它们将具有 add、remove、update、push 等方法。确保您不会犯任何错误,一切都会正常工作最好的方法是拥有一个接口数据库来存储方法,并且您最终不会在未定义 mysql 方法的地方使用 mongo 方法。您还可以为公共方法定义一个抽象类(如果有的话)。希望这会有所帮助,干杯!

    【讨论】:

      猜你喜欢
      • 2010-12-20
      • 2014-01-05
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-04
      • 1970-01-01
      相关资源
      最近更新 更多