mxivideo/docs/solid-design-refactoring.md

8.2 KiB
Raw Permalink Blame History

SOLID 设计原则重构指南

🎯 重构目标

使用SOLID设计原则优化media_manager.py提高代码的可维护性、可扩展性和可测试性。

📋 SOLID 原则应用

1. 单一职责原则 (SRP - Single Responsibility Principle)

重构前问题

  • MediaManager类承担了太多职责:依赖管理、视频信息提取、场景检测、文件管理等

重构后改进

# 依赖管理器 - 只负责管理依赖
class DependencyManager:
    def __init__(self):
        self._dependencies = {}
        self._initialize_dependencies()

# 视频信息提取器 - 只负责提取视频信息
class FFProbeVideoInfoExtractor(VideoInfoExtractor):
    def extract_video_info(self, file_path: str) -> Dict:
        # 专门负责使用ffprobe提取视频信息

# 场景检测器 - 只负责场景检测
class PySceneDetectSceneDetector(SceneDetector):
    def detect_scenes(self, file_path: str, threshold: float) -> List[float]:
        # 专门负责使用PySceneDetect检测场景

# 工厂类 - 只负责创建对象
class VideoProcessorFactory:
    def create_video_info_extractor(self) -> VideoInfoExtractor:
        # 专门负责创建合适的提取器

2. 开闭原则 (OCP - Open/Closed Principle)

重构前问题

  • 添加新的视频处理方法需要修改现有代码

重构后改进

# 抽象接口 - 对扩展开放,对修改关闭
class VideoInfoExtractor(ABC):
    @abstractmethod
    def extract_video_info(self, file_path: str) -> Dict:
        pass

class SceneDetector(ABC):
    @abstractmethod
    def detect_scenes(self, file_path: str, threshold: float) -> List[float]:
        pass

# 新的实现可以轻松添加,无需修改现有代码
class NewVideoInfoExtractor(VideoInfoExtractor):
    def extract_video_info(self, file_path: str) -> Dict:
        # 新的实现方法
        pass

3. 里氏替换原则 (LSP - Liskov Substitution Principle)

重构后实现

# 任何VideoInfoExtractor的子类都可以替换基类
def process_video(extractor: VideoInfoExtractor, file_path: str):
    info = extractor.extract_video_info(file_path)  # 可以是任何实现
    return info

# FFProbe实现
ffprobe_extractor = FFProbeVideoInfoExtractor()
info1 = process_video(ffprobe_extractor, "video.mp4")

# OpenCV实现
opencv_extractor = OpenCVVideoInfoExtractor(dependency_manager)
info2 = process_video(opencv_extractor, "video.mp4")

4. 接口隔离原则 (ISP - Interface Segregation Principle)

重构前问题

  • 大而全的接口,客户端被迫依赖不需要的方法

重构后改进

# 专门的接口,客户端只依赖需要的功能
class VideoInfoExtractor(ABC):
    @abstractmethod
    def extract_video_info(self, file_path: str) -> Dict:
        pass

class SceneDetector(ABC):
    @abstractmethod
    def detect_scenes(self, file_path: str, threshold: float) -> List[float]:
        pass

class VideoProcessor(ABC):
    @abstractmethod
    def split_video(self, input_path: str, output_dir: str, scene_times: List[float]) -> List[str]:
        pass

5. 依赖倒置原则 (DIP - Dependency Inversion Principle)

重构前问题

  • 高层模块直接依赖低层模块的具体实现

重构后改进

class MediaManager:
    def __init__(self, 
                 dependency_manager: DependencyManager = None,
                 video_info_extractor: VideoInfoExtractor = None,
                 scene_detector: SceneDetector = None):
        # 依赖注入 - 依赖抽象而不是具体实现
        self.dependency_manager = dependency_manager or globals()['dependency_manager']
        self.factory = VideoProcessorFactory(self.dependency_manager)
        
        # 延迟初始化处理器
        self._video_info_extractor = video_info_extractor
        self._scene_detector = scene_detector
    
    @property
    def video_info_extractor(self) -> VideoInfoExtractor:
        """懒加载 - 依赖抽象接口"""
        if self._video_info_extractor is None:
            self._video_info_extractor = self.factory.create_video_info_extractor()
        return self._video_info_extractor

🏗️ 重构架构

新的类层次结构

DependencyManager (依赖管理)
├── 检查和管理所有外部依赖
└── 提供统一的依赖访问接口

VideoInfoExtractor (抽象接口)
├── FFProbeVideoInfoExtractor (ffprobe实现)
└── OpenCVVideoInfoExtractor (OpenCV实现)

SceneDetector (抽象接口)
├── PySceneDetectSceneDetector (PySceneDetect实现)
└── OpenCVSceneDetector (OpenCV实现)

VideoProcessorFactory (工厂类)
├── 创建VideoInfoExtractor实例
└── 创建SceneDetector实例

MediaManager (协调器)
├── 使用依赖注入
├── 通过工厂创建处理器
└── 协调各个组件工作

🔧 使用示例

1. 基本使用 (使用默认实现)

# 自动选择最佳实现
media_manager = MediaManager()
video_info = media_manager._get_video_info("video.mp4")
scene_changes = media_manager._detect_scene_changes("video.mp4")

2. 依赖注入使用

# 手动指定实现
dependency_manager = DependencyManager()
video_extractor = FFProbeVideoInfoExtractor()
scene_detector = PySceneDetectSceneDetector(dependency_manager)

media_manager = MediaManager(
    dependency_manager=dependency_manager,
    video_info_extractor=video_extractor,
    scene_detector=scene_detector
)

3. 测试友好

# 轻松进行单元测试
class MockVideoInfoExtractor(VideoInfoExtractor):
    def extract_video_info(self, file_path: str) -> Dict:
        return {"duration": 10.0, "width": 1920, "height": 1080}

# 注入Mock对象进行测试
mock_extractor = MockVideoInfoExtractor()
media_manager = MediaManager(video_info_extractor=mock_extractor)

📈 重构收益

1. 可维护性提升

  • 单一职责: 每个类只负责一个功能,易于理解和修改
  • 代码解耦: 组件之间松耦合,修改一个不影响其他
  • 清晰架构: 层次分明,职责明确

2. 可扩展性提升

  • 新实现: 轻松添加新的视频处理方法
  • 插件化: 支持插件式扩展
  • 配置化: 可以通过配置选择不同实现

3. 可测试性提升

  • 依赖注入: 轻松注入Mock对象
  • 接口隔离: 可以独立测试每个组件
  • 单元测试: 每个类都可以独立测试

4. 代码质量提升

  • 类型安全: 使用抽象基类和类型注解
  • 错误处理: 统一的错误处理机制
  • 日志记录: 详细的日志记录

🚀 迁移指南

1. 向后兼容

# 保持向后兼容的全局变量
VIDEO_LIBS_AVAILABLE = dependency_manager.is_available('opencv')
SCENEDETECT_AVAILABLE = dependency_manager.is_available('scenedetect')

# 现有代码无需修改
media_manager = MediaManager()  # 仍然可以正常工作

2. 渐进式迁移

  1. 第一阶段: 使用新的MediaManager保持现有接口
  2. 第二阶段: 逐步使用依赖注入
  3. 第三阶段: 完全迁移到新架构

3. 性能优化

  • 懒加载: 处理器只在需要时创建
  • 缓存: 依赖管理器缓存检查结果
  • 工厂模式: 统一的对象创建逻辑

🎯 最佳实践

1. 依赖管理

# 统一的依赖检查
if dependency_manager.is_available('scenedetect'):
    # 使用PySceneDetect
else:
    # 回退到OpenCV

2. 错误处理

# 统一的错误处理模式
try:
    return self.video_info_extractor.extract_video_info(file_path)
except Exception as e:
    logger.error(f"Failed to get video info: {e}")
    return default_video_info()

3. 扩展新功能

# 添加新的视频处理器
class NewVideoProcessor(VideoProcessor):
    def split_video(self, input_path: str, output_dir: str, scene_times: List[float]) -> List[str]:
        # 新的实现
        pass

# 在工厂中注册
class VideoProcessorFactory:
    def create_video_processor(self) -> VideoProcessor:
        if self.dependency_manager.is_available('new_library'):
            return NewVideoProcessor()
        else:
            return DefaultVideoProcessor()

通过SOLID原则重构代码变得更加模块化、可测试和可维护