如何通过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 模式重构命令行工具,可以获得:
• 清晰的结构
• 易扩展、易维护
• 新功能零侵入
• 工程级可读性
