如何在Laravel中实现Uncle Bob的零参数方法原则?
我正尝试应用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的零参数理想。因此我想问:是否有解决办法?
我想到的几个方案
- 提前设置服务类属性:继续按现有方式实例化服务类,但在调用方法前设置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']); }
但问题是,如果严格遵循理想,我不确定是否能随意修改对象属性,我认为应该在实例化时就定义好对象状态。
- 按需实例化服务类:在每个方法中按需实例化服务类
public function update(GroupRequest $request, Group $group) { $service = new GroupService($data, $group); $result = $service->updateAGroup(); return redirect()->back()->with('message', $result['msg']); }
唯一的问题是,在存储方法中,需要将GroupService构造函数的$group参数设为可选,我不知道这是否真的是问题,还是我的执念。
- 拆分服务类:为每种操作(存储、更新、删除等)单独创建服务类,这样就不用考虑构造函数的可选参数问题,但这似乎有点过度设计。
无论如何,我想了解你们的想法、建议和批评。请注意,我知道这非常理想化,但我确实想知道如何利用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

