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.
199 lines
6.5 KiB
199 lines
6.5 KiB
|
1 month ago
|
# 为什么 PageViewModelFactory 必须在 Presentation 层?
|
||
|
|
|
||
|
|
## 🎯 核心原因
|
||
|
|
|
||
|
|
**PageViewModelFactory 直接依赖 Presentation 层的 ViewModel 类型!**
|
||
|
|
|
||
|
|
## 📊 依赖关系分析
|
||
|
|
|
||
|
|
### 依赖链 1:PageViewModelFactory → ViewModels
|
||
|
|
|
||
|
|
```
|
||
|
|
AuroraDesk.Core.Interfaces.IPageViewModelFactory (接口定义)
|
||
|
|
↑
|
||
|
|
| 实现
|
||
|
|
|
|
||
|
|
AuroraDesk.Presentation.Services.PageViewModelFactory
|
||
|
|
↓ 依赖
|
||
|
|
[必须知道具体的 ViewModel 类型]
|
||
|
|
- DashboardPageViewModel
|
||
|
|
- UsersPageViewModel
|
||
|
|
- SettingsPageViewModel
|
||
|
|
- ReportsPageViewModel
|
||
|
|
- ... (所有 PageViewModel)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 关键代码
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// AuroraDesk.Presentation/Services/PageViewModelFactory.cs
|
||
|
|
|
||
|
|
// ❌ 这里必须引用具体的 ViewModel 类型!
|
||
|
|
using AuroraDesk.Presentation.ViewModels.Pages; // ← 关键!
|
||
|
|
using AuroraDesk.Core.Interfaces; // 接口定义
|
||
|
|
|
||
|
|
public class PageViewModelFactory : IPageViewModelFactory
|
||
|
|
{
|
||
|
|
public IRoutableViewModel CreatePageViewModel(string pageId, IScreen screen)
|
||
|
|
{
|
||
|
|
return pageId switch
|
||
|
|
{
|
||
|
|
"dashboard" => CreatePageViewModel<DashboardPageViewModel>(screen), // ← 需要具体的 ViewModel 类型
|
||
|
|
"users" => CreatePageViewModel<UsersPageViewModel>(screen),
|
||
|
|
"settings" => CreatePageViewModel<SettingsPageViewModel>(screen),
|
||
|
|
// ...
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🚫 为什么不放在其他层?
|
||
|
|
|
||
|
|
### 方案1:放在 Infrastructure 层? ❌
|
||
|
|
|
||
|
|
```
|
||
|
|
AuroraDesk.Infrastructure.Services.PageViewModelFactory
|
||
|
|
↓ 依赖
|
||
|
|
AuroraDesk.Presentation.ViewModels.Pages.* // ❌ 违反依赖倒置!
|
||
|
|
```
|
||
|
|
|
||
|
|
**问题**:
|
||
|
|
- Infrastructure 不能依赖 Presentation
|
||
|
|
- 违反了整洁架构的依赖规则
|
||
|
|
- 会导致循环依赖
|
||
|
|
|
||
|
|
### 方案2:放在主项目(AuroraDesk)? ⚠️
|
||
|
|
|
||
|
|
```
|
||
|
|
AuroraDesk/Services/PageViewModelFactory.cs
|
||
|
|
↓ 依赖
|
||
|
|
AuroraDesk.Presentation.ViewModels.Pages.* // ✅ 可以
|
||
|
|
```
|
||
|
|
|
||
|
|
**优点**:
|
||
|
|
- 主项目可以访问所有层
|
||
|
|
- 符合依赖规则
|
||
|
|
|
||
|
|
**缺点**:
|
||
|
|
- 主项目会变得臃肿
|
||
|
|
- 降低了分层清晰度
|
||
|
|
- 当前架构中,主项目主要用于启动配置
|
||
|
|
|
||
|
|
### 方案3:放在 Presentation 层? ✅
|
||
|
|
|
||
|
|
```
|
||
|
|
AuroraDesk.Presentation.Services.PageViewModelFactory
|
||
|
|
↓ 依赖
|
||
|
|
AuroraDesk.Presentation.ViewModels.Pages.* // ✅ 同层,完全合理!
|
||
|
|
```
|
||
|
|
|
||
|
|
**优点**:
|
||
|
|
- ✅ 在同一层,无需跨层依赖
|
||
|
|
- ✅ 职责清晰:Presentation 层管理所有 UI 相关组件
|
||
|
|
- ✅ 符合依赖倒置:实现 Core 的接口,使用同一层的类型
|
||
|
|
|
||
|
|
## 📚 依赖倒置原则(DIP)解析
|
||
|
|
|
||
|
|
### 什么是依赖倒置?
|
||
|
|
|
||
|
|
**高层模块不应该依赖低层模块,两者都应该依赖抽象。**
|
||
|
|
|
||
|
|
### 在我们的架构中
|
||
|
|
|
||
|
|
```
|
||
|
|
高层(Presentation) 低层(Infrastructure)
|
||
|
|
↓ ↓
|
||
|
|
使用接口(IPageViewModelFactory) ← 实现接口(但在不同层)
|
||
|
|
↓ ↓
|
||
|
|
使用具体类型(DashboardPageViewModel) ← 这是 Presentation 层内部的事
|
||
|
|
```
|
||
|
|
|
||
|
|
**关键理解**:
|
||
|
|
1. ✅ **接口在 Core 层定义**(IPageViewModelFactory)
|
||
|
|
2. ✅ **实现可以在 Presentation 层**(PageViewModelFactory)
|
||
|
|
3. ✅ **使用同层的具体类型**(DashboardPageViewModel)
|
||
|
|
|
||
|
|
这并不违反依赖倒置,因为:
|
||
|
|
- 依赖方向是:Presentation → Core(接口)
|
||
|
|
- Presentation 使用自己的类型,这是正常的
|
||
|
|
|
||
|
|
## 🔍 对比其他服务和工厂
|
||
|
|
|
||
|
|
### NavigationService(Infrastructure 层)
|
||
|
|
|
||
|
|
```
|
||
|
|
AuroraDesk.Infrastructure.Services.NavigationService
|
||
|
|
↓ 依赖
|
||
|
|
AuroraDesk.Core.Interfaces.IPageViewModelFactory // ✅ 只依赖接口
|
||
|
|
AuroraDesk.Core.Entities.NavigationItem // ✅ 依赖 Core 层
|
||
|
|
```
|
||
|
|
|
||
|
|
**为什么可以?** 因为它只使用接口,不依赖具体的 ViewModel 类型!
|
||
|
|
|
||
|
|
### 如果 PageViewModelFactory 没有页面的具体类型?
|
||
|
|
|
||
|
|
假设我们有一个"抽象"的工厂:
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// 假设:不使用具体的 ViewModel 类型
|
||
|
|
public interface IPageViewModelFactory
|
||
|
|
{
|
||
|
|
// 只返回接口,不涉及具体类型
|
||
|
|
IRoutableViewModel CreatePageViewModel(string pageId, IScreen screen);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**问题**:那工厂内部如何创建具体的 ViewModel?
|
||
|
|
- ❌ 如果不用具体类型,就必须用反射
|
||
|
|
- ❌ 失去了编译时类型检查
|
||
|
|
- ❌ 需要维护复杂的映射配置
|
||
|
|
|
||
|
|
这就是为什么工厂通常依赖具体类型!
|
||
|
|
|
||
|
|
## ✅ 结论
|
||
|
|
|
||
|
|
**PageViewModelFactory 在 Presentation 层是正确的!**
|
||
|
|
|
||
|
|
### 依赖关系图
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ Core (核心层) │
|
||
|
|
│ - Interfaces/IPageViewModelFactory │ ← 接口定义
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
↑ 依赖 ↑ 依赖
|
||
|
|
| |
|
||
|
|
┌─────────────────────────┐ ┌──────────────────────────┐
|
||
|
|
│ Presentation │ │ Infrastructure │
|
||
|
|
│ - Services/ │ │ - Services/ │
|
||
|
|
│ PageViewModelFactory │ │ NavigationService │
|
||
|
|
│ ↓ 实现接口 │ │ ↓ 使用接口 │
|
||
|
|
│ - ViewModels/Pages/ │ │ │
|
||
|
|
│ DashboardPage... │ │ │
|
||
|
|
│ UsersPage... │ │ │
|
||
|
|
└─────────────────────────┘ └──────────────────────────┘
|
||
|
|
↑
|
||
|
|
| PageViewModelFactory 使用
|
||
|
|
|
|
||
|
|
DashboardPageViewModel, UsersPageViewModel, etc.
|
||
|
|
```
|
||
|
|
|
||
|
|
**为什么合理?**
|
||
|
|
1. ✅ `PageViewModelFactory` 实现了 Core 的接口
|
||
|
|
2. ✅ `PageViewModelFactory` 使用 Presentation 层的类型
|
||
|
|
3. ✅ 两者在同一层,没有跨层依赖问题
|
||
|
|
4. ✅ Infrastructure 层通过接口使用,不依赖具体实现
|
||
|
|
|
||
|
|
## 📋 总结
|
||
|
|
|
||
|
|
| 位置 | 是否合理 | 原因 |
|
||
|
|
|------|---------|------|
|
||
|
|
| **Infrastructure** | ❌ | 违反依赖规则(Infrastructure → Presentation) |
|
||
|
|
| **Core** | ❌ | 违反依赖规则(Core 不能依赖其他层) |
|
||
|
|
| **Application** | ❌ | Application 不能依赖 UI 层类型 |
|
||
|
|
| **主项目** | ⚠️ | 可行,但会让主项目臃肿 |
|
||
|
|
| **Presentation** | ✅ | 合理!与 ViewModels 同层,实现 Core 接口 |
|
||
|
|
|
||
|
|
**所以,您说得完全正确!** 🎉
|
||
|
|
|