12

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

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

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

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

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

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

这现在正在突破我的知识范围......

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

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

具有以下结构。

building 可以有 1 个或多个 level 对象保存在一个数组中 level 可以有 1 个或多个 room 对象保存在一个数组中 room 可以有 1 个或多个 item 对象保存在一个数组中

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

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

这是做事情的正确方法吗?

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

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

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

4

3 回答 3

8

假设您像这样组织对象: 在此处输入图像描述

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

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

缩放以获得更好的视图

构建将根据作为参数提供给 initializeById 方法的映射器使用适当的数据初始化自身。这种方法也可以在初始化关卡和房间时起作用。(注意:在初始化整个建筑物时重用那些 initializeById 方法会导致大量的数据库查询,所以我使用了一点结果索引技巧和 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;
    }

}
于 2013-02-11T04:18:53.027 回答
3

这现在正在突破我的知识范围......

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

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

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

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

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

于 2012-09-12T02:34:37.750 回答
0

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

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

于 2012-09-11T19:38:17.723 回答