You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

19 KiB

AuroraDesk 项目重构计划

📋 项目概述

目标项目名: AuroraDesk
原项目名: MyAvaloniaApp
目标架构: 整洁架构 (Clean Architecture)
重点优化: 减少 ViewModels 耦合,提高代码可维护性
环境: Windows 10
注意事项: 避免使用 Linux 语法,防止乱码问题


🎯 重构目标

1. 项目重命名

  • 项目名称:MyAvaloniaAppAuroraDesk
  • 解决方案文件:MyAvaloniaApp.slnAuroraDesk.sln
  • 项目文件:MyAvaloniaApp.csprojAuroraDesk.csproj
  • 命名空间前缀:MyAvaloniaApp.*AuroraDesk.*

2. 架构重构(整洁架构)

2.1 整洁架构依赖关系规则

整洁架构的核心原则是 依赖关系必须指向内层,即依赖方向永远向内(朝向核心层/Domain层)。

关键要点

  • Core(Domain)层定义所有接口,不是 Application 层
  • Application 和 Infrastructure 都依赖 Core,但两者之间不互相依赖
  • Infrastructure 实现 Core 层定义的接口
📌 基本依赖规则
┌─────────────────────────────────────────────┐
│   Presentation Layer (表示层)              │
│   - ViewModels, Views, Converters           │
│   ↓ 依赖 ↓                                  │
├─────────────────────────────────────────────┤
│   Application Layer (应用层)                │
│   - Use Cases, DTOs, Application Logic      │
│   ↓ 依赖 ↓                                  │
├─────────────────────────────────────────────┤
│   Core/Domain Layer (核心领域层)            │
│   - Entities, Domain Interfaces            │
│   - 定义所有服务接口                       │
│   (不依赖任何外部层)                       │
└─────────────────────────────────────────────┘
            ↑ 依赖 ↑        ↑ 实现 ↑
┌─────────────────────────────────────────────┐
│   Infrastructure Layer (基础设施层)          │
│   - Service Implementations, Data Access    │
│   - 实现 Core 层定义的接口                  │
│   (不依赖 Application 层)                  │
└─────────────────────────────────────────────┘
正确的依赖关系
  1. Core(核心领域层 / Domain)

    • 无依赖:不依赖任何其他层
    • 被依赖:所有层都可以依赖 Core
    • 包含
      • 领域实体(Entities)
      • 领域接口(如 INavigationService, IDataService 等)
      • 领域异常(Exceptions)
  2. Application(应用层)

    • 依赖:只依赖 Core 层
    • 不依赖:不依赖 Infrastructure 层
    • 被依赖:Presentation 层可以依赖 Application
    • 包含
      • 用例逻辑(Use Cases)
      • DTOs(数据传输对象)
      • 应用服务逻辑(使用 Core 接口)
  3. Infrastructure(基础设施层)

    • 依赖:只依赖 Core 层
    • 不依赖:不依赖 Application 层
    • 实现:实现 Core 层定义的接口
    • 包含
      • 服务实现(如 NavigationService, DataService
      • 数据访问实现
      • 外部 API 调用
  4. Presentation(表示层)

    • 依赖:依赖 Core 和 Application 层
    • 不依赖:不直接依赖 Infrastructure 层(通过接口)
    • 包含:ViewModels、Views、UI 相关组件
错误的依赖关系(禁止)
❌ Core → Application          // 核心层不能依赖应用层
❌ Core → Infrastructure        // 核心层不能依赖基础设施层
❌ Core → Presentation          // 核心层不能依赖表示层

❌ Application → Infrastructure // 应用层不能依赖基础设施层
❌ Infrastructure → Application  // 基础设施层不能依赖应用层(关键!)
❌ Application → Presentation   // 应用层不能依赖表示层

❌ Presentation → Infrastructure // 表示层不能直接依赖基础设施层(通过接口)
🔄 依赖倒置原则(DIP)

关键要点

  • Core 层定义接口(如 INavigationService),不是 Application 层
  • Infrastructure 层实现 Core 层定义的接口(如 NavigationService
  • Application 层使用 Core 层的接口,但不依赖 Infrastructure
  • Presentation 层通过接口依赖服务,不依赖具体实现

正确的示例

// ✅ 正确:Core 层定义接口
namespace AuroraDesk.Core.Interfaces
{
    public interface INavigationService 
    {
        void Navigate(string pageId);
        ObservableCollection<NavigationItem> GetNavigationItems();
    }
    
    public interface IDataService { }
    public interface IApiService { }
}

// ✅ 正确:Infrastructure 层实现 Core 接口
namespace AuroraDesk.Infrastructure.Services
{
    public class NavigationService : INavigationService 
    {
        // 实现接口方法
    }
}

// ✅ 正确:Application 层使用 Core 接口(不定义接口)
namespace AuroraDesk.Application.Services
{
    public class NavigationUseCase
    {
        private readonly INavigationService _navigationService; // 使用 Core 接口
        
        public NavigationUseCase(INavigationService navigationService)
        {
            _navigationService = navigationService;
        }
    }
}

// ✅ 正确:Presentation 层依赖 Core 接口和 Application 用例
namespace AuroraDesk.Presentation.ViewModels
{
    public class MainWindowViewModel
    {
        private readonly INavigationService _navigationService; // 依赖 Core 接口
        private readonly NavigationUseCase _useCase; // 依赖 Application 用例
    }
}

错误的示例

// ❌ 错误:Application 层不应该定义接口
namespace AuroraDesk.Application.Services
{
    public interface INavigationService { } // 应该在 Core 层!
}

// ❌ 错误:Infrastructure 不应该依赖 Application
namespace AuroraDesk.Infrastructure.Services
{
    using AuroraDesk.Application; // 错误!不能依赖 Application
    public class NavigationService : INavigationService { }
}
📊 本项目依赖关系图(修正后)
AuroraDesk.Presentation (表示层)
    ├─→ 依赖 AuroraDesk.Application (应用用例)
    └─→ 依赖 AuroraDesk.Core (核心实体和接口)

AuroraDesk.Infrastructure (基础设施层)
    ├─→ 依赖 AuroraDesk.Core (核心实体和接口)
    └─→ 实现 AuroraDesk.Core.Interfaces 的接口
    ❌ 不依赖 AuroraDesk.Application

AuroraDesk.Application (应用层)
    └─→ 依赖 AuroraDesk.Core (核心实体和接口)
    ❌ 不依赖 AuroraDesk.Infrastructure

AuroraDesk.Core (核心层/Domain)
    └─→ 无依赖(独立层)
    └─→ 定义所有接口
🎯 依赖检查清单(修正后)

在重构过程中,确保:

  • Core 层没有任何 using 语句引用其他层
  • Core 层定义所有服务接口(INavigationService, IDataService 等)
  • Application 层只引用 Core 层
  • Application 层不定义接口,只使用 Core 接口
  • Infrastructure 层只引用 Core 层
  • Infrastructure 层不依赖 Application 层
  • Infrastructure 层实现 Core 层定义的接口
  • Presentation 层通过接口依赖服务,不直接依赖 Infrastructure
  • 所有跨层访问都通过接口进行
  • 没有循环依赖

3. 解耦 ViewModels 策略

3.1 问题分析

当前问题:

  • MainWindowViewModel 中直接创建了所有 PageViewModel 实例(高耦合)
  • ViewModel 创建逻辑分散在 InitializeNavigationItems 方法中
  • 难以进行单元测试
  • 违反单一职责原则

解决方案:

  1. 引入 PageViewModel 工厂模式

    • 创建 IPageViewModelFactory 接口
    • 实现 PageViewModelFactory
    • 通过依赖注入管理 ViewModel 创建
  2. 引入导航服务

    • 创建 INavigationService 接口
    • 封装导航逻辑,减少 MainWindowViewModel 职责
    • 统一管理导航项配置
  3. 将配置数据外置

    • 创建导航配置数据类(NavigationConfig)
    • 从 ViewModel 中分离配置数据
    • 配置可在应用启动时注入

3.2 重构后的依赖关系

MainWindowViewModel
    ↓ (依赖)
INavigationService          // 处理导航逻辑
IPageViewModelFactory       // 创建 PageViewModel
ILogger                     // 日志记录
IScreen                     // ReactiveUI 路由

不再直接依赖:
❌ DashboardPageViewModel
❌ UsersPageViewModel
❌ SettingsPageViewModel
... (所有具体的 PageViewModel)

📝 详细重构步骤

阶段一:准备工作(不修改代码)

步骤 1.1: 创建新项目结构(可选,建议先单项目重构)

  • 创建分层项目结构(或多文件夹结构)
  • 规划文件迁移路径
  • 备份当前代码

步骤 1.2: 分析依赖关系

  • 梳理所有 ViewModel 之间的依赖
  • 识别所有直接 new 实例化的地方
  • 记录需要注入的服务

阶段二:架构重构(按顺序执行)

步骤 2.1: 创建核心领域层

  • 创建 AuroraDesk.Core 项目/文件夹
  • 迁移实体类:NavigationItem, TabItemCore.Entities
  • 更新命名空间:AuroraDesk.Core.Entities
  • 移除业务逻辑,只保留数据模型

步骤 2.2: 创建核心层接口和应用层

  • AuroraDesk.Core.Interfaces 中定义所有服务接口
    • INavigationService 接口(不是 Application 层!)
    • IDataService 接口
    • IApiService 接口
    • IResourceService 接口
    • IPageViewModelFactory 接口
  • 创建 AuroraDesk.Application 项目/文件夹
  • 定义用例类(使用 Core 接口)
  • 定义导航配置 DTO(NavigationConfig)

步骤 2.3: 创建基础设施层实现

  • 创建 AuroraDesk.Infrastructure 项目/文件夹
  • 重要:Infrastructure 只依赖 Core 层,不依赖 Application 层
  • 实现 Core 层定义的接口:
    • NavigationService 实现 INavigationService
    • PageViewModelFactory 实现 IPageViewModelFactory
    • DataService 实现 IDataService
    • ApiService 实现 IApiService
    • ResourceService 实现 IResourceService
  • 更新服务命名空间为 AuroraDesk.Infrastructure.Services

步骤 2.4: 重构表示层

  • 更新 MainWindowViewModel,移除直接创建 ViewModel 的代码
  • 注入 INavigationServiceIPageViewModelFactory
  • 简化 MainWindowViewModel 的职责
  • 更新所有命名空间为 AuroraDesk.Presentation.*

阶段三:项目重命名(谨慎执行)

步骤 3.1: 重命名项目文件

  • 重命名 .csproj 文件
  • 重命名 .sln 文件
  • 更新项目文件中的 <RootNamespace><AssemblyName>

步骤 3.2: 重命名命名空间(逐个文件修改)

  • 不要使用批量替换
  • 逐个文件修改命名空间声明
  • 逐个文件更新 using 语句
  • 每次修改后编译测试

步骤 3.3: 更新配置文件

  • 更新 appsettings.json 中的项目相关配置
  • 更新 app.manifest 中的程序集名称
  • 更新所有批处理文件和脚本

阶段四:解耦 ViewModels(重点)

步骤 4.1: 在 Core 层定义接口

// AuroraDesk.Core.Interfaces/IPageViewModelFactory.cs
public interface IPageViewModelFactory
{
    T CreatePageViewModel<T>(IScreen screen) where T : IRoutableViewModel;
    IRoutableViewModel CreatePageViewModel(string pageId, IScreen screen);
}

// AuroraDesk.Core.Interfaces/INavigationService.cs
public interface INavigationService
{
    ObservableCollection<NavigationItem> GetNavigationItems();
    void NavigateToPage(NavigationItem item);
}

步骤 4.2: 在 Infrastructure 层实现接口

// AuroraDesk.Infrastructure/Services/PageViewModelFactory.cs
public class PageViewModelFactory : IPageViewModelFactory
{
    private readonly IServiceProvider _serviceProvider;
    
    public IRoutableViewModel CreatePageViewModel(string pageId, IScreen screen)
    {
        return pageId switch
        {
            "dashboard" => _serviceProvider.GetRequiredService<DashboardPageViewModel>(),
            "users" => _serviceProvider.GetRequiredService<UsersPageViewModel>(),
            // ... 其他页面
            _ => throw new ArgumentException($"Unknown page: {pageId}")
        };
    }
}

// AuroraDesk.Infrastructure/Services/NavigationService.cs
public class NavigationService : INavigationService
{
    // 实现接口方法
}

步骤 4.3: 在 Application 层创建用例(可选)

// AuroraDesk.Application/UseCases/NavigationUseCase.cs
public class NavigationUseCase
{
    private readonly INavigationService _navigationService; // 使用 Core 接口
    
    public NavigationUseCase(INavigationService navigationService)
    {
        _navigationService = navigationService;
    }
    
    // 用例逻辑
}

步骤 4.4: 重构 MainWindowViewModel

  • 移除 InitializeNavigationItems 方法中的 ViewModel 创建逻辑
  • 注入 INavigationService(来自 Core.Interfaces)获取导航项
  • 使用 IPageViewModelFactory(来自 Core.Interfaces)创建 ViewModel
  • 简化导航逻辑,委托给 INavigationService
  • 重要:ViewModel 依赖 Core 接口,不直接依赖 Infrastructure

阶段五:注册依赖注入

步骤 5.1: 更新 ServiceCollectionExtensions

  • 注册 IPageViewModelFactoryPageViewModelFactory
  • 注册 INavigationServiceNavigationService
  • 注册所有 PageViewModel 为 Transient(或 Scoped)
  • 更新命名空间引用

步骤 5.2: 更新 App.axaml.cs

  • 确保所有新服务正确注册
  • 更新 ViewLocator 的命名空间
  • 测试依赖注入是否正常工作

阶段六:测试和验证

步骤 6.1: 编译测试

  • 清理解决方案并重新编译
  • 修复所有编译错误
  • 修复所有命名空间引用错误

步骤 6.2: 运行时测试

  • 启动应用程序
  • 测试导航功能
  • 测试所有页面能否正常加载
  • 测试标签页功能

步骤 6.3: 代码检查

  • 检查是否还有直接 new ViewModel 的地方
  • 检查命名空间是否全部更新
  • 检查是否有循环依赖

🔧 技术实现细节

1. PageViewModel 注册方式

// ServiceCollectionExtensions.cs
services.AddTransient<DashboardPageViewModel>();
services.AddTransient<UsersPageViewModel>();
services.AddTransient<SettingsPageViewModel>();
// ... 其他 PageViewModel

2. 导航配置数据

// NavigationConfig.cs
public class NavigationConfig
{
    public List<NavigationItemConfig> Items { get; set; } = new();
}

public class NavigationItemConfig
{
    public string Id { get; set; } = string.Empty;
    public string Title { get; set; } = string.Empty;
    public string PageViewModelType { get; set; } = string.Empty;
    public IconType IconType { get; set; }
    public List<NavigationItemConfig>? Children { get; set; }
}

3. MainWindowViewModel 重构示例

重构前:

private void InitializeNavigationItems()
{
    _navigationItems = new ObservableCollection<NavigationItem>
    {
        new NavigationItem { ViewModel = new DashboardPageViewModel(_screen) },
        new NavigationItem { ViewModel = new UsersPageViewModel(_screen) },
        // ...
    };
}

重构后:

using AuroraDesk.Core.Interfaces; // 依赖 Core 接口,不依赖 Infrastructure

private readonly INavigationService _navigationService;

public MainWindowViewModel(
    IScreen screen, 
    INavigationService navigationService, // Core 接口
    ILogger<MainWindowViewModel>? logger = null)
{
    _screen = screen;
    _navigationService = navigationService;
    _logger = logger;
    
    NavigationItems = _navigationService.GetNavigationItems();
}

⚠️ 注意事项

1. 不要批量替换

  • 禁止:使用 Visual Studio 的全局查找替换功能批量替换命名空间
  • 正确做法:逐个文件修改,每次修改后编译测试

2. Windows 10 环境

  • 避免:使用 Linux 风格的路径分隔符或命令
  • 使用:Windows 路径分隔符 \ 或使用 Path.Combine
  • 使用:PowerShell 脚本而非 Bash 脚本

3. 编译顺序

  • 先编译被依赖的层:
    • Core(最底层,无依赖)
    • ApplicationInfrastructure(都依赖 Core,可并行编译)
    • Presentation(依赖 Core 和 Application)
  • 每次修改一层后立即编译测试

4. Git 版本控制

  • 每个阶段完成后提交一次代码
  • 使用有意义的提交信息
  • 如果出现问题可以回退到上一个阶段

📊 预期成果

代码质量提升

  • ViewModels 耦合度降低 80%+
  • 代码可测试性提升
  • 符合整洁架构原则
  • 易于扩展新功能

项目结构

  • 清晰的分层架构
  • 统一的命名空间规范(Aurora 前缀)
  • 更好的代码组织

维护性

  • 新页面添加只需注册,无需修改 MainWindowViewModel
  • 导航逻辑集中管理
  • 配置与代码分离

📅 执行时间估算

  • 阶段一(准备):0.5 天
  • 阶段二(架构重构):2-3 天
  • 阶段三(重命名):1-2 天
  • 阶段四(解耦):2-3 天
  • 阶段五(DI注册):0.5 天
  • 阶段六(测试):1 天

总计:约 7-10 个工作日


检查清单

在每个阶段完成后,使用此清单验证:

  • 所有文件命名空间已更新
  • 所有 using 语句已更新
  • 项目文件已更新
  • 解决方案文件已更新
  • 代码编译无错误
  • 应用程序可以正常启动
  • 主要功能正常工作
  • 已更新 modify.md 记录修改

📝 修改记录

每次完成一个重要步骤后,请在 modify.md 文件中记录:

  • 修改日期
  • 修改内容
  • 修改的文件列表
  • 遇到的问题及解决方案

文档创建日期: 2025年1月
最后更新: 2025年1月
状态: 计划阶段