计算机语言特性中的代码复用

39 阅读8分钟

代码复用(Code Reuse)是软件工程中的核心原则,通过避免重复代码来提高开发效率和代码质量。本文总结计算机语言特性中的代码复用,包括函数、类、泛型等机制,并对比不同语言的实现方式。

函数级复用

函数是代码复用的基础单元,通过封装可重复使用的逻辑来避免代码重复。

函数调用

函数调用是最直接的复用方式,将重复的逻辑封装成函数,在需要时调用。

# Python
def calculate_tax(amount, rate):
    return amount * rate

tax1 = calculate_tax(100, 0.1)
tax2 = calculate_tax(200, 0.15)
// TypeScript
function calculateTax(amount: number, rate: number): number {
  return amount * rate;
}

const tax1 = calculateTax(100, 0.1);
const tax2 = calculateTax(200, 0.15);
// Java
public double calculateTax(double amount, double rate) {
    return amount * rate;
}

double tax1 = calculateTax(100, 0.1);
double tax2 = calculateTax(200, 0.15);
// C++
double calculateTax(double amount, double rate) {
    return amount * rate;
}

double tax1 = calculateTax(100, 0.1);
double tax2 = calculateTax(200, 0.15);

高阶函数

高阶函数(Higher-Order Function)接受函数作为参数或返回函数,实现更抽象的复用。

// TypeScript - 函数作为参数
const numbers = [1, 2, 3, 4];
const doubled = numbers.map((x) => x * 2); // map 接受函数作为参数
# Python - 返回函数
def multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

times2 = multiplier(2)  # 返回新函数
print(times2(5))  # 10
// Java - 函数作为参数
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> doubled = numbers.stream()
    .map(x -> x * 2)  // map 接受 lambda 表达式
    .collect(Collectors.toList());

// Java - 使用函数式接口
Function<Integer, Integer> multiplier(int factor) {
    return x -> x * factor;  // 返回函数
}
// C++ - 函数作为参数(使用 std::function)
#include <functional>
#include <vector>

void applyToAll(std::vector<int>& vec, std::function<int(int)> func) {
    for (auto& x : vec) {
        x = func(x);
    }
}

std::vector<int> numbers = {1, 2, 3, 4};
applyToAll(numbers, [](int x) { return x * 2; });  // 传入 lambda

高阶函数通过参数化行为而非数据,实现了更灵活的代码复用。

结构化复用

结构化复用通过类、继承、组合、接口等机制,实现数据和行为的组织与复用。

类和对象

类(Class)封装数据和方法,对象(Object)是类的实例,实现了数据和行为的复用。

# Python
class Calculator:
    def add(self, a, b):
        return a + b

    def multiply(self, a, b):
        return a * b

calc = Calculator()
result = calc.add(1, 2)
// TypeScript
class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }

  multiply(a: number, b: number): number {
    return a * b;
  }
}

const calc = new Calculator();
const result = calc.add(1, 2);
// Java
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }
}

Calculator calc = new Calculator();
int result = calc.add(1, 2);
// C++
class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }

    int multiply(int a, int b) {
        return a * b;
    }
};

Calculator calc;
int result = calc.add(1, 2);

继承

继承(Inheritance)允许子类复用父类的代码,并扩展或修改行为。

# Python
class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof"

class Cat(Animal):
    def speak(self):
        return "Meow"
// TypeScript
class Animal {
  speak(): string {
    return "";
  }
}

class Dog extends Animal {
  speak(): string {
    return "Woof";
  }
}
// Java
class Animal {
    public String speak() {
        return "";
    }
}

class Dog extends Animal {
    @Override
    public String speak() {
        return "Woof";
    }
}
// C++
class Animal {
public:
    virtual std::string speak() {
        return "";
    }
};

class Dog : public Animal {
public:
    std::string speak() override {
        return "Woof";
    }
};

接口(Interface)和协议(Protocol) 是定义类型契约的机制,通过多态(Polymorphism)支持代码复用。它们不直接包含可复用的代码,而是通过约定统一的方法签名,让不同类的实例可以被同一段代码处理。

  • 接口:名义类型(Nominal Typing),类必须显式声明实现接口
  • 协议:结构类型(Structural Typing),只要结构匹配即满足协议
# Python - 协议(结构类型)
from typing import Protocol

class Drawable(Protocol):  # 协议
    def draw(self) -> str:
        ...  # 这里用 ... 或 pass 都可以,但不要有实际实现

class Circle:
    def draw(self) -> str:  # 无需显式声明实现协议
        return "Drawing circle"

def render(shape: Drawable):  # Circle 自动满足协议
    print(shape.draw())

# 使用示例
circle = Circle()
render(circle)  # 输出: Drawing circle
// TypeScript - 接口支持多态
interface Animal {
  speak(): string;
}

class Dog implements Animal {
  speak() {
    return "Woof";
  }
}

// 通过接口复用同一个函数
function makeSound(animal: Animal) {
  console.log(animal.speak());
}

makeSound(new Dog()); // Woof

组合

组合(Composition)通过包含其他对象来复用功能,比继承更灵活。

# Python
class Engine:
    def start(self):
        return "Engine started"

class Car:
    def __init__(self):
        self.engine = Engine()  # 组合

    def start(self):
        return self.engine.start()
// TypeScript
class Engine {
  start(): string {
    return "Engine started";
  }
}

class Car {
  private engine: Engine;

  constructor() {
    this.engine = new Engine(); // 组合
  }

  start(): string {
    return this.engine.start();
  }
}
// Java
class Engine {
    public String start() {
        return "Engine started";
    }
}

class Car {
    private Engine engine;  // 组合

    public Car() {
        this.engine = new Engine();
    }

    public String start() {
        return engine.start();
    }
}
// C++
class Engine {
public:
    std::string start() {
        return "Engine started";
    }
};

class Car {
private:
    Engine engine;  // 组合

public:
    std::string start() {
        return engine.start();
    }
};

类型参数化复用

类型参数化通过类型参数编写适用于多种类型的代码,避免重复实现相似逻辑。不同语言采用不同的实现原理,各有权衡。

C++ - 编译期单态化(Monomorphization)

C++ 模板在编译期为每个类型实例生成独立的机器码,实现零运行时开销,但会增加二进制文件大小。

// C++ - 函数模板
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int i = max(3, 5);          // 编译期生成 max<int> 的独立代码
double d = max(3.1, 5.2);   // 编译期生成 max<double> 的独立代码
// C++ - 类模板
template<typename T>
class Stack {
private:
    std::vector<T> elements;

public:
    void push(T elem) {
        elements.push_back(elem);
    }

    T pop() {
        T elem = elements.back();
        elements.pop_back();
        return elem;
    }
};

Stack<int> intStack;     // 编译期生成 Stack<int> 的完整类代码
Stack<string> strStack;  // 编译期生成 Stack<string> 的完整类代码

Java - 编译期类型检查,运行时擦除类型信息

Java 泛型在编译期进行类型检查,保证类型安全,但在运行时擦除泛型类型参数(如 List<String><String>),退化为原始类型。注意:虽然泛型信息被擦除,但对象本身仍是强类型的。

// Java - 泛型函数
public <T> T first(List<T> items) {
    return items.get(0);
}

Integer num = first(Arrays.asList(1, 2, 3));  // 编译期检查类型
String text = first(Arrays.asList("a", "b")); // 运行时泛型 <T> 被擦除
// Java - 类型擦除示例
List<String> strings = new ArrayList<>();
strings.add("hello");
strings.add(123);  // ❌ 编译期错误:类型不匹配,无法通过编译

// 运行时 List<String> 和 List<Integer> 是同一个类(都是 ArrayList)
String s = strings.get(0);  // ✅ 编译器自动插入 (String) 强制转换

// 可以通过原始类型绕过编译期检查(不推荐)
List rawList = strings;     // 转换为原始类型
rawList.add(123);           // ⚠️ 编译器警告,但允许通过
String wrong = strings.get(1);  // ❌ 运行时 ClassCastException
// 原因:编译器插入的 (String) 转换失败,因为实际存储的是 Integer

TS/Python - 编译期类型检查,运行时动态类型

TypeScript 和 Python 的类型提示仅用于编译期(或静态分析期)的类型检查,运行时完全是动态类型,变量可以随时改变类型,类型信息不存在。

// TypeScript - 泛型函数
function first<T>(items: T[]): T {
  return items[0];
}

const num = first([1, 2, 3]); // 编译期推断 T 为 number
const text = first(["a", "b"]); // 编译期推断 T 为 string
// 编译为 JavaScript 后,类型信息完全消失
// TypeScript - 类型检查 vs 运行时行为
let value: string = "hello";
value = 123; // ❌ TypeScript 编译错误,无法通过 tsc 编译

// 如果绕过类型检查(如使用 @ts-ignore 或直接写 JS):
// JavaScript 运行时:✅ 完全合法,变量可随时改变类型
# Python - 泛型提示
from typing import List

def first(items: List[str]) -> str:
    return items[0]

result = first([1, 2, 3])  # ⚠️ 类型检查器警告,但运行时正常执行
# Python - 运行时动态类型
items: list[str] = ["hello"]
items.append(123)  # ✅ 运行时允许,类型提示不影响执行

x: str = "hello"
x = 123  # ✅ 完全合法,变量可随时改变类型

宏(Macro)在预处理阶段进行文本替换,实现代码复用。C、C++ 支持,Python、TypeScript、Java 不支持。

// C - 宏
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int result = MAX(3, 5);  // 预处理后替换为 ((3) > (5) ? (3) : (5))
// C++ - 宏(用法与 C 相同)
#define SQUARE(x) ((x) * (x))

int result = SQUARE(5);  // 预处理后替换为 ((5) * (5))

装饰器包装复用

装饰器(Decorator)通过包装现有函数或类来复用和增强功能,在不修改原代码的情况下添加额外行为。Python、TypeScript 支持,Java、C++ 不支持。

# Python - 装饰器
def timing(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        print(f"Time: {time.time() - start}")
        return result
    return wrapper

@timing
def process():
    import time
    time.sleep(1)

process()  # 输出执行时间
// TypeScript - 装饰器
function timing(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    const start = Date.now();
    const result = original.apply(this, args);
    console.log(`Time: ${Date.now() - start}ms`);
    return result;
  };
}

class Service {
  @timing
  process() {
    // 执行逻辑
  }
}

注解(Annotation)在写法上与装饰器相似(都使用 @ 符号),但本质不同:装饰器直接包装并修改函数/类的行为,而注解只是添加元数据标记,由编译器或框架在编译/运行时读取并处理。例如 Java 的 @Override 用于编译期检查,Spring 的 @Autowired 用于运行时依赖注入。

模块化复用

模块(Module)和包(Package)系统是跨文件、跨项目代码复用的基础,通过导入/导出机制实现代码共享。

# 导入整个模块
import math
print(math.sqrt(16))  # 4.0

# 导入特定函数
from datetime import datetime
print(datetime.now())

# 导入第三方库
import requests
response = requests.get('https://api.example.com')
// 导出函数和类
export function calculateTax(amount: number): number {
  return amount * 0.1;
}

export class User {
  constructor(public name: string) {}
}

// 导入使用
import { calculateTax, User } from "./utils";
import express from "express"; // 第三方库
// 声明包
package com.example.utils;

public class Calculator {
    public static int add(int a, int b) {
        return a + b;
    }
}

// 导入使用
import com.example.utils.Calculator;
import java.util.ArrayList;  // 标准库
import java.util.*;          // 通配符导入
// 标准库头文件
#include <vector>
#include <string>
#include <iostream>

// 自定义头文件
#include "myheader.h"

// C++20 模块(现代方式)
import std.core;
import mymodule;

模块系统是代码复用的基础设施,所有其他复用机制都依赖于模块系统来组织和分发代码。