# 组织数据与行为:方法与独立函数
学习目标:对比将业务逻辑绑定为对象成员方法与作为外部独立纯函数的取舍,理解数据与行为的内聚方式。
前置知识与范围:结构体与函数。本篇以计算订单总额(含商品项小计与运费)为例,对比方法与独立函数。
# 问题与边界
给定一个订单,包含商品项列表 items(每个商品项包含单价 price_cents 和数量 quantity)以及固定运费 shipping_cents:
- 数据示例:
- Item 1: 单价 1000 分,数量 2(小计 2000 分)
- Item 2: 单价 500 分,数量 1(小计 500 分)
- 运费:300 分
- 计算契约:
- 总额 = 商品小计总和 + 运费 =
(1000 * 2) + (500 * 1) + 300 = 2800分。 - 若商品列表为空,总额恰好等于运费
300分。
- 总额 = 商品小计总和 + 运费 =
# 关键差异
| 语言 | 成员方法风格 | 独立函数风格 | 扩展函数能力 |
|---|---|---|---|
| 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 等提出的接口原则)鼓励:若一个操作仅需通过公开接口完成,优先将其实现为非成员非友元自由函数,以增强封装性。
# 迁移误区与适用限制
- 职责膨胀(God Class):把所有相关或不相关的逻辑(如订单格式化、支付网关调用、存盘保存)统统作为方法写进
Order类中,导致实体臃肿难以维护。 - 无状态方法盲目面向对象:如果一个方法完全不读取、也不修改对象私有字段,将其做成成员方法只是增加了耦合,应该重构为无副作用的独立纯函数。
# 运行与验证
语言版本基线见示例基线。 所有程序运行后控制台输出均为:
纯文本total: 2800
订单金额汇总逻辑校验全部通过。