15-C#

编程思想进阶笔记:POP → OOP → AOP → DDD

本文以"用户注册"这个业务场景为主线,逐步演示四种编程思想的演进过程,帮助理解每种思想解决了什么问题、又带来了什么新的局限。


一、POP ------ 面向过程编程

核心思想

面向过程(Procedure-Oriented Programming)是最直接的编程方式:把要做的事情按步骤一行一行写下来,从上到下顺序执行。思维方式是线性的,适合逻辑简单、不需要扩展的小程序。

示例

csharp 复制代码
// 用户注册的面向过程写法
Console.WriteLine("用户注册,提交信息");
Console.WriteLine("1 参数检查");
Console.WriteLine("2 数据入库");
Console.WriteLine("3 记录日志");

问题在哪

  • 所有逻辑堆在一起,代码越写越长,越来越难维护
  • 需求一变,就要到处改代码,牵一发动全身
  • 无法复用,同样的逻辑在不同地方重复出现
  • 不能应对变化,扩展性极差

二、OOP ------ 面向对象编程

核心思想

面向对象(Object-Oriented Programming)把程序拆分成一个个"对象",每个对象负责自己的事情。三大特性:封装、继承、多态

  • 封装:把细节藏起来,外部只看接口
  • 继承:子类复用父类代码
  • 多态(面向抽象):上层代码只依赖接口,不依赖具体实现,这是 OOP 扩展性的核心

示例演进

第一步:封装对象

csharp 复制代码
// 把用户信息封装成对象
UserInfo userInfo = new UserInfo()
{
    Account = "Administrator",
    Name = "张三",
    Password = "888888"
};

第二步:面向抽象(依赖接口而非具体类)

csharp 复制代码
// 定义接口
public interface IDBHelper
{
    int Save(UserInfo userInfo);
}

// MySQL 实现
public class MySQLDBHelper : IDBHelper
{
    public int Save(UserInfo userInfo)
    {
        Console.WriteLine("数据入MySQL");
        return 0;
    }
}

// SQLServer 实现
public class SqlServerDBHelper : IDBHelper
{
    public int Save(UserInfo userInfo)
    {
        Console.WriteLine("数据入SqlServer");
        return 0;
    }
}

第三步:用简单工厂解耦

csharp 复制代码
public class SimpleFactory
{
    public static IDBHelper CreateDBHelper()
    {
        // 只改这一行,上层代码完全不用动
        return new MySQLDBHelper();
        // return new SqlServerDBHelper();
    }
}

// 调用方
IDBHelper dbHelper = SimpleFactory.CreateDBHelper();
dbHelper.Save(userInfo);

OOP 的价值

  • 封装屏蔽了细节,上层代码不关心底层怎么实现
  • 面向抽象(接口 + 多实现)让切换实现变得简单,这是 80% 设计模式的核心套路
  • 依赖注入(DI)、工厂模式、策略模式......都是这个思路的延伸

OOP 的局限

OOP 的扩展性靠的是对象替换对象。但如果我不想换掉整个对象,只是想在某个方法执行前后加点通用逻辑(比如日志、权限检查、性能统计),OOP 就显得力不从心了。

最直接的做法是修改原来的类,但这违反了开闭原则(对扩展开放,对修改关闭)。


三、AOP ------ 面向切面编程

核心思想

面向切面(Aspect-Oriented Programming)解决的问题是:在不修改原有对象、不替换原有对象的前提下,给它额外扩展功能

这些额外的功能通常是横跨多个业务模块的通用逻辑,比如:

  • 日志记录
  • 权限验证
  • 性能统计
  • 异常处理
  • 事务管理
  • 缓存

AOP 不能做业务逻辑,它只负责这些"横切关注点"。

实现方式一:静态代理

手动写一个代理类,实现同一个接口,内部持有真实对象,在调用前后插入额外逻辑。

csharp 复制代码
public class DBHelperProxy : IDBHelper
{
    // 持有真实对象
    private IDBHelper _iDBHelper = new MySQLDBHelper();
    private ILogHelper _logger = new LogConsole();

    public int Save(UserInfo userInfo)
    {
        // 方法执行前
        _logger.Log("Prepare Save");

        // 调用真实方法
        int result = _iDBHelper.Save(userInfo);

        // 方法执行后
        _logger.Log("After Save");

        return result;
    }
}

// 使用时,把代理对象当作真实对象用
IDBHelper dbHelper = new DBHelperProxy();
dbHelper.Save(userInfo);

优点:简单直观,不需要任何框架支持。

缺点:每个类都要手写一个代理类,代码量大,维护成本高。

实现方式二:动态代理(.NET Core 版)

.NET Core 提供了 DispatchProxy,可以在运行时动态生成代理,不需要为每个类手写代理代码。

csharp 复制代码
// 真实代理:拦截所有方法调用
public class MyRealProxy<T> : DispatchProxy
{
    public T _Instance = default;

    protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
    {
        // 方法执行前的通用逻辑
        Console.WriteLine("方法执行前可以加入的逻辑");

        // 调用真实方法
        var result = targetMethod?.Invoke(_Instance, args);

        // 方法执行后的通用逻辑
        Console.WriteLine("方法执行后可以加入的逻辑");

        return result;
    }
}

// 透明代理:负责创建代理对象
public static class TransparentProxy
{
    public static T Create<T>(T instance)
    {
        dynamic proxy = DispatchProxy.Create<T, MyRealProxy<T>>()!;
        proxy._Instance = instance;
        return proxy;
    }
}

// 使用
IDBHelper dbHelper = new MySQLDBHelper();
dbHelper = TransparentProxy.Create<IDBHelper>(dbHelper);
dbHelper.Save(userInfo); // 自动触发前置/后置逻辑

注:.NET Framework 时代用的是 RealProxy(基于 .NET Remoting),.NET Core 之后已移除,统一用 DispatchProxy

动态代理的进阶用法

Invoke 方法里,可以通过反射读取方法上的自定义特性(Attribute),实现更细粒度的控制:

csharp 复制代码
// 比如:只有标了 [Log] 特性的方法才记录日志
if (targetMethod.IsDefined(typeof(LogAttribute), true))
{
    // 记录日志
}

AOP 的价值

  • 业务代码只关注业务逻辑,通用逻辑统一在切面里处理
  • 代码复用,一处修改,全局生效
  • 方便团队协作,业务开发和基础设施开发分离

实际框架中的 AOP

在实际项目中,AOP 通常通过以下方式实现:

方式 代表框架
动态代理 Castle DynamicProxy、AspectCore
IL 编织 PostSharp
中间件管道 ASP.NET Core Middleware
过滤器 ASP.NET Core Filter(ActionFilter、ExceptionFilter 等)

四、DDD ------ 领域驱动设计

核心思想

领域驱动设计(Domain-Driven Design)不是一种编程语法,而是一种软件架构方法论,适用于复杂业务系统的设计。

它的核心理念是:软件的核心复杂性来自业务领域本身,代码结构应该反映业务模型

关键概念

限界上下文(Bounded Context)

把一个大系统按业务边界拆分成多个子域,每个子域有自己独立的模型和语言。比如"订单系统"和"库存系统"是两个不同的限界上下文,它们对"商品"的理解可能不同。

聚合根(Aggregate Root)

一组相关对象的入口,外部只能通过聚合根来操作内部对象,保证数据一致性。

实体(Entity)

有唯一标识的对象,即使属性相同,只要 ID 不同就是不同的实体。比如两个同名用户,是两个不同的实体。

值对象(Value Object)

没有唯一标识,只靠属性值来区分。比如"地址",两个地址只要内容相同就认为是同一个。

领域服务(Domain Service)

不属于任何实体的业务逻辑,放在领域服务里。

仓储(Repository)

负责聚合根的持久化,隔离领域层和数据访问层。

DDD 与 OOP/AOP 的关系

DDD 是更高层次的架构思想,它告诉你如何组织代码结构;OOP 是实现手段,DDD 的各种概念(实体、值对象、聚合根)都用 OOP 来实现;AOP 则负责处理横切关注点,在 DDD 架构中同样适用。

适用场景

DDD 适合业务逻辑复杂、团队规模较大的项目。对于简单的 CRUD 应用,引入 DDD 反而会增加不必要的复杂度。


五、四种思想的对比总结

维度 POP OOP AOP DDD
关注点 执行步骤 对象和职责 横切关注点 业务领域模型
扩展方式 修改代码 替换对象(接口+多实现) 切面拦截 领域模型演进
适用规模 小脚本 中小型项目 通用功能增强 复杂业务系统
核心原则 开闭原则、依赖倒置 不破坏封装 领域模型驱动

六、一个贯穿全文的演进脉络

以"用户注册"为例,四种思想的演进是这样的:

复制代码
POP:把注册步骤写成一段顺序代码
  ↓ 问题:难以维护,无法复用
OOP:把用户、数据库操作封装成对象,用接口解耦
  ↓ 问题:想加日志,只能修改对象或替换对象
AOP:用代理模式在方法前后插入日志,不动原有代码
  ↓ 问题:系统越来越大,业务边界模糊
DDD:按业务领域划分边界,用领域模型驱动设计

每一步都是在解决上一步遗留的问题,没有哪种思想是万能的,关键是在合适的场景用合适的工具。

相关推荐
阿拉斯攀登1 小时前
第 19 篇 驱动性能优化与功耗优化实战
android·驱动开发·瑞芯微·嵌入式驱动·安卓驱动
hua872222 小时前
Golang 构建学习
java·开发语言·学习
2301_803554522 小时前
qt信号槽机制以及底层实现原理
开发语言·qt
大傻^2 小时前
LangChain4j RAG 核心:Document、Embedding 与向量存储抽象
开发语言·人工智能·python·embedding·langchain4j
笨笨马甲2 小时前
Qt 音视频编解码
开发语言·qt
Halo_tjn2 小时前
Java 三个修饰符 相关知识点
java·开发语言
2401_883035462 小时前
C++20概念(Concepts)入门指南
开发语言·c++·算法
番茄去哪了2 小时前
Java基础面试题day01
java·开发语言·后端·javase·八股·面向对象编程
wuqingshun3141592 小时前
说说进程和线程的区别?
java·开发语言·jvm