☰
PHP算法设计四大范式拆解:面向对象、函数式、事件驱动与递归
2026/10/22 3:56:11 网站建设 项目流程

如果你写过几年 PHP,大概率会有这种感觉:语法早就不是问题了,真正把代码写得清楚、跑得高效、改得动的,是你在写代码时脑子里那套“组织逻辑”。网上聊 PHP 算法设计的资料不少,但多数要么堆 LeetCode 题解,要么照搬设计模式目录,真正从“范式”层面去解剖 PHP 代码该怎么组织、怎么选型、怎么混用的内容其实不多。这篇博文我想用“庖丁解牛”的方式,把 PHP 算法设计里最核心的几种范式一一拆开:面向对象范式、函数式范式、事件驱动与异步范式、分治与递归范式,最后用一个真实场景把四种范式串起来跑一遍。适合正在做中大型业务系统、被面条式代码折磨过、想系统提升代码组织能力的开发者阅读,如果你刚接触 PHP 也没关系,我会把每个概念都配上可以直接运行的例子,你跟着敲一遍就能建立整体认知。

1. 面向对象范式:把算法变成有状态的对象

1.1 为什么算法设计离不开类与对象

很多从脚本风格写过来的 PHP 开发者,写算法时长这样:一个函数接收一堆参数,中间用临时变量存中间结果,最后返回一个数组。这种写法在小功能里没什么问题,但一旦算法的状态变多——比如订单价格计算要经过优惠券、会员折扣、运费、税费多个阶段——参数就会越传越长,函数内部的分支也会越来越多,最后改一个规则就要全文搜索。

面向对象范式解决的不是“怎么算”,而是“把计算过程中的状态和步骤放在哪里”。当你把算法包装成一个类,每一步计算就是类里的一个方法,中间结果就是对象的属性。这样带来的直接好处有三点:第一,调用方不需要知道算法内部有多少中间状态,只需要 new 一个对象,调一个方法;第二,不同算法之间可以通过接口约定统一行为,方便替换;第三,对象的属性天然成为算法执行过程中的“工作台”,不用靠参数列表来回传递。

举个例子,一个订单折扣计算器,我见过最典型的坏味道写法是:

function calcDiscount($user, $order, $coupons, $settings) { // 30 行分支判断 }

一旦优惠规则增加,这个函数就是灾难。而用面向对象重写后,每个优惠规则变成一个独立类,计算器只负责把规则跑一遍。这才是面向对象范式在算法设计里最本质的价值:它不是在“算”,而是在“组织计算”。

1.2 策略模式:让算法可以在运行时切换

策略模式是面向对象范式里和算法设计关系最紧密的一个模式。它的核心思想很简单:定义一组算法,把它们各自封装起来,并且使它们可以互相替换。在 PHP 里,最常见的落地方式是接口 + 实现类。

我之前做过一个物流运费计算模块,不同物流公司的计费逻辑完全不同,有的按重量,有的按体积,有的按首重续重,还有按距离阶梯计费的。如果写成一个类然后 switch 分支,每加一家物流公司就要改一次主类,违背开闭原则。用策略模式后,代码结构变成这样:

interface ShippingFeeStrategy { public function calculate(array $package): float; } class WeightStrategy implements ShippingFeeStrategy { public function calculate(array $package): float { return $package['weight'] * 2.5; } } class VolumeStrategy implements ShippingFeeStrategy { public function calculate(array $package): float { return $package['volume'] * 0.8; } } class ShippingFeeCalculator { public function __construct(private ShippingFeeStrategy $strategy) {} public function setStrategy(ShippingFeeStrategy $strategy): void { $this->strategy = $strategy; } public function getFee(array $package): float { return $this->strategy->calculate($package); } }

调用方在运行时根据物流公司编码选择对应策略对象,注入计算器。这样新增一家物流公司,只需要新增一个实现类,不用碰原有代码。我在实际项目中还踩过一个坑:策略类如果每次请求都 new 会有创建开销,但对 PHP-FPM 这种短生命周期模型来说影响很小,真正需要注意的是策略对象内部不要保存请求级以外的状态,否则多个请求复用同一个容器时容易串数据。

提示:策略模式适合算法行为差异大、调用方需要灵活切换的场景。如果你的算法分支是固定不变的,用策略模式反而是过度设计,老老实实写 if/else 更清晰。

1.3 状态装载:用对象封装长任务现场

除了策略切换,面向对象范式还有一个经常被忽略的价值:作为长任务的“状态容器”。一个算法涉及多步计算,每步都会产生中间结果,如果这些中间结果都塞在函数参数里,调用链会非常脆弱。

我举一个实际业务场景:对账系统里有个对账算法,需要依次执行数据拉取、格式转换、两边匹配、差异计算、结果汇总五个步骤。用过程式写法的参数传递能把你逼疯,每步都要传前一步的结果。更好的做法是制造一个对账上下文对象:

class ReconciliationContext { public array $sourceRows = []; public array $targetRows = []; public array $matchedIds = []; public array $diffRows = []; public array $summary = []; }

每个步骤的方法接收这个上下文,往里填数据。这样做的好处是:步骤之间的耦合从“参数列表的隐式约定”变成了“上下文对象的明确字段”,你在任何一步都能看到当前处理的进度,排查问题也只需要打印一个对象。

这种思路其实就是工作单元模式的一种简化版。实际用的时候,我会给上下文对象加一个步骤记录数组,每执行一步就追加一条日志,这样在跑批任务时即使中途挂掉,也能从上下文日志看出死在哪一步。这个习惯救过我两次线上事故,强烈建议你试试。

2. 函数式范式:让数据流在你的代码里跑起来

2.1 三个高频函数组合成数据管道

PHP 从 5.3 开始支持匿名函数,到 7.x 之后箭头函数也补齐了,函数式编程的底子已经相当够用。在算法设计里,最常用的一套函数式组合拳是array_map、array_filter、array_reduce。它们分别对应“变换”“筛选”“归约”三种数据操作,组合起来可以写出非常清晰的数据管道。

我拿一个用户数据处理做演示。假设某个接口需要从数据库取一批用户记录,然后做三步处理:过滤掉已删除的、把用户名转为小写、统计各用户组的数量。传统写法是三个 foreach 循环,而函数式写法是这样:

$users = [ ['name' => 'Alice', 'group' => 'A', 'deleted' => 0], ['name' => 'BOB', 'group' => 'B', 'deleted' => 1], ['name' => 'carol', 'group' => 'A', 'deleted' => 0], ]; $activeUsers = array_values(array_filter($users, fn($u) => !$u['deleted'])); $normalized = array_map(fn($u) => ['name' => strtolower($u['name']), 'group' => $u['group']], $activeUsers); $groupCount = array_reduce($normalized, function($carry, $u) { $carry[$u['group']] = ($carry[$u['group']] ?? 0) + 1; return $carry; }, []); print_r($groupCount);

这段代码的好处不是“更短”,而是每一步都只做一件事,数据从上一个函数流向下一个函数,中途没有任何共享变量被偷偷修改。排查问题时,你可以单步执行任何一个函数,检查它的输入输出,而不需要在一大段 for 循环里慢慢推演。

注意:使用array_filter时要留神,它会保留原数组的键名。如果后续逻辑依赖数字索引,记得用array_values重新索引。这是函数式写法里最容易踩的静默坑。

2.2 闭包与一等公民函数

函数式范式的第二个核心点是:函数本身可以作为参数传递、作为返回值返回、赋值给变量。PHP 里的闭包、箭头函数、callable类型声明,让这些能力用起来很方便。

最常见的场景是自定义排序。usort的第二参数需要传入一个比较函数,你用fn($a, $b) => $a['score'] <=> $b['score']就能实现按分数升序。如果要切换升降序,可以生成一个比较器工厂:

function makeComparator(string $field, string $direction): callable { return fn($a, $b) => $direction === 'asc' ? ($a[$field] <=> $b[$field]) : ($b[$field] <=> $a[$field]); } $users = [['score' => 90], ['score' => 75], ['score' => 88]]; usort($users, makeComparator('score', 'desc'));

这种方式把“排序规则”本身变成数据,可以在运行时自由组合。我在实际项目里还用闭包做过字段校验器集合:每个校验规则是一个闭包,放在数组里,遍历执行,哪个字段没过就收集错误信息。这种写法比一个巨大的 valid 方法清晰得多,后面加规则也只是往数组里塞一个闭包。

不过闭包也不是越多越好。闭包会捕获外部变量,如果捕获的是对象引用,可能会导致对象生命周期被意外拉长。PHP 里闭包对内存的回收机制和普通函数不太一样,长生命周期服务里要注意不要在高频调用处大量创建捕获大对象的闭包。

2.3 不可变思维:减少副作用也减少 Bug

函数式范式里最重要的思维不是“用了这些函数”,而是“尽量不修改外部状态”。我见过不少 PHP 项目,写着写着把函数式函数用成了语法糖,闭包内部还去改全局变量、改传入数组的引用——那还不如写循环。

真正的函数式思维要求你把数据当作不可变值来处理。比如上面例子里,每次array_map都返回新数组,而不是在旧数组上原地修改。这样做的最大收益是:你永远不需要担心一个函数“偷偷改掉”了另一个函数正在用的数据。在多人协作的项目里,这种确定性极其宝贵。

我在重构一个老项目时,把一段到处用$result引用传递的代码改成纯函数式管道,结果原本 “要仔细检查每个函数有没有动我的变量” 的排查过程变成了 “我看返回值就行了”。这两个项目的维护成本差距是肉眼可见的。当然,PHP 不是纯函数式语言,数组是写时复制的,你不可能完全避开引用和状态,但在算法内部尽量保持“输入不变、输出新值”的习惯,代码的可预测性会上升一个台阶。

3. 事件驱动与异步范式:高并发业务下的算法调度

3.1 发布订阅模型如何拆解业务流程

面向对象和函数式解决的是“单个算法怎么写”的问题,事件驱动解决的是“多个算法之间怎么协作”的问题。在真实业务系统里,一个动作往往触发一连串后续处理。拿“用户下单”来说,同步要做库存扣减,然后可能要发短信、给销售员推送、更新用户等级、生成对账流水——如果这些都在控制器里顺序调用,代码就会变成一条长长的调用链,任何一步变慢都会拖慢主流程。

事件驱动范式的解法是:主流程只负责发布“order.created”事件,由事件分发器把事件分发给所有监听者。每个监听者内部执行各自的算法,互不干扰。我在一个小型电商系统里实现过一个极简的事件分发器:

class EventDispatcher { private array $listeners = []; public function listen(string $event, callable $listener): void { $this->listeners[$event][] = $listener; } public function dispatch(string $event, array $payload): void { foreach ($this->listeners[$event] ?? [] as $listener) { $listener($payload); } } } $dispatcher = new EventDispatcher(); $dispatcher->listen('order.created', function($order) { // 扣减库存 }); $dispatcher->listen('order.created', function($order) { // 发送通知 });

这样做最大的好处是:新增一个下游处理逻辑时,不需要改主流程代码,只需要新增一个监听器。算法与算法之间的边界变得非常清晰。实际用的时候,我推荐你千万不要自己重复造轮子去管理监听器优先级,一个不小心就是灾难;用现成的事件组件或者严格控制监听器数量就行。

3.2 消息队列与 Worker:PHP 也能异步

很多人以为 PHP 是纯同步阻塞模型,做不了异步。这句话对 PHP-FPM 的 HTTP 请求来说是成立的,但对整个 PHP 应用来说并不全面。把耗时算法从请求链路里摘出去,最常见也最可靠的方式是:写入消息队列,由独立的 Worker 进程消费。

我记得有个业务要求上传 Excel 后解析并导入数据库,几万行数据解析加入库可能要十几秒,如果直接在请求里做,用户等得发疯,nginx 也会超时。设计成消息队列方案后,控制器只做一件事:把文件路径和解析参数写入 Redis 队列,立即返回“上传成功”。Worker 脚本通过BRPOP阻塞取任务,然后慢慢解析导入。

// 生产者 $redis->lpush('excel_import_queue', json_encode([ 'file' => '/tmp/upload_'.$fileId.'.xlsx', 'user_id' => $userId, ])); // Worker 消费者 while ($task = $redis->brpop('excel_import_queue', 5)) { $data = json_decode($task[1], true); // 解析 Excel,逐行入库 }

这种方案的运行逻辑就是个典型的生产者消费者算法。它的调度核心不是“在一个进程里安排执行顺序”,而是“通过队列保证任务的有序流动”。对于流量大到一定程度、单机 Worker 不够用的场景,可以水平扩展多个 Worker 进程,但要注意队列消息的幂等处理,否则重复消费会让数据翻倍。我踩过这个坑:某次 Worker 异常重启,任务被消费了两次,业务表里出现了一整批重复数据。

3.3 范式对比:同步调用与事件驱动的边界

事件驱动和异步范式有它的适用边界,不是所有场景都应该异步化。我见过一个团队把用户注册也做成异步,结果用户注册完立刻登录,却查不到自己的账号,因为写入还在队列里排队——这就是典型的“范式选错”。

我的经验判断标准是:

场景推荐范式原因
请求需要立即拿到结果同步调用异步会让用户感知到延迟或不一致
后续处理失败不影响主流程事件驱动监听器隔离失败
耗时超过请求容忍度消息队列异步把等待移出关键路径
需要保证绝对顺序消息队列 + 单一消费者多消费者并发会破坏顺序

简单说:能同步就同步,同步太慢就异步,异步有副作用就加补偿机制。这种“分层决策”本身就是一种调度算法设计。我在系统设计评审时,最常问的一句话就是“这个操作用户需要等多久?等不起就放到队列里去”。

4. 分治与递归范式:复杂问题先拆再合

4.1 递归遍历树形数据的正确姿势

业务系统里到处是树:组织架构、商品分类、评论楼中楼、权限菜单。遍历这种结构,最自然的写法就是递归。PHP 写递归有几个关键点:出口条件写对、深度限制、防止循环引用。

拿组织架构树举例,你需要从一个根节点出发,把它所有子部门找出来,再递归处理子部门的子部门。一个很常见的写法是:

function buildTree(array $allNodes, int $parentId): array { $branch = []; foreach ($allNodes as $node) { if ($node['parent_id'] === $parentId) { $children = buildTree($allNodes, $node['id']); if ($children) { $node['children'] = $children; } $branch[] = $node; } } return $branch; }

这段代码逻辑是对的,但性能隐患很大:每一次递归都遍历全部节点,复杂度是 O(n²)。当节点数上千时,响应时间会肉眼可见地变慢。我在实际项目里处理大目录树时,会先用一次遍历构建parent_id => children的映射,再通过映射递归组装,复杂度降到 O(n)。这是递归算法设计中一个特别重要的优化意识:别在递归里重复扫描同一个数据源。

提示:如果数据量真的特别大,比如几万层级的复杂树,还要考虑 PHP 函数调用栈的深度问题。PHP 默认没有硬性递归深度限制,但会受xdebug.max_nesting_level影响,也会占用内存。业务上建议限制递归最大层级,超出就走迭代或者标记异常。

4.2 记忆化搜索:用空间换时间的经典优化

递归容易被诟病为“慢”,很多时候是因为同一个子问题被反复计算。这里就得请出记忆化搜索:用一个缓存容器记录已经算过的结果,再次遇到直接返回。

比如经典的分组汇总问题:假设你要统计某个部门所有层级的业绩总和,部门 A 下面有部门 B,部门 B 下面还有子部门;如果一个叶子部门被多个上层部门汇总引用,最简单的递归会重复计算。这个时候给函数加一个静态缓存:

function sumDepartment(array $deptId, array &$cache): int { if (isset($cache[$deptId])) { return $cache[$deptId]; } $total = getSelfDeptValue($deptId); foreach (getChildDeptIds($deptId) as $childId) { $total += sumDepartment($childId, $cache); } $cache[$deptId] = $total; return $total; }

这里引用传递$cache,每次递归都会先查缓存。整个树的每个节点只计算一次,这就是记忆化的价值。你在算法题里学过的“斐波那契数列用数组缓存避免重复递归”,在业务代码里同样适用,只是业务里的“重复子问题”藏得更隐蔽,需要你主动识别。

我实际用过的一个案例是员工汇报关系链上的激励金额汇总。每个员工的下级树都是重叠的(因为组织架构树共享),如果对每个员工单独递归一次,全公司上千人算下来非常慢。加了一层记忆化之后,整体耗时从分钟级降到秒级。这就是用空间换时间的典型收益。

4.3 分治思想在批处理中的应用

分治的核心不只是递归,还包括“分而治之”的策略:把大任务拆成小任务、分别处理、合并结果。在服务端业务里,最常见的分治场景是“大数据量的分批处理”。

比如你要给 10 万用户群发优惠券。一次性查出来再循环发放,数据库连接、内存、接口超时全都会出问题。分治的做法是分批:一次取 500 个用户的 ID 列表,执行发放,记录进度,再取下 500 个。我把这种批处理算法提炼成一个通用骨架:

function processInBatches(string $table, int $batchSize, callable $process): void { $lastId = 0; while (true) { $rows = query("SELECT * FROM {$table} WHERE id > {$lastId} ORDER BY id ASC LIMIT {$batchSize}"); if (empty($rows)) break; foreach ($rows as $row) { $process($row); } $lastId = max(array_column($rows, 'id')); } }

这里用“大于上一批最大 ID”而不是 OFFSET,是为了避免数据在分批过程中变化导致的偏移错位,更深层的原因是传统 OFFSET 翻页在深度分页时性能极差。这个写法本身就是分治思想在数据规模上的体现。对于一个动辄几十万行的任务,你不需要一个巨型算法一次性搞定所有,而是用一个稳定的小算法反复执行,然后把每次的结果合并起来。分治的“合”这一步在实际工程里通常体现为对处理进度或结果的汇总记录,比如记录已处理 ID 区间。

5. 范式混用实战:一个多级订单汇总统计系统

5.1 业务建模与范式选型

前面讲了四种范式,实际项目里很少只用一种。现在我用一个完整场景把它们串起来:某公司有多级销售组织,每个销售有订单,系统需要按组织层级做订单金额汇总,汇总完成后触发报表生成和消息通知。

需求拆开之后有四个特点:

  • 组织层级是树形结构,涉及多级递归汇总——适合分治与递归范式。
  • 汇总计算过程中需要过滤无效订单、拆字段、聚合归约——适合函数式范式。
  • 不同组织层级有不同汇总规则,比如一级组织要加计团队奖金,二级组织只看订单净额——适合策略模式即面向对象范式。
  • 汇总完成后要通知多个下游系统,且通知不能阻塞主流程——适合事件驱动范式。

范式选型的结果是:整体上用一个汇总服务类做编排,数据管道用函数式写,组织树递归用分治法加记忆化缓存,汇总结果发事件异步通知。这是一个非常典型的“混合范式”项目。

5.2 核心代码实现拆解

先建立组织树并递归汇总。组织数据表里每个节点有id、parent_id、name。订单表里每条订单记录有org_id、amount、status。第一步,构建父子映射:

function buildOrgMap(array $orgs): array { $map = []; foreach ($orgs as $org) { $map[$org['parent_id']][] = $org; } return $map; }

第二步,递归汇总函数,用引用缓存避免重复子树的重复计算:

function sumOrg(int $orgId, array $orgMap, array &$cache): array { if (isset($cache[$orgId])) { return $cache[$orgId]; } $orders = fetchOrdersByOrg($orgId); $selfAmount = array_reduce( array_filter($orders, fn($o) => $o['status'] === 'paid'), fn($carry, $o) => $carry + $o['amount'], 0.0 ); $childrenAmount = 0.0; foreach ($orgMap[$orgId] ?? [] as $child) { $childrenAmount += sumOrg($child['id'], $orgMap, $cache)['total']; } $result = [ 'org_id' => $orgId, 'self' => $selfAmount, 'children' => $childrenAmount, 'total' => $selfAmount + $childrenAmount, ]; $cache[$orgId] = $result; return $result; }

这个函数里,array_filter和array_reduce体现的就是函数式数据管道,sumOrg的递归加记忆化体现的是分治与递归。第三步,不同类型的组织套用不同的汇总策略。这里我定义一个接口,两个实现类分别对应“销售小组”和“事业部门”:

interface SummaryStrategy { public function summarize(array $nodeData): array; } class TeamSummary implements SummaryStrategy { public function summarize(array $nodeData): array { $nodeData['total'] = $nodeData['total'] * 1.1; // 团队奖金系数 return $nodeData; } } class DepartmentSummary implements SummaryStrategy { public function summarize(array $nodeData): array { $nodeData['total'] = round($nodeData['total'], 2); return $nodeData; } }

第四步,汇总完成后发事件。我用一个简单的事件器:

class SummaryCompletedEvent { public function __construct(public array $orgSummary) {} } $dispatcher->dispatch('summary.completed', new SummaryCompletedEvent($finalResult));

监听着重体现异步处理:报表生成监听器把结果写入导出队列,通知监听器只负责推送一条“汇总完成”消息。这样主流程里递归和函数式计算结束之后,后续动作都异步化了。

完整跑一遍这个案例你就会发现,每种范式在里面各司其职:没有面向对象,三种不同的组织汇总逻辑会搅成一锅粥;没有函数式,订单过滤和金额归约要多写很多循环;没有递归和记忆化,组织树的汇总性能扛不住;没有事件驱动,下游通知逻辑会污染整个汇总流程。混合使用不是炫技,而是每一个选择都在解决一个具体问题。

5.3 混用范式的边界与注意事项

范式混用最大的风险是“过度设计”。我见过有的开发者学了策略模式,写个两行的计算也搞三个类四个接口;学了事件驱动,A 方法调 B 方法也不直接调,非要发个事件绕一圈。范式是为了解决问题而存在的,不是为了让你代码看起来很高深。

我的判断标准非常简单:如果这块代码三个星期后你自己读起来需要花超过五分钟才能明白它在干嘛,那就是过度设计了。混用范式之前,先想清楚每个范式在这个位置上解决了什么不可替代的问题。函数式管道解决数据变换的清晰度,递归解决树形结构的自然表达,策略解决算法家族的可替换,事件解决调用方与处理方的解耦——如果某个位置用普通函数就能说清楚,就不要套范式。

提示:混用范式时,接口之间的数据格式一定要提前定好。比如sumOrg返回的是['org_id'=>..., 'total'=>...]这种数组结构,那么策略类的输入输出就必须兼容它。范式之间通过“标准数据结构”协作,而不是通过互相调用对方的方法协作,这样才能保持各个范式的独立性。

6. 常见问题与避坑实录

6.1 典型问题速查表

我在实践里把容易翻车的点整理成一个问题速查表,你在设计自己的算法时可以对照着检查:

问题现象根源解决方案
递归遍历大量数据时响应极慢每次递归重复扫描全量数据先构建子节点映射,再递归组装,复杂度从 O(n²) 降为 O(n)
闭包修改外部数组不生效PHP 闭包默认按值捕获,引用需显式声明使用use (&$var)传递引用,或返回新数组
事件监听器执行顺序错乱没有约定优先级使用带优先级的调度器,或把有依赖的监听器合并为一个
消息消费者崩溃后任务重复处理队列没有消费确认消费后写入处理记录表,启动时检查幂等标记
array_filter 后键值错乱数组键名被保留链式调用array_values重置索引
静态缓存造成脏数据长生命周期进程持有缓存给缓存加业务维度的 key,按组织、时间等隔离
树上节点存在循环引用数据异常导致 parent_id 形成环递归时传入访问集合,检测到已访问节点即终止

6.2 我的三点踩坑心得

第一,递归之前先问自己一句“这棵树到底有多深”。我接过一个权限菜单系统,层级本身只有三层,但数据配置错了,某个节点的 parent_id 指向了自己的子节点,结果递归直接跑满内存。后来我写递归一律加深度参数,超过预设最大层级就抛异常。层级深度不是你想当然的五层,一定要和业务确认,并且代码要做兜底。

第二,函数式管道看着简洁,但是调试的时候别硬扛。我有段时间特别喜欢用超长链式的array_filter()->array_map()->array_reduce()一条龙,后来线上查一个问题,为了确认中间某一步的数据形状,不得不把链拆开,临时打日志。从那以后我的习惯是:中间结果复杂、不方便一眼看穿的数据变换,宁可用三四行普通循环写清楚,也不要硬缩写成一行怪物。代码首先是给人读的,其次才是给机器跑的。

第三,事件驱动最大的坑不是性能,是心智负担。代码里只要点一下 dispatch,后续逻辑就“飞”出去了,你在主流程里再也看不到完整的业务路径。所以我要求自己给每个事件写清楚注释:谁发的、哪些监听器会响应、每个监听器大致做什么。没有这份说明,三个月后维护起来就是你看着 dispatch 发愣,根本不知道它会触发什么。

写在最后的个人体会

如果你问我这几种范式里哪种最重要,我的答案可能出乎意料:不是任何一种具体范式,而是拆解问题的能力。拿到一个业务需求,先别急着写代码,先用伪代码把数据流转画一遍,再看数据流里有哪几个阶段:哪些阶段是树形递归,哪些阶段是数据变换,哪些阶段要策略替换,哪些阶段要异步解耦。这个“先设计数据流,再选择范式”的习惯,是我觉得最值得你从这篇文章里带走的东西。我自己带团队评审代码时,也很少直接说“你这里该用策略模式”,而是问“这个分支未来有多大概率会扩展,如果会,你怎么让新增分支不碰老代码”——答案自然就是范式选型的方向。希望这篇拆解能让你在面对复杂业务时,脑子里不再是模糊的“面向对象”、“函数式”这些标签,而是一把能把问题一层层剖开的刀。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询