跳转到主内容

SeatFlow.Application 层的外观模式、策略管道、命令模式、插件管理和 DI 注册

Application 层详解

SeatFlow.Application 是 SeatFlow 的应用编排层,作为 UI 层和领域层之间的桥梁。它负责编排数据加载、策略执行、插件管理和命令历史。所有业务逻辑的协调入口均在这一层。

IApplicationFacade (外观接口)

IApplicationFacade 是 UI 层的单一入口点(外观模式),封装 40+ 方法,涵盖:

public interface IApplicationFacade
{
    // 数据管理
    Task<IReadOnlyList<StudentDatasetInfo>> ListStudentDatasetsAsync(CancellationToken ct = default);
    Task ImportStudentsAsync(string filePath, CancellationToken ct = default);

    // 排座执行
    Task<SeatingWorkspace> GenerateSeatingAsync(SeatingRequest request,
        IProgress<SeatingProgress>? progress = null, CancellationToken ct = default);

    // 导出
    Task ExportSeatingPlanAsync(ExportOptions options, SeatingWorkspace workspace,
        ClassroomLayoutDefinition layout, CancellationToken ct = default);

    // 快照
    Task<IReadOnlyList<SeatingSnapshot>> GetSnapshotsAsync(string venueId, CancellationToken ct = default);
    Task RollbackToSnapshotAsync(string snapshotId, CancellationToken ct = default);

    // 命令历史(撤销/重做)
    Task<bool> ExecuteCommandAsync(IUndoableCommand command, CancellationToken ct = default);
    Task<bool> UndoAsync(CancellationToken ct = default);
    Task<bool> RedoAsync(CancellationToken ct = default);

    // 策略配置
    Task<IReadOnlyList<StrategyDisplayInfo>> GetStrategiesAsync(CancellationToken ct = default);
    Task SaveStrategyConfigAsync(string id, StrategyConfig config, CancellationToken ct = default);

    // ... 更多方法
}

ViewModel 通过构造函数注入 IApplicationFacade,不直接依赖任何领域或基础设施类型。

StrategyExecutionPipeline (策略管道)

策略管道采用 Fill-in-Order 模型执行独立策略:

public class StrategyExecutionPipeline
{
    public async Task<SeatingPlan> ExecuteAsync(
        SeatingWorkspace workspace, CancellationToken cancellationToken)
    {
        // 按 Priority 降序执行每个启用的独立策略
        foreach (var strategy in _strategies
            .OrderByDescending(s => s.Priority)
            .Where(s => s.IsEnabled))
        {
            var result = await strategy.ExecuteAsync(workspace, cancellationToken);
            if (!result.Success)
            {
                workspace.LogError(strategy.Id, strategy.Name,
                    "Pipeline_ExecFailed", result.Message);
            }
        }
        return workspace.BuildSeatingPlan();
    }
}

执行流程(ApplicationFacade 在构建策略列表时已排除 visible=false 的策略,管道中仅处理已纳入的策略):

  1. 按 Priority 降序排列 IsEnabled 为 true 的独立策略
  2. 依次执行每个独立策略,所有策略操作同一个 SeatingWorkspace
  3. 依赖策略不在外部管道中执行——它们在 RandomFillStrategy 的分配循环中按上下文内部优先级评估

RandomFill 上下文循环

RandomFillStrategy 作为依赖策略的宿主,其分配循环:

while 还有未分配学生 AND 空座位:
    随机选 (student, seat)
    rerollCount = 0
    loop:
        依次调用依赖策略 EvaluateAsync (按内部 Priority 降序)
        if Reject → rerollCount++
            if >= maxRerolls 兜底强制分配
            else 换随机座位重试
        if Handled → 依赖策略已自行分配,跳过 TryAssignSeat
        if 全部 Approve → TryAssignSeat, 刷新列表

约束学生(DeskMate 分组)优先分配以减少重掷次数。逐出操作尊重先前策略(FixedSeat、FrontRowRotation)的分配结果。

命令模式

IUndoableCommandCommandHistory 提供快照式撤销/重做:

public interface IUndoableCommand
{
    string Description { get; }
    Task<bool> ExecuteAsync(CancellationToken ct = default);
    Task<bool> UndoAsync(CancellationToken ct = default);
}

CommandHistory 管理操作栈,支持撤销和重做导航。采用快照式实现——每个命令保存操作前后的状态快照,而非增量操作记录。

插件管理

PluginManager

位于 SeatFlow.Application/PluginManagement/

组件 职责
PluginManager 插件发现(扫描插件目录)、加载、卸载、运行时启用/禁用
PluginLoadContext 基于 AssemblyLoadContext 的隔离加载上下文,外部 DLL 独立于主程序域
PluginStrategyAdapter IPluginSeatingStrategy 适配为 ISeatingStrategy

关键特性:

  • RefreshPackageAsync(packageId) — 热插拔:卸载 → 重新扫描 → 加载
  • SetStrategyEnabledAsync(strategyId, enabled) — 运行时即时生效
  • FindStrategy(strategyId) — 判断策略来源,自动路由配置存储后端

插件隔离

通过自定义 AssemblyLoadContext 加载外部插件程序集,确保:

  • 插件 DLL 版本冲突不影响主程序
  • 插件可安全卸载
  • 受限的 API 访问权限

插件配置路由

配置类型 内置策略 插件策略
运行时配置 {AppData}/StrategyConfig/{id}.config.json Plugins/{pkgId}/{path}/{id}.config.json
数据集配置 {AppData}/StrategyConfig/{id}/... Plugins/{pkgId}/{path}/{id}/...
启用状态 StrategyConfig.IsEnabled Plugins/{pkgId}/data/enables.json

脚本适配器

位于 SeatFlow.Application/Scripting/

适配器 支持语言 说明
LuaScriptPluginAdapter Lua 通过受限沙箱执行 Lua 脚本,禁用 IO/网络
CSharpScriptPluginAdapter C# Script 编译和执行 C# 脚本,同样受限

脚本适配器将脚本逻辑包装为 IPluginSeatingStrategy,使其能以与其他插件相同的方式在管道中执行。

DI 注册

Application 层注册

ServiceCollectionExtensions.AddSeatFlowApplication(snapshotBasePath, pluginsPath) 注册所有 Application、Core 和 Infrastructure 层的服务:

public static IServiceCollection AddSeatFlowApplication(
    this IServiceCollection services,
    string snapshotBasePath,
    string pluginsPath)

注册的服务包括:

  • 策略(内置 7 条策略 + 依赖策略)
  • 导出器
  • 数据提供者
  • 仓库
  • 插件管理器
  • 策略执行管道
  • 外观

UI 层补充注册

Program.cs 中调用 AddSeatFlowApplication() 后,UI 层补充注册自己的单例:

services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IFileService, FileService>();
services.AddSingleton<IDialogService, DialogService>();
services.AddSingleton<WatchdogService>();
services.AddSingleton<MainWindow>();
services.AddSingleton<MainShellViewModel>();
// 所有页面 ViewModel
services.AddSingleton<HomeViewModel>();
services.AddSingleton<MemberManagementViewModel>();
// ...

日志

  • 使用 Serilog 4 + Microsoft.Extensions.Logging.ILogger<T>
  • 日志输出到文件(Serilog.Sinks.File
  • Application 服务通过构造函数注入 ILogger<T>
  • 可选日志参数回退到 NullLogger<T>.Instance
public class StrategyExecutionPipeline
{
    private readonly ILogger<StrategyExecutionPipeline> _logger;

    public StrategyExecutionPipeline(
        IEnumerable<ISeatingStrategy> strategies,
        ILogger<StrategyExecutionPipeline>? logger = null)
    {
        _strategies = strategies;
        _logger = logger ?? NullLogger<StrategyExecutionPipeline>.Instance;
    }
}