POP面向过程编程
大约 12 分钟约 3459 字
POP面向过程编程
简介
Procedure Oriented Programming
POP面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。考虑问题是从解决问题的步骤出发(很自然的)。最怕步骤多,最怕业务复杂。
面向过程是最原始的编程范式,以"过程"(函数/方法)为核心组织代码逻辑。C 语言是面向过程的代表语言。
特点
基本结构
面向过程编程的核心三大结构:
顺序结构
// 按照代码书写顺序,从上到下依次执行
int a = 10;
int b = 20;
int sum = a + b;
Console.WriteLine($"两数之和:{sum}");选择结构
// 根据条件判断,选择不同的执行路径
if (score >= 90)
{
Console.WriteLine("优秀");
}
else if (score >= 60)
{
Console.WriteLine("及格");
}
else
{
Console.WriteLine("不及格");
}
// switch 多分支选择
switch (dayOfWeek)
{
case 1: Console.WriteLine("星期一"); break;
case 2: Console.WriteLine("星期二"); break;
case 3: Console.WriteLine("星期三"); break;
default: Console.WriteLine("其他"); break;
}循环结构
// for 循环 — 明确知道循环次数
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"第 {i + 1} 次执行");
}
// while 循环 — 不确定循环次数
while (hasData)
{
hasData = ProcessNext();
}
// foreach 遍历集合
foreach (var item in list)
{
Console.WriteLine(item);
}函数与模块化
函数的定义与调用
// 函数是面向过程的基本组织单元
static int CalculateSum(int a, int b)
{
return a + b;
}
static void PrintResult(int result)
{
Console.WriteLine($"计算结果:{result}");
}
// 调用
int result = CalculateSum(10, 20);
PrintResult(result);模块化设计 — 分而治之
/// <summary>
/// 面向过程实现一个简单的学生成绩管理
/// 将大问题拆分为多个小函数
/// </summary>
// 全局数据(面向过程中数据和行为是分离的)
static List<Student> students = new List<Student>();
// 步骤1:添加学生
static void AddStudent(int id, string name, float score)
{
students.Add(new Student { Id = id, Name = name, Score = score });
Console.WriteLine($"添加学生 {name} 成功");
}
// 步骤2:查询学生
static Student FindStudent(int id)
{
foreach (var stu in students)
{
if (stu.Id == id) return stu;
}
return null;
}
// 步骤3:统计平均分
static float CalculateAverage()
{
if (students.Count == 0) return 0;
float total = 0;
foreach (var stu in students)
{
total += stu.Score;
}
return total / students.Count;
}
// 步骤4:显示所有学生
static void ShowAll()
{
Console.WriteLine("学号\t姓名\t成绩");
foreach (var stu in students)
{
Console.WriteLine($"{stu.Id}\t{stu.Name}\t{stu.Score}");
}
Console.WriteLine($"平均分:{CalculateAverage():F2}");
}
struct Student
{
public int Id;
public string Name;
public float Score;
}面向过程的典型应用场景
1. 脚本和自动化任务
/// <summary>
/// 文件批处理脚本 — 面向过程的典型场景
/// 步骤清晰,逻辑简单
/// </summary>
static void Main(string[] args)
{
// 步骤1:获取源目录
string sourceDir = @"C:\Logs";
// 步骤2:筛选过期文件
var files = Directory.GetFiles(sourceDir, "*.log");
var expiredFiles = new List<string>();
foreach (var file in files)
{
var info = new FileInfo(file);
if (info.LastWriteTime < DateTime.Now.AddDays(-30))
{
expiredFiles.Add(file);
}
}
// 步骤3:备份
string backupDir = @"C:\Backup";
if (!Directory.Exists(backupDir))
Directory.CreateDirectory(backupDir);
foreach (var file in expiredFiles)
{
string fileName = Path.GetFileName(file);
File.Move(file, Path.Combine(backupDir, fileName));
}
// 步骤4:记录日志
File.WriteAllText(
Path.Combine(backupDir, "clean.log"),
$"清理完成,共处理 {expiredFiles.Count} 个文件"
);
}2. 算法实现
/// <summary>
/// 快速排序 — 纯算法逻辑,面向过程的经典场景
/// </summary>
static void QuickSort(int[] arr, int left, int right)
{
if (left < right)
{
int pivotIndex = Partition(arr, left, right);
QuickSort(arr, left, pivotIndex - 1);
QuickSort(arr, pivotIndex + 1, right);
}
}
static int Partition(int[] arr, int left, int right)
{
int pivot = arr[right];
int i = left - 1;
for (int j = left; j < right; j++)
{
if (arr[j] <= pivot)
{
i++;
Swap(ref arr[i], ref arr[j]);
}
}
Swap(ref arr[i + 1], ref arr[right]);
return i + 1;
}
static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}POP vs OOP 对比
| 对比维度 | POP 面向过程 | OOP 面向对象 |
|---|---|---|
| 核心思想 | 以过程/函数为中心 | 以对象/类为中心 |
| 数据管理 | 数据与操作分离 | 数据与操作封装在一起 |
| 适用场景 | 简单脚本、算法、底层开发 | 复杂业务系统、大型项目 |
| 扩展性 | 低,修改影响范围大 | 高,通过继承和多态扩展 |
| 复用性 | 函数级别复用 | 类/组件级别复用 |
| 思维方式 | 怎么做(步骤) | 谁来做(职责) |
| 典型语言 | C、Pascal、Shell | C#、Java、Python |
面向过程的进阶技巧
递归与分治
/// <summary>
/// 递归的经典应用 — 二分查找
/// 面向过程风格:清晰的步骤分解
/// </summary>
static int BinarySearch(int[] arr, int target, int left, int right)
{
if (left > right) return -1; // 递归终止条件
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid; // 找到目标
else if (arr[mid] > target)
return BinarySearch(arr, target, left, mid - 1); // 左半部分
else
return BinarySearch(arr, target, mid + 1, right); // 右半部分
}
// 使用
int[] sorted = { 1, 3, 5, 7, 9, 11, 13, 15 };
int index = BinarySearch(sorted, 7, 0, sorted.Length - 1); // 3状态机 — 面向过程的经典组织方式
/// <summary>
/// 面向过程实现有限状态机 — 订单状态流转
/// </summary>
enum OrderState
{
Created, // 已创建
Paid, // 已支付
Shipped, // 已发货
Delivered, // 已送达
Completed, // 已完成
Cancelled // 已取消
}
// 状态转移表 — 面向过程的表驱动方式
static readonly Dictionary<(OrderState, string), OrderState> Transitions = new()
{
{ (OrderState.Created, "pay"), OrderState.Paid },
{ (OrderState.Paid, "ship"), OrderState.Shipped },
{ (OrderState.Shipped, "deliver"), OrderState.Delivered },
{ (OrderState.Delivered, "confirm"), OrderState.Completed },
{ (OrderState.Created, "cancel"), OrderState.Cancelled },
{ (OrderState.Paid, "cancel"), OrderState.Cancelled },
};
static bool TryTransition(OrderState current, string action, out OrderState newState)
{
var key = (current, action);
if (Transitions.TryGetValue(key, out newState))
{
Console.WriteLine($"状态转移: {current} --[{action}]--> {newState}");
return true;
}
Console.WriteLine($"无效转移: {current} --[{action}]--> ???");
newState = current;
return false;
}
// 使用
OrderState state = OrderState.Created;
TryTransition(state, "pay", out state); // Created -> Paid
TryTransition(state, "ship", out state); // Paid -> Shipped
TryTransition(state, "cancel", out state); // 无效转移(Shipped 不可取消)管道模式 — 面向过程的数据处理链
/// <summary>
/// 面向过程的管道模式 — 数据处理流水线
/// 每个步骤是一个函数,数据依次流过
/// </summary>
delegate List<string> PipelineStep(List<string> input);
static List<string> FilterEmpty(List<string> input) =>
input.Where(s => !string.IsNullOrWhiteSpace(s)).ToList();
static List<string> TrimSpaces(List<string> input) =>
input.Select(s => s.Trim()).ToList();
static List<string> ToLowerCase(List<string> input) =>
input.Select(s => s.ToLower()).ToList();
static List<string> RemoveDuplicates(List<string> input) =>
input.Distinct().ToList();
static List<string> SortAlphabetically(List<string> input) =>
input.OrderBy(s => s).ToList();
// 构建管道 — 纯面向过程的步骤组合
static List<string> BuildPipeline(params PipelineStep[] steps)
{
// 返回一个管道函数
return (List<string> input) =>
{
List<string> result = input;
foreach (var step in steps)
{
result = step(result);
}
return result;
};
}
// 使用
var rawData = new List<string> { " Apple", "banana", "", " apple ", "Cherry", "banana" };
var pipeline = BuildPipeline(FilterEmpty, TrimSpaces, ToLowerCase, RemoveDuplicates, SortAlphabetically);
var cleaned = pipeline(rawData);
// 结果: ["apple", "banana", "cherry"]回调函数 — 面向过程的扩展机制
/// <summary>
/// 回调函数是面向过程实现"扩展点"的经典方式
/// </summary>
// 通用的数据处理函数 — 通过回调实现不同的处理逻辑
static void ProcessData(List<int> data, Func<int, bool> filter, Action<int> processor)
{
// 步骤1:遍历数据
foreach (var item in data)
{
// 步骤2:过滤(通过回调)
if (filter(item))
{
// 步骤3:处理(通过回调)
processor(item);
}
}
}
// 不同的回调实现不同的业务逻辑
ProcessData(
new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
filter: x => x % 2 == 0, // 只处理偶数
processor: x => Console.WriteLine(x * x) // 输出平方
);
// 带多个回调的通用排序
static void SortWithCallback<T>(T[] array,
Func<T, T, int> compare,
Action<T[]> beforeSort = null,
Action<T[]> afterSort = null)
{
beforeSort?.Invoke(array);
Array.Sort(array, (a, b) => compare(a, b));
afterSort?.Invoke(array);
}面向过程与现代框架的结合
ASP.NET Core 中间件管道
// ASP.NET Core 的中间件管道本质上就是面向过程的
// 请求依次经过每个中间件处理
var app = builder.Build();
// 每个中间件就是一个处理步骤
app.Use(async (context, next) =>
{
// 步骤1:请求前处理
Console.WriteLine("请求前: 记录开始时间");
var sw = Stopwatch.StartNew();
await next(); // 传递给下一个中间件
// 步骤4:请求后处理
Console.WriteLine($"请求后: 耗时 {sw.ElapsedMilliseconds}ms");
});
app.Use(async (context, next) =>
{
// 步骤2:身份验证
var token = context.Request.Headers["Authorization"].FirstOrDefault();
if (string.IsNullOrEmpty(token))
{
context.Response.StatusCode = 401;
return;
}
await next(); // 传递给下一个中间件
});
app.Use(async (context, next) =>
{
// 步骤3:业务处理
Console.WriteLine("业务处理");
await next();
});事件驱动的面向过程
// 面向过程 + 事件驱动 — 处理工作流
static void Main()
{
// 步骤1:初始化
var processor = new OrderProcessor();
// 步骤2:注册事件处理器(回调)
processor.OnOrderCreated += (id, amount) =>
{
Console.WriteLine($"[步骤A] 订单 {id} 已创建,金额: {amount}");
};
processor.OnPaymentReceived += (id, transactionId) =>
{
Console.WriteLine($"[步骤B] 订单 {id} 已支付,交易号: {transactionId}");
};
processor.OnShipped += (id, trackingNumber) =>
{
Console.WriteLine($"[步骤C] 订单 {id} 已发货,快递单号: {trackingNumber}");
};
// 步骤3:执行流程
processor.ProcessOrder(1001, 299.99m);
}
class OrderProcessor
{
public event Action<int, decimal> OnOrderCreated;
public event Action<int, string> OnPaymentReceived;
public event Action<int, string> OnShipped;
public void ProcessOrder(int orderId, decimal amount)
{
OnOrderCreated?.Invoke(orderId, amount);
var txnId = $"TXN{Guid.NewGuid():N}";
OnPaymentReceived?.Invoke(orderId, txnId);
var tracking = $"SF{new Random().Next(100000, 999999)}";
OnShipped?.Invoke(orderId, tracking);
}
}面向过程的问题 — 以实际案例说明
问题场景:电商订单处理
/// <summary>
/// 面向过程处理订单 — 随着业务增长,代码变得难以维护
/// </summary>
static void ProcessOrder(int orderId, int userId, List<CartItem> items)
{
// 步骤1:验证
if (items == null || items.Count == 0)
{
Console.WriteLine("购物车为空");
return;
}
// 步骤2:计算价格
decimal totalPrice = 0;
foreach (var item in items)
{
totalPrice += item.Price * item.Quantity;
}
// 步骤3:折扣计算(越来越多的 if-else)
if (totalPrice >= 500)
totalPrice *= 0.8m;
else if (totalPrice >= 300)
totalPrice *= 0.9m;
else if (totalPrice >= 100)
totalPrice *= 0.95m;
// VIP 会员折扣?满减?优惠券?—— 每加一个规则就要改这个函数
// 步骤4:扣库存
foreach (var item in items)
{
// 跨模块调用,耦合度高
if (!CheckStock(item.ProductId, item.Quantity))
{
Console.WriteLine($"商品 {item.ProductId} 库存不足");
return;
}
}
// 步骤5:创建订单
// 步骤6:支付
// 步骤7:发通知
// ......越来越多的步骤,函数越来越长
}
static bool CheckStock(int productId, int quantity)
{
// 查数据库检查库存
return true;
}核心问题:当业务复杂到一定程度,面向过程的线性思维会导致代码臃肿、耦合度高、难以扩展。这正是 OOP、AOP、DDD 等更高层次编程思想诞生的原因。
优点
缺点
总结
面向过程是编程的基石,即使在 OOP/AOP/DDD 盛行的今天,面向过程依然无处不在 — 每一个方法的内部实现,本质上都是面向过程的。理解 POP 是进阶其他编程范式的前提。
选择建议:简单任务用 POP,复杂系统用 OOP + DDD,横切关注点用 AOP。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来“高级”而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《POP面向过程编程》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕“为了模式而模式”,尤其是在简单业务里。
复盘问题
- 如果把《POP面向过程编程》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《POP面向过程编程》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《POP面向过程编程》最大的收益和代价分别是什么?
