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
19 KiB
AuroraDesk 项目重构计划
📋 项目概述
目标项目名: AuroraDesk
原项目名: MyAvaloniaApp
目标架构: 整洁架构 (Clean Architecture)
重点优化: 减少 ViewModels 耦合,提高代码可维护性
环境: Windows 10
注意事项: 避免使用 Linux 语法,防止乱码问题
🎯 重构目标
1. 项目重命名
- 项目名称:
MyAvaloniaApp→AuroraDesk - 解决方案文件:
MyAvaloniaApp.sln→AuroraDesk.sln - 项目文件:
MyAvaloniaApp.csproj→AuroraDesk.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 层) │
└─────────────────────────────────────────────┘
✅ 正确的依赖关系
-
Core(核心领域层 / Domain)
- ✅ 无依赖:不依赖任何其他层
- ✅ 被依赖:所有层都可以依赖 Core
- ✅ 包含:
- 领域实体(Entities)
- 领域接口(如
INavigationService,IDataService等) - 领域异常(Exceptions)
-
Application(应用层)
- ✅ 依赖:只依赖 Core 层
- ✅ 不依赖:不依赖 Infrastructure 层
- ✅ 被依赖:Presentation 层可以依赖 Application
- ✅ 包含:
- 用例逻辑(Use Cases)
- DTOs(数据传输对象)
- 应用服务逻辑(使用 Core 接口)
-
Infrastructure(基础设施层)
- ✅ 依赖:只依赖 Core 层
- ✅ 不依赖:不依赖 Application 层
- ✅ 实现:实现 Core 层定义的接口
- ✅ 包含:
- 服务实现(如
NavigationService,DataService) - 数据访问实现
- 外部 API 调用
- 服务实现(如
-
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方法中 - 难以进行单元测试
- 违反单一职责原则
解决方案:
-
引入 PageViewModel 工厂模式
- 创建
IPageViewModelFactory接口 - 实现
PageViewModelFactory类 - 通过依赖注入管理 ViewModel 创建
- 创建
-
引入导航服务
- 创建
INavigationService接口 - 封装导航逻辑,减少 MainWindowViewModel 职责
- 统一管理导航项配置
- 创建
-
将配置数据外置
- 创建导航配置数据类(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,TabItem到Core.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实现INavigationServicePageViewModelFactory实现IPageViewModelFactoryDataService实现IDataServiceApiService实现IApiServiceResourceService实现IResourceService
- 更新服务命名空间为
AuroraDesk.Infrastructure.Services
步骤 2.4: 重构表示层
- 更新
MainWindowViewModel,移除直接创建 ViewModel 的代码 - 注入
INavigationService和IPageViewModelFactory - 简化
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
- 注册
IPageViewModelFactory→PageViewModelFactory - 注册
INavigationService→NavigationService - 注册所有 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(最底层,无依赖)
- Application 和 Infrastructure(都依赖 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月
状态: 计划阶段