Files
2026_DesignAI/officefile/supplements/01-foundations/01.1-modular-intelligence.md
T
pengxiao 219232de74 refactor: 重组项目目录结构
以讲义内容为骨架迁移到标准目录格式:
- officefile/ 主内容(12章 + 附录 + CC4SI补充)
- dofile/ 代码示例(11个Python脚本)
- data/ 图片资源
- output/ 生成输出(忽略)
- Archive/ 归档旧目录(忽略)
- .claude/skills/ 保留markdown-to-docx工具链
- .pandoc/ 保留CSL和本地化配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 14:00:56 +08:00

14 KiB
Raw Blame History

01.1 智能的模块化视角

核心问题

为什么智能系统需要模块化设计? 技能(Skill)的本质是什么?如何设计可复用的智能组件? 函数式组合思想如何应用于AI系统?


概念讲解

模块化的必要性

随着系统复杂度增加,模块化变得必不可少:

复杂度与模块化的关系

低复杂度 ──→ [单体脚本] ──→ 可维护
                   ↑
                   简单直接

中复杂度 ──→ [函数库] ──→ 需要组织
              ↑
              按功能分类

高复杂度 ──→ [模块化系统] ──→ 必须模块化
                ↑
                清晰边界,可组合

模块化的收益

收益类型 说明 例子
可理解性 每个模块可独立理解 理解缓冲区分析不需要理解投影变换
可测试性 模块可单独测试 测试空间索引不需要完整工作流
可复用性 模块可在不同场景使用 缓冲区算法用于多个项目
可替换性 模块可用等价实现替换 QGIS ↔ ArcGIS 同一功能
可维护性 修改局限在模块内 修复bug不影响其他模块

函数式组合思想

函数式编程的核心:组合小函数构建复杂行为

简单函数 ──┬─── buffer(geom, distance)
           ├─── intersect(a, b)
           ├─── centroid(geom)
           └─── distance(a, b)
              │
              ↓ 组合
              │
   complex_operation = pipe(
       load_data,
       clean_geometry,
       buffer(100),
       intersect(study_area),
       calculate_area,
       format_output
   )

关键特性

  1. 纯函数:相同输入→相同输出,无副作用
  2. 高阶函数:函数可以作为参数和返回值
  3. 不可变数据:数据不修改,而是创建新版本

技能即能力封装

在Claude Code中,技能(Skill)是智能的模块化单元:

# 技能的结构
---
name: skill-name              # 技能名称
description: 技能描述         # 何时使用
parameters:                   # 输入参数
  - param1: type
  - param2: type
returns:                      # 输出
  - result: type
---

## 技能逻辑

具体的执行步骤...

技能设计的三个层次

Level 1: 原子技能
    └── 单一功能,不可再分
        例如:buffer, intersect, dissolve

Level 2: 组合技能
    └── 由原子技能组合而成
        例如:site_selection = buffer + intersect + rank

Level 3: 工作流技能
    └── 完整的决策流程
        例如:ecological_network_analysis

设计原理

模块化的设计原则

1. 单一职责原则 (SRP)

每个模块只做一件事,做好一件事:

# 好的设计:每个函数职责单一
def calculate_distance(geom1, geom2):
    """只计算距离"""
    return geom1.distance(geom2)

def format_distance(distance, unit='m'):
    """只格式化输出"""
    if distance > 1000:
        return f"{distance/1000:.2f} km"
    return f"{distance:.0f} m"

# 使用
dist = calculate_distance(point_a, point_b)
formatted = format_distance(dist)

# 不好的设计:混合了计算和格式化
def calculate_and_format_distance(geom1, geom2):
    distance = geom1.distance(geom2)
    # 格式化逻辑混在一起
    if distance > 1000:
        return f"{distance/1000:.2f} km"
    return f"{distance:.0f} m"

2. 开闭原则 (OCP)

对扩展开放,对修改关闭:

# 使用抽象基类实现扩展性
from abc import ABC, abstractmethod

class SpatialOperation(ABC):
    """空间操作的抽象基类"""

    @abstractmethod
    def execute(self, data):
        pass

class BufferOperation(SpatialOperation):
    """缓冲区操作"""
    def __init__(self, distance):
        self.distance = distance

    def execute(self, data):
        return data.buffer(self.distance)

class IntersectOperation(SpatialOperation):
    """相交操作"""
    def __init__(self, other_data):
        self.other_data = other_data

    def execute(self, data):
        return data.intersection(self.other_data)

# 可以添加新操作而不修改现有代码
class UnionOperation(SpatialOperation):
    """合并操作"""
    def execute(self, data):
        return data.union(self.other_data)

3. 依赖倒置原则 (DIP)

依赖抽象而非具体实现:

# 好的设计:依赖抽象
class WorkflowProcessor:
    def __init__(self, operation: SpatialOperation):
        self.operation = operation  # 依赖抽象

    def process(self, data):
        return self.operation.execute(data)

# 可以轻松替换具体实现
processor = WorkflowProcessor(BufferOperation(100))

# 不好的设计:依赖具体实现
class WorkflowProcessor:
    def __init__(self, buffer_distance):
        self.buffer_distance = buffer_distance

    def process(self, data):
        # 硬编码了具体操作
        return data.buffer(self.buffer_distance)

技能接口设计

良好的技能接口设计:

from typing import Protocol, TypeVar, Generic

T = TypeVar('T')

class SkillInput(Protocol[T]):
    """技能输入协议"""
    def validate(self) -> bool:
        """验证输入有效性"""
        ...

class SkillOutput(Protocol[T]):
    """技能输出协议"""
    def to_dict(self) -> dict:
        """转换为可序列化格式"""
        ...

class Skill(Generic[T]):
    """技能基类"""

    name: str
    description: str

    def can_handle(self, input_data: T) -> bool:
        """判断是否能处理此输入"""
        pass

    def execute(self, input_data: T) -> SkillOutput[T]:
        """执行技能"""
        pass

    def estimate_cost(self, input_data: T) -> float:
        """估算执行成本(时间/资源)"""
        pass

代码示例

模块化的空间分析系统

"""
模块化空间分析系统示例
展示如何用函数式组合构建复杂分析
"""
from typing import Callable, List, Any, TypeVar
from functools import reduce
import geopandas as gpd

T = TypeVar('T')

class SpatialPipeline:
    """空间分析流水线"""

    def __init__(self):
        self.steps: List[Callable] = []

    def add_step(self, step: Callable, name: str = None):
        """添加处理步骤"""
        step.name = name or step.__name__
        self.steps.append(step)
        return self

    def execute(self, initial_data):
        """执行流水线"""
        result = initial_data

        for step in self.steps:
            print(f"执行步骤: {getattr(step, 'name', step.__name__)}")
            result = step(result)

        return result

def pipe(*functions):
    """函数式组合工具"""
    return reduce(lambda f, g: lambda x: g(f(x)), functions)

# === 原子操作 ===

def load_data(path: str) -> gpd.GeoDataFrame:
    """加载数据"""
    print(f"加载: {path}")
    return gpd.read_file(path)

def clean_geometry(gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
    """清理几何"""
    print("清理几何")
    # 修复无效几何
    gdf['geometry'] = gdf.geometry.buffer(0)
    return gdf[gdf.geometry.is_valid]

def reproject(gdf: gpd.GeoDataFrame, target_crs: str = 'EPSG:3857') -> gpd.GeoDataFrame:
    """重投影"""
    print(f"重投影到: {target_crs}")
    return gdf.to_crs(target_crs)

def buffer(gdf: gpd.GeoDataFrame, distance: float) -> gpd.GeoDataFrame:
    """缓冲区分析"""
    print(f"缓冲距离: {distance}")
    return gdf.buffer(distance)

def intersect(gdf: gpd.GeoDataFrame, other: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
    """相交分析"""
    print("相交分析")
    return gdf.overlay(other, how='intersection')

def calculate_area(gdf: gpd.GeoDataFrame) -> float:
    """计算面积"""
    area = gdf.geometry.area.sum()
    print(f"总面积: {area:.2f} 平方米")
    return area

# === 高阶操作 ===

def make_buffer(distance: float) -> Callable:
    """缓冲操作工厂函数"""
    return lambda gdf: buffer(gdf, distance)

def make_reproject(crs: str) -> Callable:
    """重投影工厂函数"""
    return lambda gdf: reproject(gdf, crs)

def make_intersect(other_data: gpd.GeoDataFrame) -> Callable:
    """相交工厂函数"""
    return lambda gdf: intersect(gdf, other_data)

# === 使用示例 ===

def example_pipeline_usage():
    """流水线使用示例"""

    # 方式1:使用Pipeline类
    pipeline = SpatialPipeline()
    pipeline.add_step(load_data, "加载数据")
    pipeline.add_step(clean_geometry, "清理几何")
    pipeline.add_step(lambda gdf: reproject(gdf, 'EPSG:3857'), "重投影")
    pipeline.add_step(lambda gdf: buffer(gdf, 100), "缓冲")
    pipeline.add_step(calculate_area, "计算面积")

    # result = pipeline.execute("data.geojson")

    # 方式2:使用函数式组合
    analysis_pipeline = pipe(
        load_data,
        clean_geometry,
        lambda gdf: reproject(gdf, 'EPSG:3857'),
        lambda gdf: buffer(gdf, 100),
        calculate_area
    )

    # result = analysis_pipeline("data.geojson")

    return pipeline

# === 技能封装 ===

class BufferSkill:
    """缓冲区技能"""

    name = "buffer_analysis"
    description = "执行缓冲区分析"

    def __init__(self, distance: float, unit: str = 'm'):
        self.distance = distance
        self.unit = unit

    def execute(self, data: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
        """执行技能"""
        # 确保在合适的坐标系中
        if data.crs and data.crs.is_geographic:
            data = data.to_crs('EPSG:3857')

        result = data.buffer(self.distance)

        return gpd.GeoDataFrame(
            geometry=result,
            crs=data.crs
        )

    def __repr__(self):
        return f"BufferSkill(distance={self.distance}{self.unit})"

class SiteSelectionSkill:
    """选址技能:组合多个原子操作"""

    name = "site_selection"
    description = "基于多准则的选址分析"

    def __init__(self,
                 distance_from_road: float,
                 distance_from_water: float,
                 min_area: float):
        self.road_distance = distance_from_road
        self.water_distance = distance_from_water
        self.min_area = min_area

    def execute(self,
                sites: gpd.GeoDataFrame,
                roads: gpd.GeoDataFrame,
                water: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
        """
        执行选址分析

        组合操作:
        1. 找到距离道路指定范围内的区域
        2. 排除距离水体太近的区域
        3. 筛选面积满足要求的区域
        """

        # 1. 道路缓冲
        road_buffer = roads.buffer(self.road_distance)

        # 2. 水体缓冲(排除区)
        water_buffer = water.buffer(self.water_distance)

        # 3. 找到满足条件的site
        suitable = sites[
            sites.geometry.intersects(road_buffer.union_all()) &
            ~sites.geometry.intersects(water_buffer.union_all())
        ]

        # 4. 面积筛选
        suitable = suitable[suitable.geometry.area >= self.min_area]

        return suitable

if __name__ == "__main__":
    # 示例:构建一个选址分析流水线
    print("=== 模块化空间分析系统 ===\n")

    # 创建技能
    buffer_skill = BufferSkill(distance=500, unit='m')
    print(f"创建技能: {buffer_skill}")

    # 技能可以独立测试
    print("\n技能的核心优势:")
    print("1. 可理解性 - 每个技能职责单一")
    print("2. 可测试性 - 独立测试每个技能")
    print("3. 可复用性 - 在不同场景中使用")
    print("4. 可组合性 - 小技能组合成大技能")

案例分析

QGIS插件架构分析

QGIS的模块化设计是学习的好例子:

QGIS架构
    │
    ├── Core (核心库)
    │   ├── QgsGeometry - 几何操作
    │   ├── QgsVectorLayer - 矢量图层
    │   ├── QgsRasterLayer - 栅格图层
    │   └── QgsProcessing - 处理框架
    │
    ├── Providers (数据提供者)
    │   ├── OGR Provider - 矢量数据
    │   ├── GDAL Provider - 栅格数据
    │   └── PostGIS Provider - 数据库
    │
    ├── Plugins (插件)
    │   ├── 每个插件独立模块
    │   ├── 通过接口访问核心功能
    │   └── 可单独安装/卸载
    │
    └── Processing Algorithms (处理算法)
        ├── 算法库(600+算法)
        ├── 可组合使用
        └── 模型构建器

关键设计模式

  1. Provider模式:数据访问抽象
  2. Plugin模式:功能扩展
  3. Algorithm模式:处理步骤封装

AI系统的启发

# 类似QGIS的AI技能架构
class AISkillRegistry:
    """AI技能注册表"""

    def __init__(self):
        self.skills = {}

    def register(self, skill):
        """注册技能"""
        self.skills[skill.name] = skill

    def get(self, name: str):
        """获取技能"""
        return self.skills.get(name)

    def list_by_category(self, category: str):
        """按类别列出技能"""
        return [s for s in self.skills.values()
                if s.category == category]

# 使用
registry = AISkillRegistry()
registry.register(BufferSkill(distance=100))
registry.register(SiteSelectionSkill(...))

# 查找和使用技能
buffer = registry.get("buffer_analysis")
result = buffer.execute(data)

反思与延伸

思考问题

  1. 边界划分:如何确定一个模块的边界?太小的模块和太大的模块各有什么问题?

  2. 接口设计:设计一个技能接口时,应该考虑哪些因素?

  3. 复用性:什么代码值得复用?什么不值得?

  4. 组合爆炸:当模块数量很大时,如何管理模块之间的依赖?

实践练习

  1. 重构练习:找一个你写的复杂函数,将其拆分为多个小函数

  2. 接口设计:为你熟悉的空间分析操作设计技能接口

  3. 组合挑战:用5个以下的基本操作组合出10个不同的分析流程

延伸阅读

  • "Refactoring" (Martin Fowler) - 重构与模块化
  • "The Art of Unix Programming" - 模块化哲学
  • QGIS Plugin开发指南

关键要点

  1. 模块化是管理复杂性的核心方法
  2. 函数式组合让小函数构建大功能
  3. 技能是智能的封装单元
  4. 良好的接口设计是模块化的关键
  5. QGIS的架构是学习的优秀范例