You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Laravel中实现Uncle Bob的零参数方法原则?

问题:如何在Laravel中贴近《Clean Code》的零参数方法原则?

我正尝试应用Uncle Bob在《Clean Code》一书中提出的原则——方法的最优参数数量为零。我发现这很难实现,但仍想尝试,希望能得到帮助,了解如何在我的代码中尽可能贴近这一原则。

当前代码

控制器代码

<?php

namespace App\Http\Controllers\Cms;

use App\Http\Controllers\Controller;
use App\Http\Requests\GroupRequest;
use App\Models\Group;
use App\Services\GroupsService;
use Illuminate\Http\Request;

class GroupsController extends Controller
{
  protected $group_service;

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

  public function index()
  {
    $groups = $this->group_service->listAll();

    return view('cms.groups.index', compact('groups'));
  }

  public function store(GroupRequest $request)
  {
    $result = $this->group_service->createGroupWith($request->all());

    return redirect()->back()->with('message', $result['msg']);
  }
 
  public function update(GroupRequest $request, Group $group)
  {
    $result = $this->group_service->updateAGroupWith($request->all(), $group);

    return redirect()->back()->with('message', $result['msg']);
  }
}

服务类代码

<?php

namespace App\Services;

use App\Models\Group;
use Illuminate\Support\Facades\DB;

class GroupsService
{
  public function listAll()
  {
    $groups = Group::all();

    return $groups;
  }

  public function createGroupWith($data)
  {
    try {
      DB::beginTransaction();

      $modules_id = array_pop($data);

      $group = Group::create($data);
      $group->modules()->attach($modules_id);

      DB::commit();
      return ['msg' => 'Grupo criado com sucesso!'];
    } catch (\Throwable $error) {
      DB::rollBack();
      return ['msg' => $error->getMessage()];
    }
  }

  public function updateAGroupWith($data, $group)
  {
    try {
      DB::beginTransaction();

      $modules_ids = array_pop($data);

      $group->update($data);
      $group->modules()->sync($modules_ids);

      DB::commit();
      return ['msg' => 'O grupo foi atualizado com sucesso!'];
    } catch (\Throwable $error) {
      DB::rollBack();
      return ['msg' => $error->getMessage()];
    }
  }
}

如你所见,Laravel在控制器的__construct方法中实例化了服务类,并在其他方法中使用。问题在于,存储或更新记录时,我需要传递数据(更新时还需传递要更新的Group实例),这违反了Uncle Bob的零参数理想。因此我想问:是否有解决办法?

我想到的几个方案

  1. 提前设置服务类属性:继续按现有方式实例化服务类,但在调用方法前设置group和data属性
public function update(GroupRequest $request, Group $group)
{
  $this->group_service->group = $group;
  $this->group_service->data = $request->all();
  $result = $this->group_service->updateAGroup();

  return redirect()->back()->with('message', $result['msg']);
}

但问题是,如果严格遵循理想,我不确定是否能随意修改对象属性,我认为应该在实例化时就定义好对象状态。

  1. 按需实例化服务类:在每个方法中按需实例化服务类
public function update(GroupRequest $request, Group $group)
{
  $service = new GroupService($data, $group);
  $result = $service->updateAGroup();

  return redirect()->back()->with('message', $result['msg']);
}

唯一的问题是,在存储方法中,需要将GroupService构造函数的$group参数设为可选,我不知道这是否真的是问题,还是我的执念。

  1. 拆分服务类:为每种操作(存储、更新、删除等)单独创建服务类,这样就不用考虑构造函数的可选参数问题,但这似乎有点过度设计。

无论如何,我想了解你们的想法、建议和批评。请注意,我知道这非常理想化,但我确实想知道如何利用Laravel已有的模式尽可能贴近这一原则。


解答

首先得明确:Uncle Bob的零参数原则是理想方向,不是必须死守的教条,实际开发中要结合框架特性和业务场景灵活调整,避免为了凑零参数而写出更难维护的代码。针对你的场景,下面是几个落地的思路:

思路1:用「命令模式」适配零参数原则

Laravel的服务容器很适合实现命令模式,把每个操作封装成独立的命令类,命令类在构造时接收所需的所有依赖(数据、模型实例等),然后执行方法就是零参数。

示例实现

1. 创建CreateGroupCommand命令类

namespace App\Commands\Groups;

use App\Models\Group;
use Illuminate\Support\Facades\DB;

class CreateGroupCommand
{
    private $data;

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

    public function execute()
    {
        try {
            DB::beginTransaction();

            $modules_id = array_pop($this->data);
            $group = Group::create($this->data);
            $group->modules()->attach($modules_id);

            DB::commit();
            return ['msg' => 'Grupo criado com sucesso!'];
        } catch (\Throwable $error) {
            DB::rollBack();
            return ['msg' => $error->getMessage()];
        }
    }
}

2. 创建UpdateGroupCommand命令类

namespace App\Commands\Groups;

use App\Models\Group;
use Illuminate\Support\Facades\DB;

class UpdateGroupCommand
{
    private $group;
    private $data;

    public function __construct(Group $group, array $data)
    {
        $this->group = $group;
        $this->data = $data;
    }

    public function execute()
    {
        try {
            DB::beginTransaction();

            $modules_ids = array_pop($this->data);
            $this->group->update($this->data);
            $this->group->modules()->sync($modules_ids);

            DB::commit();
            return ['msg' => 'O grupo foi atualizado com sucesso!'];
        } catch (\Throwable $error) {
            DB::rollBack();
            return ['msg' => $error->getMessage()];
        }
    }
}

3. 控制器中调用命令

// store方法
public function store(GroupRequest $request)
{
    $command = new CreateGroupCommand($request->all());
    $result = $command->execute();

    return redirect()->back()->with('message', $result['msg']);
}

// update方法
public function update(GroupRequest $request, Group $group)
{
    $command = new UpdateGroupCommand($group, $request->all());
    $result = $command->execute();

    return redirect()->back()->with('message', $result['msg']);
}

这种方式完全符合零参数方法的要求:每个命令的execute()方法没有参数,所有依赖都在构造时注入,而且每个命令只负责单一操作,符合单一职责原则。

思路2:改进你的方案2,让服务类更专注

你提到的按需实例化服务类的思路没问题,不用纠结构造函数的可选参数,反而可以把服务类拆分成「创建专用」和「更新专用」的小服务,比一个大而全的GroupsService更清晰:

示例实现

1. 创建GroupCreator服务类

namespace App\Services\Groups;

use App\Models\Group;
use Illuminate\Support\Facades\DB;

class GroupCreator
{
    private $data;

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

    public function create()
    {
        // 原createGroupWith的逻辑
        try {
            DB::beginTransaction();

            $modules_id = array_pop($this->data);
            $group = Group::create($this->data);
            $group->modules()->attach($modules_id);

            DB::commit();
            return ['msg' => 'Grupo criado com sucesso!'];
        } catch (\Throwable $error) {
            DB::rollBack();
            return ['msg' => $error->getMessage()];
        }
    }
}

2. 创建GroupUpdater服务类

namespace App\Services\Groups;

use App\Models\Group;
use Illuminate\Support\Facades\DB;

class GroupUpdater
{
    private $group;
    private $data;

    public function __construct(Group $group, array $data)
    {
        $this->group = $group;
        $this->data = $data;
    }

    public function update()
    {
        // 原updateAGroupWith的逻辑
        try {
            DB::beginTransaction();

            $modules_ids = array_pop($this->data);
            $this->group->update($this->data);
            $this->group->modules()->sync($modules_ids);

            DB::commit();
            return ['msg' => 'O grupo foi atualizado com sucesso!'];
        } catch (\Throwable $error) {
            DB::rollBack();
            return ['msg' => $error->getMessage()];
        }
    }
}

3. 控制器调用

// store方法
public function store(GroupRequest $request)
{
    $creator = new GroupCreator($request->all());
    $result = $creator->create();

    return redirect()->back()->with('message', $result['msg']);
}

// update方法
public function update(GroupRequest $request, Group $group)
{
    $updater = new GroupUpdater($group, $request->all());
    $result = $updater->update();

    return redirect()->back()->with('message', $result['msg']);
}

这种拆分不算过度设计,反而让每个服务类的职责更单一,方法也都是零参数,完全贴合你想要的原则,同时代码可读性和可维护性更强。

思路3:对方案1的优化——避免直接修改服务类属性

如果你不想拆分服务类,又想贴近零参数,那可以给GroupsService添加「设置器方法」,而不是直接修改属性,这样更符合面向对象的封装原则:

示例实现

服务类修改

namespace App\Services;

use App\Models\Group;
use Illuminate\Support\Facades\DB;

class GroupsService
{
    private $group;
    private $data;

    public function setGroup(Group $group)
    {
        $this->group = $group;
        return $this; // 链式调用
    }

    public function setData(array $data)
    {
        $this->data = $data;
        return $this; // 链式调用
    }

    public function listAll()
    {
        return Group::all();
    }

    public function createGroup()
    {
        try {
            DB::beginTransaction();

            $modules_id = array_pop($this->data);
            $group = Group::create($this->data);
            $group->modules()->attach($modules_id);

            DB::commit();
            return ['msg' => 'Grupo criado com sucesso!'];
        } catch (\Throwable $error) {
            DB::rollBack();
            return ['msg' => $error->getMessage()];
        }
    }

    public function updateGroup()
    {
        try {
            DB::beginTransaction();

            $modules_ids = array_pop($this->data);
            $this->group->update($this->data);
            $this->group->modules()->sync($modules_ids);

            DB::commit();
            return ['msg' => 'O grupo foi atualizado com sucesso!'];
        } catch (\Throwable $error) {
            DB::rollBack();
            return ['msg' => $error->getMessage()];
        }
    }
}

控制器调用

// store方法
public function store(GroupRequest $request)
{
    $result = $this->group_service->setData($request->all())->createGroup();

    return redirect()->back()->with('message', $result['msg']);
}

// update方法
public function update(GroupRequest $request, Group $group)
{
    $result = $this->group_service->setGroup($group)->setData($request->all())->updateGroup();

    return redirect()->back()->with('message', $result['msg']);
}

这种方式通过封装的设置器来传递依赖,避免直接修改属性,同时保持方法零参数,也利用了Laravel依赖注入的特性,不用每次都实例化服务类。


最后提醒

零参数原则的核心是减少方法的耦合度,让方法的依赖更清晰,而不是为了零参数而写晦涩的代码。在Laravel中,结合命令模式、单一职责的小服务类,既能贴近这个原则,又能利用框架的特性让代码更优雅。

内容的提问来源于stack exchange,提问作者Bernardo Benini Fantin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 05:05:52