本文记录了我如何将一个400行的Repository方法拆解成可维护组件的真实历程。如果你的代码也变成了“屎山”,或许我的经验能帮到你。
我们团队的项目中有一个OrderRepository
,其中getAdminIndex
方法已经膨胀到400多行。它负责管理后台的订单列表展示,包含查询构建、数据统计、分页处理、结果加工等十多种职责。
看着这段代码,我仿佛听到了它的哭泣:
public function getAdminIndex($params, $isTj = false)
{
// 光是查询条件就有30多个if判断
// 数据处理又来了几十行计算逻辑
// 这还只是方法的一半内容...
}
为什么要重构?
这个问题让我夜不能寐:明明我们已经按照Repository模式架构了,为什么还会出现这种庞然大物?
答案很简单:我们只是机械地分层,却没有真正践行单一职责原则。这个方法的“罪行”包括:
- 查询构建与业务逻辑耦合:30多个查询条件堆在一起
- 数据转换与业务计算混杂:金额计算、状态判断全塞在一起
- 统计与分页处理共存:一个方法既要统计又要分页
- 缺乏抽象层次:所有逻辑都平铺在一个方法中
是时候动手重构了!
第一步:拆分查询构建器
我首先创建了OrderQueryBuilder
,专门负责处理那30多个查询条件:
class OrderQueryBuilder
{
private $query;
private $params;
public function __construct(array $params)
{
$this->query = Order::query();
$this->params = $params;
}
// 核心方法:应用所有过滤器
public function applyFilters()
{
// 状态筛选
if ($status = $this->getParam('status')) {
$this->applyStatusFilter($status);
}
// 时间范围筛选
if ($startTime = $this->getParam('startTime')) {
$this->query->where('created_at', '>=', $startTime);
}
// 支付渠道筛选
if ($payChannel = $this->getParam('pay_channel')) {
$this->applyPayChannelFilter($payChannel);
}
// 还有30多个条件...
// 每个条件都有自己的处理方法
return $this->query;
}
// 支付渠道筛选逻辑
private function applyPayChannelFilter($payChannel)
{
$channelMap = [
1 => fn() => $this->query->where('is_alipay', 1),
2 => fn() => $this->query->where('is_wx', 1),
3 => fn() => $this->query->where('is_card', 1),
// 更多映射关系...
];
if (isset($channelMap[$payChannel])) {
$channelMap[$payChannel]();
} else {
$this->query->where('pay_type', $payChannel == 1 ? 0 : 1);
}
}
// 状态筛选逻辑
private function applyStatusFilter($status)
{
$statusHandlers = [
Order::STATUS_CLOSED => fn() => $this->query->whereNotNull('closed_at'),
Order::STATUS_COMPLAINT => fn() => $this->applyComplaintFilter(),
// 其他状态处理...
];
if (isset($statusHandlers[$status])) {
$statusHandlers[$status]();
}
}
private function getParam($key)
{
return $this->params[$key] ?? null;
}
}
第二步:创建数据处理器
订单数据需要各种计算和转换,我创建了OrderDataProcessor
:
class OrderDataProcessor
{
// 处理订单集合
public function processCollection($orders)
{
foreach ($orders as $order) {
$this->processSingleOrder($order);
}
}
// 处理单个订单
public function processSingleOrder($order)
{
// 具体的处理逻辑
$order->total_amount_yuan = fenToYuan($order->total_amount);
$order->status_text = $this->getStatusText($order->status);
// 更多处理...
}
}
第三步:重组Repository
现在我可以优雅地重组原来的巨无霸方法:
class OrderRepository
{
protected $dataProcessor;
public function __construct(OrderDataProcessor $dataProcessor)
{
$this->dataProcessor = $dataProcessor;
}
public function getAdminIndex($params, $isTj = false)
{
// 构建查询
$query = (new OrderQueryBuilder($params))->applyFilters();
// 统计分支
if ($isTj) {
$sum = $query->sum('total_amount');
$fee = $query->sum('fee');
$count = $query->count();
return [
'sum' => fenToYuan($sum),
'count' => $count
];
}
// 分页查询
$orders = $query->orderBy('id', 'desc')->paginate(10);
// 数据处理
$this->dataProcessor->processCollection($orders->getCollection());
return $orders;
}
}
我收获的经验
-
不要被模式名称束缚:Repository模式不是把数据库操作堆在一起的理由
-
单一职责是底线:一个方法超过50行就应该警惕,超过100行就值得重构
-
拆分策略很重要:
- 按职责拆分:查询构建、数据处理、业务逻辑
- 按变化频率拆分:经常变的和稳定的逻辑分开
-
测试变得容易:现在我可以单独测试查询构建器、数据处理器和Repository
-
代码可读性大幅提升:新同事能够快速理解代码结构
还能做得更好
虽然重构取得了成效,但还有优化空间:
-
进一步抽象查询条件:每个查询条件都可以成为独立对象
-
引入缓存机制:频繁调用的统计结果可以缓存
-
异常处理增强:当前版本错误处理还不够完善
重构前的代码:
- 一个400行的巨方法
- 难以测试和维护
- 新同事看得头晕眼花
重构后的代码:
- 多个专注单一职责的类
- 平均每个方法20行左右
- 易于测试和理解
重构不是一次性的工程,而是一种持续改进的心态。每当我要往一个方法里添加新功能时,都会先问自己:这个职责是否已经有人承担了?如果没有,是不是应该创建一个新的组件?
结语
希望我的实战经验对你有启发。如果你的代码库中也有这样的“巨无霸”,不妨尝试用类似的方法拆解它。记住:好的代码不是一开始就设计出来的,而是在不断重构中演化出来的。