面向对象、面向过程

吴书松
吴书松
发布于 2026-02-26 / 2 阅读
0
0

面向对象、面向过程

面向过程编程(POP)和面向对象编程(OOP)是两种主要的编程范式。理解它们的核心区别,并通过 Java(一种典型的面向对象语言)来举例说明,可以帮助你更深入地掌握这两种思想。

一、面向过程编程(Procedure Oriented Programming)

核心思想:将程序看作一系列按顺序执行的操作步骤,通过函数(或过程)来分解任务。数据(变量)和操作数据的方法(函数)是分开的。

特点

  • 以函数为基本单位,函数处理数据。

  • 数据(全局变量或局部变量)和函数分离。

  • 程序流程 = 算法 + 数据结构(算法在前,数据结构在后)。

  • 典型语言:C、Pascal。

类比:就像一份详细的菜谱,告诉你先做什么、后做什么,每一步操作什么食材。


二、面向对象编程(Object Oriented Programming)

核心思想:将程序看作一组相互协作的对象。每个对象包含数据(属性)和操作数据的方法(行为),对象之间通过消息传递进行交互。

特点

  • 以类/对象为基本单位,将数据和操作封装在一起。

  • 三大特性:封装、继承、多态。

  • 程序流程 = 对象 + 消息传递。

  • 典型语言:Java、C++、Python。

类比:就像一个厨房里的各个角色(厨师、烤箱、食材),每个角色有自己的属性和技能,他们协作完成一道菜。


三、用 Java 解释两种范式

Java 是面向对象的语言,但我们可以用 Java 代码分别写出面向过程和面向对象的风格,从而对比它们的差异。

我们以一个简单的问题为例:计算圆的面积和周长

3.1 面向过程的 Java 写法(尽管不推荐,但可行)

在 Java 中,我们可以将所有代码写在 main 方法里,或者定义一些静态方法,数据和操作分开。

java

public class ProceduralCircle {
    // 静态方法:计算面积
    public static double calculateArea(double radius) {
        return Math.PI * radius * radius;
    }

    // 静态方法:计算周长
    public static double calculatePerimeter(double radius) {
        return 2 * Math.PI * radius;
    }

    public static void main(String[] args) {
        double radius = 5.0;
        
        // 直接调用函数处理数据
        double area = calculateArea(radius);
        double perimeter = calculatePerimeter(radius);
        
        System.out.println("半径: " + radius);
        System.out.println("面积: " + area);
        System.out.println("周长: " + perimeter);
    }
}

分析

  • 数据(radius)和操作(calculateAreacalculatePerimeter)是分离的。

  • 程序按照步骤顺序执行:定义数据 → 调用函数 → 输出结果。

  • 如果需求变化(例如增加颜色属性),需要修改多处,可扩展性差。

3.2 面向对象的 Java 写法

我们将圆抽象为一个类,把数据和操作封装在一起。

java

// 定义圆类
public class Circle {
    // 属性(数据)
    private double radius;

    // 构造方法
    public Circle(double radius) {
        this.radius = radius;
    }

    // 方法(操作)
    public double getArea() {
        return Math.PI * radius * radius;
    }

    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }
}

// 测试类
public class OOPDemo {
    public static void main(String[] args) {
        // 创建一个圆对象
        Circle circle = new Circle(5.0);
        
        // 通过对象调用方法
        double area = circle.getArea();
        double perimeter = circle.getPerimeter();
        
        System.out.println("半径: " + circle.getRadius());
        System.out.println("面积: " + area);
        System.out.println("周长: " + perimeter);
    }
}

分析

  • 数据 radius 和方法 getArea()getPerimeter() 封装在 Circle 类中。

  • 创建对象 circle,通过对象来调用方法,体现了“消息传递”。

  • 如果需要增加圆的颜色,只需在 Circle 类中添加属性 color 和相关方法,不影响外部使用。

  • 可以利用继承、多态等特性进一步扩展(例如增加圆柱体类继承圆类)。


四、总结对比

特性

面向过程

面向对象

基本单位

函数

类/对象

数据与操作

分离

封装在一起

程序组织

按步骤顺序执行

通过对象协作

代码复用

函数复用

类继承、组合复用

扩展性

修改代码时影响范围大

容易扩展,符合开闭原则

适用场景

简单任务、对性能要求极高的底层系统

大型复杂系统,需要模拟现实世界

Java 作为面向对象的语言,鼓励我们使用面向对象的思想来设计程序,从而获得更好的可维护性、可扩展性和代码复用性。不过,理解面向过程对于理解计算机底层执行逻辑仍然很有帮助。在实际开发中,我们常常会结合两者,用面向对象设计整体架构,而在具体方法内部使用面向过程的逻辑来解决问题。

5、充血模型和面向对象的关系

1. 贫血模型 vs 充血模型

这两个概念来自领域驱动设计(DDD),描述的是领域对象(例如实体、值对象)中数据和业务逻辑的分布方式。

  • 贫血模型

    • 领域对象只包含数据属性(getter/setter),几乎不包含业务逻辑。

    • 业务逻辑全部写在服务层(Service)中。

    • 结果:领域对象只是数据的容器,类似于“数据类”,而服务层负责处理所有逻辑,这实际上是一种面向过程的风格(数据与操作分离)。

  • 充血模型

    • 领域对象既包含数据属性,也包含与该数据相关的业务逻辑。

    • 服务层负责协调、事务等,而具体的业务规则(如计算、状态变更)封装在领域对象内部。

    • 结果:领域对象是自完备的,符合面向对象的封装特性,也更容易维护和测试。


2. 面向对象模型与充血模型的联系

  • 面向对象模型的核心思想之一就是封装:将数据和对数据的操作绑定在一起,形成一个独立的实体(对象)。这正是充血模型所追求的——让对象“有血有肉”,不仅存储状态,还能表现行为。

  • 因此,充血模型可以看作是面向对象设计原则在领域层的一种落地。如果一个系统完全采用充血模型,那么它的领域对象就是典型的面向对象设计产物。


3. 举例说明

以前面的 Circle 类为例:

  • 贫血风格的 Circle

    java

    public class Circle {
        private double radius;
        // getter/setter
    }

    然后在一个 CircleService 中:

    java

    public class CircleService {
        public double calculateArea(Circle circle) {
            return Math.PI * circle.getRadius() * circle.getRadius();
        }
    }

    这种风格下,Circle 只是一个数据载体,逻辑都在外部,不利于扩展(比如增加周长计算又要改服务)。

  • 充血风格的 Circle(即我们之前的面向对象版本):

    java

    public class Circle {
        private double radius;
        public double getArea() { ... }
        public double getPerimeter() { ... }
    }

    逻辑内聚在对象内部,外部只需调用对象的方法即可。

很明显,充血模型更贴近面向对象的封装思想。


4. 为什么会有贫血模型?

贫血模型在某些场景下也有其合理性,比如:

  • 简单的 CRUD 应用,业务逻辑很少,使用贫血模型可以快速开发。

  • 与某些框架(如早期的 EJB、Hibernate 的 POJO)结合时,贫血模型更易于持久化。

  • 团队习惯或历史原因。

但贫血模型常常导致“贫血领域对象+事务脚本”的架构,随着业务复杂度的增加,代码会变得难以维护,这也是领域驱动设计提倡充血模型的原因。


5. 总结

  • 面向对象模型是一种编程范式,强调数据与行为的一体化。

  • 充血模型是面向对象思想在领域层的具体体现,它让领域对象同时拥有状态和行为。

  • 因此,可以说充血模型是符合面向对象设计的一种领域模型风格,而贫血模型则更接近面向过程的思维。

你的理解是正确的:它们确实类似,充血模型就是面向对象思想在领域建模中的一种实践。在实际开发中,我们应尽量让领域对象“充血”,以更好地利用面向对象的优势。


评论