如何通过Command模式扩展命令行工具的功能?

摘要:用 Command 模式构建可扩展的 C# 命令行工具(支持多命令与路径解析) 在开发工具型程序(如:数据转换、图像处理、批处理工具)时,一个常见的演进过程是: 一个 Main → 一堆
用 Command 模式构建可扩展的 C# 命令行工具(支持多命令与路径解析) 在开发工具型程序(如:数据转换、图像处理、批处理工具)时,一个常见的演进过程是: 一个Main→ 一堆 if-else → 越来越难维护 本文介绍一种工程实践中非常成熟的做法: 用 Command 模式重构命令行工具,让每个功能成为一个独立命令(Command),主程序只负责调度(dispatch)。 一、问题背景:为什么要重构 CLI 结构? 传统写法通常是这样: staticvoidMain(string[]args) { if(args[0] =="a") {/* 功能 A */} elseif(args[0] =="b") {/* 功能 B */} elseif(args[0] =="c") {/* 功能 C */} } 当功能增加后,会出现: •Main过于臃肿 • 功能之间强耦合 • 不利于测试与扩展 • 不利于长期维护 更合理的目标是: 新增一个命令,只新增一个文件 主程序不需要修改逻辑 二、设计目标 我们希望命令行工具具备以下特性: • 每个功能是一个独立命令 • 支持命令行传参(如-i input -o output) • 支持相对路径 / 绝对路径切换 • 主程序只负责「分发命令」 三、整体架构概览 CLI Tool │ ├── Program.cs // 只做 dispatch ├── ICommand.cs // Command 抽象 ├── CommandContext.cs // 参数 & 路径上下文 ├── PathResolver.cs // 路径解析 │ ├── Commands/ │ ├── CommandA.cs │ ├── CommandB.cs │ └── CommandC.cs 四、核心思想:Command 模式 1️⃣ Command 接口 publicinterfaceICommand { stringName {get; } stringDescription {get; } voidExecute(CommandContext context); } •Name:命令名(如convert,export) •Execute:命令执行入口 2️⃣ 示例 Command(功能模块) publicclassCommandExample:ICommand { publicstringName =>"example"; publicstringDescription =>"Run example task"; publicvoidExecute(CommandContext ctx) { stringinput = ctx.ResolvePath("i"); stringoutput = ctx.ResolvePathOrDefault("o","out.dat"); ExampleProcessor.Run(input, output); } } 👉每个命令一个类,职责单一 五、主程序:只负责 Dispatch classProgram { staticreadonlyList<ICommand> Commands =new() { newCommandExample(), newCommandOther() }; staticvoidMain(string[]args) { var(cmdName, options) = ArgParser.Parse(args); varcommand = Commands.FirstOrDefault(c => c.Name == cmdName); if(command ==null) { PrintHelp(); return; } varcontext =newCommandContext(options); command.Execute(context); } } 主程序的特点: • 不包含业务逻辑 • 不关心参数含义 • 只负责: • 找到 Command • 调用Execute 六、命令行参数解析(示例) publicstaticclassArgParser { publicstatic(string, Dictionary<string,string>)Parse(string[]args) { stringcommand =args[0]; vardict =newDictionary<string,string>(); for(inti =1; i <args.Length -1; i++) { if(args[i].StartsWith("-")) dict[args[i].TrimStart('-')] =args[++i]; } return(command, dict); } } 示例调用: tool.exe example -i data/input.json -o result.bin 七、路径处理:支持相对 / 绝对模式 命令行工具中,路径问题非常常见。 CommandContext publicclassCommandContext { publicDictionary<string,string> Args {get; } publicstringBaseDir {get; } publicCommandContext(Dictionary<string,string>args) { Args =args; BaseDir =args.ContainsKey("absolute") ? Directory.GetCurrentDirectory() : AppContext.BaseDirectory; } publicstringResolvePath(stringkey) { returnPathResolver.Resolve(Args[key], BaseDir); } } PathResolver publicstaticclassPathResolver { publicstaticstringResolve(stringpath,stringbaseDir) { returnPath.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(baseDir, path)); } } 支持: tool.exe example -i data/a.json tool.exe example -i data/a.json --absolute 八、用到了哪些设计模式? ✅ Command Pattern(核心) • 每个命令封装一个操作 • 主程序通过接口统一调用 ✅ Strategy Pattern(弱形式) • 不同 Command = 不同执行策略 • 运行时选择 ✅ Context Object(工程实践) • 参数、路径、环境信息集中管理 • Command 不直接依赖全局状态 九、这种结构适合什么场景? 非常适合: • 数据处理工具 • 验证工具 • Unity / OpenCV / Web 辅助工具 • 内部工程 CLI 工具链 甚至可以无缝接入 Unity Editor 或 CI 流水线。 十、总结 通过 Command 模式重构命令行工具,可以获得: • 清晰的结构 • 易扩展、易维护 • 新功能零侵入 • 工程级可读性