219232de74
以讲义内容为骨架迁移到标准目录格式: - 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>
563 lines
14 KiB
Markdown
563 lines
14 KiB
Markdown
# 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)是智能的模块化单元:
|
||
|
||
```yaml
|
||
# 技能的结构
|
||
---
|
||
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)**
|
||
|
||
每个模块只做一件事,做好一件事:
|
||
|
||
```python
|
||
# 好的设计:每个函数职责单一
|
||
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)**
|
||
|
||
对扩展开放,对修改关闭:
|
||
|
||
```python
|
||
# 使用抽象基类实现扩展性
|
||
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)**
|
||
|
||
依赖抽象而非具体实现:
|
||
|
||
```python
|
||
# 好的设计:依赖抽象
|
||
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)
|
||
```
|
||
|
||
### 技能接口设计
|
||
|
||
良好的技能接口设计:
|
||
|
||
```python
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## 代码示例
|
||
|
||
### 模块化的空间分析系统
|
||
|
||
```python
|
||
"""
|
||
模块化空间分析系统示例
|
||
展示如何用函数式组合构建复杂分析
|
||
"""
|
||
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系统的启发**:
|
||
|
||
```python
|
||
# 类似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的架构是学习的优秀范例**
|