如何避免模型臃肿与雄辩的模型?

时间:2016-01-11 07:21:43

标签: php laravel

我一直在努力在闲暇时间在Laravel做一个小游戏,而且我遇到了如何最好地构建应用程序以及某些模型变得非常臃肿的问题。

我目前正在使用雄辩的模型并将功能直接附加到它们上面。例如,我有一个User模型,它以下面的函数开始

$user->verify()
$user->creditGold()
$user->debitGold()

这看似合理。当我向网站添加功能时,课程开始变得越来越笨重,例如:

$user->creditItem()
$user->debitItem()
$user->equipItem()
$user->unequipItem()
$user->moveToTown()
$user->takeQuest()
$user->ban()
$user->unban()
// etc. etc. 

有很多代码感觉非常无关,已被推入这一类并且它非常混乱。

我开始做的是制作由User类实例化并保存的辅助模型。以下示例

$user->inventory()->creditItem()
$user->inventory()->debitItem()
$user->inventory()->useItem()

调用和使用很容易,但感觉不正确。

有没有人建议如何最好地分解大量代码,这些代码在概念上属于同一个实体?我喜欢functionality being coupled with data的想法,因为我认为这是理解OO的最自然的方式,但是对我来说,将代码抽象到服务层并且具有将用户作为参数的服务类更好。反而采取行动(即$service->giveItemToUser($user, $item))?

3 个答案:

答案 0 :(得分:5)

这就是SoC(关注点分离)原则变得非常重要的地方。这意味着确保您的应用程序的每个部分仅关注它需要关注的内容。

关注点分离

让我们首先确定您User课程中的一些问题。

  1. 库存
  2. 设备
  3. 任务
  4. 以上是您的用户将使用的一般资源。其中每一项都有他们关心的事情:

    1. 库存
      • 项目
    2. 设备
      • 项目
    3. 任务
      • 任务
    4. 您已经可以看到我们有几个用户需要相同信息的单独部分。

      将逻辑与状态分开

      在这个阶段,我们现在需要将其他一些问题分开。具体来说,业务逻辑(我们想要对数据做什么)和数据访问层本身(ORM / Models)。就个人而言,我喜欢使用存储库模式将这些事物分开。适用于模型并关注整体逻辑和应用程序过程的类。我觉得模型是状态的表示,应该只担心获取或持久化状态。

      所以我把这些东西分开了:

      1. 模型

        • 用户
        • 项目
        • 任务
      2. 存储库(依赖项)

        • UserRepository(用户,物品,库存,设备,任务)
        • InventoryRepository(Item)
        • EquipmentRepository(Item,Collection)
        • QuestRepository(Quest)
      3. 代码示例

        现在,这为我提供了我想要的设置和组织的明确定义。但是让我们举一些示例代码。这与数据如何持久化无关(手动或通过Eloquent关系等)。

        <?php namespace App\Repositories;
        
        use App\Models\Item;
        use Illuminate\Support\Collection;
        
        class Inventory {
        
            protected $contents;
        
            public function __construct(Item $item, Collection $contents)
            {
                $this->item = $item;
                $this->contents = $contents;
            }
        
            public function add(Item $item)
            {
                $this->contents->push($item);
            }
        
            public function remove(Item $item)
            {
                $this->contents->forget($item->id);
            }
        
            public function contains(Item $item)
            {
                return $this->contents->has($item->id);
            }
        }
        

        InventoryRepository仅关注管理其项目集合。添加它们,删除它们并检查是否存在其他项目。要做到这一点,它取决于Collection类和Item模型。

        <?php namespace App\Repositories;
        
        use App\Models\Item;
        
        class Equipment {
        
            protected $slots = [
                'head' => null,
                'body' => null,
                'legs' => null,
                'feet' => null,
                'arms' => null,
            ];
        
            public function __construct(Item $item)
            {
                $this->item = $item;
            }
        
            public function get($slot)
            {
                return $this->slots[$slot];
            }
        
            public function set($slot, Item $item)
            {
                $this->slots[$slot] = $item;
            }
        
            public function empty($slot)
            {
                $this->slots[$slot] = null;
            }
        
            public function hasEquipment($slot)
            {
                return !empty($this->get($slot));
            }
        
            public function isEquipped(Item $item) 
            {
                if ($this->hasEquipment($item->slot))
                {
                    return $this->get($item->slot)->id == $item->id;
                }
            }
        }
        

        另一个班只关注目前装备的物品。装备,装备等

        全力以赴

        一旦您定义了单独的部分,就可以将它们全部带入您的UserRepository类。通过将它们作为依赖项引入,UserRepository中包含的代码将显式地基于用户管理,而访问加载的依赖项将为您提供所需的所有功能。

        <?php App\Repositories;
        
        use App\Models\User;
        use App\Repositories\Inventory;
        use App\Repositories\Equipment;
        
        class User {
        
            protected $user;
            protected $quests;
            protected $equipment;
            protected $inventory;
        
            public function __construct(
                User $user,
                Quests $quests,
                Equipment $equipment,
                Inventory $inventory
            ) {
                $this->user = $user;
                $this->quests = $quests;
                $this->equipment = $equipment;
                $this->inventory = $inventory;
            }
        
            public function equip(Item $item)
            {
                if ($this->inventory->contains($item))
                {
                    $this->equipment->set($item->slot, $item);
                }
            }
        
            public function unequip(Item $item)
            {
                if ($this->equipment->isEquipped($item)) 
                {
                    $this->equipment->empty($item->slot);
                }
            }
        }
        

        这又是组织代码的概念。在这种类型的设置中,您希望如何将数据加载并保存到数据库。这也不是组织代码的唯一方法。这里的内容是如何将代码分解为单独的部分和关注点,以便更好地模块化和隔离功能,以便更容易管理和消化位。

        我希望这很有帮助,不要犹豫,提出任何问题。

答案 1 :(得分:2)

我认为您的用户有很多责任。看看SOLID原理。从单一责任原则开始。 因此,请从用户库存操作中取出,并将库存责任放入库存服务中。

答案 2 :(得分:0)

是的,我会将业务逻辑抽象出服务或存储库层。

这是一篇好文章:

https://bosnadev.com/2015/03/07/using-repository-pattern-in-laravel-5/

传递具有附加功能的模型类的一个问题是模型类中固有的膨胀。基本上每个模型类都带有整个数据库模型的权重,这非常重。这是典型的Active Record ORM实现,如Eloquent,而不是像Doctrine这样的Data Mapper风格的ORM。但是,我猜测你已经咬了Laravel子弹,或多或少地与Eloquent结合了。

我发现的一件事,即失去数据模型的重量,但保持OO风格的界面是使用Laravel的Fluent类。为了帮助我,我在这里写了一个小扩展:

https://packagist.org/packages/delatbabel/fluents

使用:

将其拉入
composer require delatbabel/fluents