AI Library · 知识库

# 组织数据与行为:方法与独立函数

学习目标:对比将业务逻辑绑定为对象成员方法与作为外部独立纯函数的取舍,理解数据与行为的内聚方式。

前置知识与范围:结构体与函数。本篇以计算订单总额(含商品项小计与运费)为例,对比方法与独立函数。

# 问题与边界

给定一个订单,包含商品项列表 items(每个商品项包含单价 price_cents 和数量 quantity)以及固定运费 shipping_cents:

# 关键差异

语言 成员方法风格 独立函数风格 扩展函数能力
Rust impl Order { fn total(&self) } fn calculate_total(order: &Order) 通过 trait 为已有类型追加方法
Go func (o Order) Total() int64 func CalculateTotal(o Order) int64 不支持扩展已有包类型的方法
Java 类内部定义 public long total() 工具类静态方法 OrderUtils.calculateTotal(order) 不支持直接扩展方法
JavaScript order.total()(类实例方法) calculateTotal(order)(普通纯函数) 可通过原型链添加,但不推荐
TypeScript 类方法或接口 独立函数配合结构化类型 模块扩充(Module Augmentation)
Kotlin 成员方法 fun total(): Long 顶层函数 fun calculateTotal(order: Order) 原生支持扩展函数 fun Order.total()
Python 实例方法 def total(self) 模块级函数 def calculate_total(order) 动态猴子补丁(不推荐)
C++ 成员函数 int64_t total() const 非成员自由函数 int64_t calculate_total(const Order&) ADL(参数依赖查找)支持自由函数接口

# 各语言实现

# Rust

Ruststruct OrderItem {
    price_cents: i64,
    quantity: i64,
}

struct Order {
    items: Vec<OrderItem>,
    shipping_cents: i64,
}

// 风格一:关联方法(封装在 Order 内部)
impl Order {
    fn subtotal(&self) -> i64 {
        self.items.iter().map(|i| i.price_cents * i.quantity).sum()
    }

    fn total(&self) -> i64 {
        self.subtotal() + self.shipping_cents
    }
}

// 风格二:独立纯函数(解耦数据结构与外部计费逻辑)
fn calculate_total_taxed(order: &Order, tax_rate: f64) -> i64 {
    let base = order.total();
    base + (base as f64 * tax_rate) as i64
}

fn main() {
    let order = Order {
        items: vec![
            OrderItem { price_cents: 1000, quantity: 2 },
            OrderItem { price_cents: 500, quantity: 1 },
        ],
        shipping_cents: 300,
    };

    assert_eq!(order.total(), 2800);
    assert_eq!(calculate_total_taxed(&order, 0.1), 3080);

    println!("total: {}", order.total());
}

Rust 中数据结构定义(struct)与行为实现(impl)显式分离,使代码可以按需组织。

# Go

Gopackage main

import "fmt"

type OrderItem struct {
	PriceCents int64
	Quantity   int64
}

type Order struct {
	Items         []OrderItem
	ShippingCents int64
}

// 成员方法
func (o Order) Total() int64 {
	var sum int64
	for _, it := range o.Items {
		sum += it.PriceCents * it.Quantity
	}
	return sum + o.ShippingCents
}

// 独立函数
func CalculateTotal(o Order) int64 {
	return o.Total()
}

func main() {
	order := Order{
		Items: []OrderItem{
			{PriceCents: 1000, Quantity: 2},
			{PriceCents: 500, Quantity: 1},
		},
		ShippingCents: 300,
	}

	if order.Total() != 2800 || CalculateTotal(order) != 2800 {
		panic("mismatch")
	}

	fmt.Printf("total: %d\n", order.Total())
}

Go 的方法本质上就是以第一个参数作为接收者的普通函数语法糖。

# Java

Javaimport java.util.List;

public class Main {
    public record OrderItem(long priceCents, long quantity) {}
    public record Order(List<OrderItem> items, long shippingCents) {
        // Record 紧凑方法
        public long total() {
            long sum = 0;
            for (OrderItem it : items) {
                sum += it.priceCents() * it.quantity();
            }
            return sum + shippingCents;
        }
    }

    // 独立静态函数
    public static long calculateTotal(Order order) {
        return order.total();
    }

    public static void main(String[] args) {
        Order order = new Order(
            List.of(new OrderItem(1000, 2), new OrderItem(500, 1)),
            300
        );

        assert order.total() == 2800;
        assert calculateTotal(order) == 2800;

        System.out.println("total: " + order.total());
    }
}

Java 16+ 的 record 允许在内部声明辅助计算方法,保持数据与基本计算的高内聚。

# JavaScript

JavaScriptconst assert = {
  equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn, errType) => {
    try { fn(); throw new Error("expected throw"); } catch (e) {
      if (e.message === "expected throw") throw e;
      if (errType && !(e instanceof errType)) throw e;
    }
  }
};

// 纯数据对象 + 独立计算函数
function calculateTotal(order) {
  const subtotal = order.items.reduce(
    (sum, it) => sum + it.priceCents * it.quantity,
    0
  );
  return subtotal + order.shippingCents;
}

const order = {
  items: [
    { priceCents: 1000, quantity: 2 },
    { priceCents: 500, quantity: 1 },
  ],
  shippingCents: 300,
};

assert.equal(calculateTotal(order), 2800);

console.log(`total: ${calculateTotal(order)}`);

在前端与 Node 领域,纯字面量对象配合无状态纯函数是非常主流的函数式组织模式。

# TypeScript

TypeScriptconst assert = {
  equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn: () => void, errType?: unknown) => {
    try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
      if (e instanceof Error && e.message === "expected throw") throw e;
      if (typeof errType === "function" && !(e instanceof errType)) throw e;
    }
  }
};

interface OrderItem {
  readonly priceCents: number;
  readonly quantity: number;
}

interface Order {
  readonly items: readonly OrderItem[];
  readonly shippingCents: number;
}

function calculateTotal(order: Order): number {
  const subtotal = order.items.reduce(
    (sum, it) => sum + it.priceCents * it.quantity,
    0
  );
  return subtotal + order.shippingCents;
}

const order: Order = {
  items: [
    { priceCents: 1000, quantity: 2 },
    { priceCents: 500, quantity: 1 },
  ],
  shippingCents: 300,
};

assert.equal(calculateTotal(order), 2800);

console.log(`total: ${calculateTotal(order)}`);

TypeScript 的结构子类型(Structural Typing)支持直接传入任何满足形状要求的对象给独立函数。

# Kotlin

Kotlindata class OrderItem(val priceCents: Long, val quantity: Long)
data class Order(val items: List<OrderItem>, val shippingCents: Long)

// Kotlin 原生扩展函数:外表是方法,本质是静态函数
fun Order.total(): Long {
    val subtotal = items.sumOf { it.priceCents * it.quantity }
    return subtotal + shippingCents
}

fun main() {
    val order = Order(
        items = listOf(OrderItem(1000, 2), OrderItem(500, 1)),
        shippingCents = 300
    )

    check(order.total() == 2800L)

    println("total: ${order.total()}")
}

Kotlin 的扩展函数特性完美平衡了“调用时点号语法补全的流畅度”与“无需侵入原类型源码”的解耦诉求。

# Python

Pythonfrom typing import NamedTuple, List

class OrderItem(NamedTuple):
    price_cents: int
    quantity: int

class Order(NamedTuple):
    items: List[OrderItem]
    shipping_cents: int

    def total(self) -> int:
        return sum(it.price_cents * it.quantity for it in self.items) + self.shipping_cents

def calculate_total(order: Order) -> int:
    return order.total()

if __name__ == "__main__":
    order = Order(
        items=[OrderItem(1000, 2), OrderItem(500, 1)],
        shipping_cents=300
    )

    assert order.total() == 2800
    assert calculate_total(order) == 2800

    print(f"total: {order.total()}")

Python 的类中方法清晰可见,亦可自由定义外部独立函数供流水线消费。

# C++

C++#include <iostream>
#include <vector>
#include <numeric>
#include <cstdint>
#include <cassert>

struct OrderItem {
    int64_t price_cents;
    int64_t quantity;
};

struct Order {
    std::vector<OrderItem> items;
    int64_t shipping_cents;

    int64_t total() const {
        int64_t sum = 0;
        for (const auto& it : items) {
            sum += it.price_cents * it.quantity;
        }
        return sum + shipping_cents;
    }
};

// 自由函数
int64_t calculate_total(const Order& order) {
    return order.total();
}

int main() {
    Order order{
        {{1000, 2}, {500, 1}},
        300
    };

    assert(order.total() == 2800);
    assert(calculate_total(order) == 2800);

    std::cout << "total: " << order.total() << "\n";
}

C++ 哲学(尤其是 Herb Sutter 等提出的接口原则)鼓励:若一个操作仅需通过公开接口完成,优先将其实现为非成员非友元自由函数,以增强封装性。

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为:

纯文本total: 2800

订单金额汇总逻辑校验全部通过。

# 相关阅读