用 Command 模式构建可扩展的 C# 命令行工具(支持多命令与路径解析)
在开发工具型程序(如:数据转换、图像处理、批处理工具)时,一个常见的演进过程是:
一个
Main→ 一堆 if-else → 越来越难维护
本文介绍一种工程实践中非常成熟的做法:
用 Command 模式重构命令行工具,让每个功能成为一个独立命令(Command),主程序只负责调度(dispatch)。
一、问题背景:为什么要重构 CLI 结构?
传统写法通常是这样:
static void Main(string[] args)
{
if (args[0] == "a") { /* 功能 A */ }
else if (args[0] == "b") { /* 功能 B */ }
else if (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 接口
public interface ICommand
{
string Name { get; }
string Description { get; }
void Execute(CommandContext context);
}
-
• Name:命令名(如convert,export) -
• Execute:命令执行入口
2️⃣ 示例 Command(功能模块)
public class CommandExample : ICommand
{
public string Name => "example";
public string Description => "Run example task"; public void Execute(CommandContext ctx)
{
string input = ctx.ResolvePath("i");
string output = ctx.ResolvePathOrDefault("o", "out.dat"); ExampleProcessor.Run(input, output);
}
}
👉 每个命令一个类,职责单一
五、主程序:只负责 Dispatch
class Program
{
static readonly List<ICommand> Commands = new()
{
new CommandExample(),
new CommandOther()
}; static void Main(string[] args)
{
var (cmdName, options) = ArgParser.Parse(args); var command = Commands.FirstOrDefault(c => c.Name == cmdName);
if (command == null)
{
PrintHelp();
return;
} var context = new CommandContext(options);
command.Execute(context);
}
}
主程序的特点:
-
• 不包含业务逻辑 -
• 不关心参数含义 -
• 只负责: -
• 找到 Command -
• 调用 Execute
六、命令行参数解析(示例)
public static class ArgParser
{
public static (string, Dictionary<string, string>) Parse(string[] args)
{
string command = args[0];
var dict = new Dictionary<string, string>(); for (int i = 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
public class CommandContext
{
public Dictionary<string, string> Args { get; }
public string BaseDir { get; } public CommandContext(Dictionary<string, string> args)
{
Args = args;
BaseDir = args.ContainsKey("absolute")
? Directory.GetCurrentDirectory()
: AppContext.BaseDirectory;
} public string ResolvePath(string key)
{
return PathResolver.Resolve(Args[key], BaseDir);
}
}
PathResolver
public static class PathResolver
{
public static string Resolve(string path, string baseDir)
{
return Path.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 模式重构命令行工具,可以获得:
-
• 清晰的结构 -
• 易扩展、易维护 -
• 新功能零侵入 -
• 工程级可读性