.NET中的设计模式四:命令模式 选择自 lane_cn 的 Blog

本文介绍命令模式在报表系统中的应用。Command类实现ICommand及execute方法,系统运用该模式分离复杂业务逻辑。还采用单元测试开发方式,提高可测试性。此外,命令模式常与工厂、组合模式结合,且命令类序列化可实现强大功能,如异常处理和分布式操作。
命令模式(Command)结构并不复杂,简单的说,就是将某些职责封装成对象,对象的激发者(Invoker)和接收者(Reciever)都不过问命令的执行过程。用这样的方式解除了激发者和接受者之间的耦合,为程序提供了更清晰的层次结构。实际运用中经常是将系统中的命令抽象成一个接口,所有的命令都是这个接口的实现,如下所示:

Command类是ICommand的实现,Command类实现了execute方法,其他命令类都是Command方法的子类。这样激发者接受者和接受者面临的环境就非常简单了:统一作为Command类型就可以处理了。

最简单的命令模式的实现代码如下:

///定义Command接口

public interface ICommand

{

    void execute();

}

 

///实现ICommand接口

///Command类是所有命令的基类,在这里实现一些命令的共同处理过程

public abstract class Command : ICommand

{

    public virtual void execute()

    {

    }

    //实现其他的方法....

}

 

///实现一个具体的Command1

public class Command1 : Command

{

    public void override execute()

    {

        //具体的处理过程

    }

}

 

///实现一个具体的Command2

public class Command2 : Command

{

    public void override execute()

    {

        //具体的处理过程

    }

}

 

///实现一个具体的Command3

public class Command3 : Command

{

    public void override execute()

    {

        //具体的处理过程

    }

}

命令模式的实际应用

下面从一个系统实例看一看命令模式的应用。ReportingService是一个报表系统的一部分,是一个运行在Windows系统上的Service程序,所需要完成的功能是:每天晚上11点到凌晨2点,将数据库中的大批量的数据生成数百张报表。报表的分布较为复杂,有的要求在Web服务器上发布报表,有的要求将报表数据输出成一个文本文件,还有的要将报表保存在数据库系统中。

系统的设计运用了命令模式,其中的一部分结构如下:

程序中定义了ICommand接口,实现了多个Command类(这里只画出3个表示示例),分别执行各个报表的生成任务。Command类提供了下列接口:

分类名称类型说明
属性finishedbool表示任务是否已经进行完毕
属性contextIApplicationContext任务执行需要的环境和资源,包括数据库连接、文件系统等等
方法executevoid执行任务

Service的主线程每天定时将系统运行环境进行初始化,建立一个ApplicationContext类的实例(Application是一个Singlton模式的类,图中没有体现),这个类中保存了Command运行需要使用的全部资源。然后初始化一个Command对象列表,逐个执行每一个Command。这样一来,复杂的业务逻辑就从程序的主框架中分离开了,Service的主线程所需要做的全部工作就是协调资源的分配和异常的处理,还可以不断检查命令执行的状态,看看finished属性是否为真。如果执行完以后发现某些命令执行不成功,要做相应的处理。

程序采用了单元测试的开发方式,在对程序主线程进行测试的时候,使用了一个虚拟的Command实现。测试每一个Command类的时候则为每个Command实现了虚拟的ApplicationContext。这样的结构为单元测试提供了很多便利。关于提高程序的可测试性可以参见我的另一篇文章:怎样测试代码中难测试的部分

命令模式和其他模式的结合

为了给Command的建立提供一个整洁简单的方式,Command模式经常和工厂模式(Factory)结合使用,采用一个工厂对Command对象的建立进行管理,减少命令激发者与命令对象之间的耦合程度。

在窗体程序中,Command模式还经常与组合模式(Composition)结合使用。可以在初始化窗体的时候,将画面上的菜单项、工具栏、按钮等控件与相应的Command进行组合,这样在控件点击时,调用对应的Command,即可实现对命令的处理。

在《程序员》杂志本年第10月期介绍了一个利用Command模式实现程序中Undo功能的文章。程序中为每个Command定义了“执行”和“撤销”两种操作,如下:

图中的Action就是Commamnd对象,只是名称不同,意义是一样的。Action对象存在do和undo方法,分别是“执行”和“撤销”方法。可以将执行完毕的Action对象的done属性设置为true,再将其保存在一个列表中——ActionList。在ActionList中找到最后一个done属性为true的Action,执行其undo方法,即可实现“撤销”的操作。

命令类的序列化

如果将Command基类及其每一个子类都进行序列化,可以实现更多的强大的功能。程序执行过程中,如果发生异常,比如网络发生中断、文件丢失等异常,可以将没有执行完毕的Command序列化保存下来。等待可以执行的时候再将这些对象重新加载,命令可以继续执行。甚至用户已经重新启动了计算机,命令也可以继续执行。Command对象的序列化也为分布式的操作提供了便利。在网络环境中,可以在某处建立一个Command对象,将其序列化后传输到网络上另一台计算机上进行执行。这种情况下不要忘记异常类的序列化,如果自己定义了Exception的子类,也要将其进行序列化,以便在网络环境中捕捉和处理Command执行时发生的异常。Microsoft网站上有一篇文章介绍.NET中对象的序列化,详细的介绍了对象序列化的概念。

07-31
这段代码是一个 C++ 中的 **内联函数(inline function)**,用于将一种枚举类型 `cem::message::env_model::LaneType` 转换为另一种枚举类型 `cem::message::sensor::BevLaneType`。它的作用是将环境模型中的车道类型映射为 BEV(Bird's Eye View,鸟瞰图)传感器模块使用的车道类型。 --- ## 函数定义解析 ```cpp inline cem::message::sensor::BevLaneType mapLaneType(cem::message::env_model::LaneType lane_type) const { ... } ``` ### 1. `inline` - **作用**:告诉编译器这个函数是“内联函数”,建议编译器在调用处展开函数体,避免函数调用的栈开销。 - **适用场景**:小型、频繁调用的函数,如映射函数、getter/setter 等。 - **注意**:是否真正内联由编译器决定,`inline` 只是建议。 ### 2. 返回类型 ```cpp cem::message::sensor::BevLaneType ``` - 表示返回值是一个 `BevLaneType` 类型的枚举值,属于 `cem::message::sensor` 命名空间。 ### 3. 参数 ```cpp cem::message::env_model::LaneType lane_type ``` - 输入参数是一个 `LaneType` 类型的枚举值,属于 `cem::message::env_model` 命名空间。 ### 4. `const` - 函数末尾的 `const` 表示这是一个 **常量成员函数**,即该函数不会修改类的成员变量。 - 仅适用于类中的成员函数。所以这个函数应该是某个类的成员函数。 --- ## 函数逻辑解析 函数使用 `switch` 语句对输入的 `lane_type` 进行分类,然后映射为 `BevLaneType` 的不同值。 ### 示例映射逻辑: ```cpp case cem::message::env_model::LaneType::LANE_UNKNOWN: return cem::message::sensor::BevLaneType::LANE_TYPE_UNKNOWN; ``` - 当车道类型为“未知”时,返回 BEV 中的“未知”类型。 ```cpp case cem::message::env_model::LaneType::LANE_NORMAL: case cem::message::env_model::LaneType::LANE_ACC: case cem::message::env_model::LaneType::LANE_DEC: case cem::message::env_model::LaneType::LANE_ACC_DCC: return cem::message::sensor::BevLaneType::LANE_TYPE_MAIN; ``` - 多个不同的环境模型车道类型被映射为同一个 BEV 类型 `LANE_TYPE_MAIN`。 --- ## 使用方式 假设你有一个 `LaneType` 类型的变量: ```cpp cem::message::env_model::LaneType input = cem::message::env_model::LaneType::LANE_NORMAL; ``` 然后你可以调用这个函数进行映射: ```cpp auto output = mapLaneType(input); ``` 输出 `output` 的类型就是 `cem::message::sensor::BevLaneType`。 --- ## 示例类中使用 ```cpp class BevMapper { public: inline cem::message::sensor::BevLaneType mapLaneType(cem::message::env_model::LaneType lane_type) const { // switch 实现 } }; // 使用 BevMapper mapper; auto bev_type = mapper.mapLaneType(cem::message::env_model::LaneType::LANE_RAMP); ``` --- ## 为什么使用 `inline`? 如果这个函数被多个 `.cpp` 文件包含(例如放在头文件中),为了防止链接错误(multiple definition),必须使用 `inline`。 --- ## 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值