1、概述

  命令模式和策略模式的类图确实很相似,只是命令模式多了一个接收者(Receiver)角色。它们虽然同为行为类模式,但是两者的区别还是很明显的。策略模式的意图是封装算法,它认为“算法”已经是一个完整的、不可拆分的原子业务(注意这里是原子业务,而不是原子对象),即其意图是让这些算法独立,并且可以相互替换,让行为的变化独立于拥有行为的客户;而命令模式则是对动作的解耦,把一个动作的执行分为执行对象(接收者角色)、执行行为(命令角色),让两者相互独立而不相互影响。

  我们从一个相同的业务需求出发,按照命令模式和策略模式分别设计出一套实现,来看看它们的侧重点有什么不同。zip和gzip文件格式相信大家都很熟悉,它们是两种不同的压缩格式,我们今天就来对一个目录或文件实现两种不同的压缩方式:zip压缩和gzip压缩(这里的压缩指的是压缩和解压缩两种对应的操作行为,下同)。

2、策略模式实现压缩算法

2.1 类图

  使用策略模式实现压缩算法非常简单,也是非常标准的。

  在类图中,我们的侧重点是zip压缩算法和gzip压缩算法可以互相替换,一个文件或者目录可以使用zip压缩,也可以使用gzip压缩,选择哪种压缩算法是由高层模块(实际操作者)决定的。

2.2 代码

2.2.1 抽象的压缩算法

  我们来看一下代码实现。先看抽象的压缩算法。

class CIAlgorithm
{
public:
CIAlgorithm(){};
~CIAlgorithm(){}; //压缩算法
bool mbCompress(const string &sSource, const string &sTo);
//解压缩算法
bool mbUncompress(const string &sSource, const string &sTo);
};

  每一个算法要实现两个功能:压缩和解压缩,传递进来一个绝对路径source,compress把它压缩到to目录下,uncompress则进行反向操作——解压缩,这两个方法一定要成对地实现,为什么呢?用gzip解压缩算法能解开zip格式的压缩文件吗?

2.2.2 zip压缩算法

class CZip : public CIAlgorithm
{
public:
CZip(){};
~CZip(){}; //zip格式的压缩算法
bool mbCompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP压缩成功!" << endl;
return true;
} // zip格式的解压缩算法
bool mbUncompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP解压缩成功!" << endl;
return true;
}
};

2.2.3 gzip压缩算法

class CGzip : public CIAlgorithm
{
public:
CGzip(){};
~CGzip(){}; //gzip的压缩算法
bool mbCompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP压缩成功!" << endl;
return true;
} // gzip解压缩算法
bool mbUncompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP解压缩成功!" << endl;
return true;
}
};

2.2.4 环境角色

  这两种压缩算法实现起来都很简单,两个具体的算法实现了同一个接口,完全遵循依赖倒转原则。我们再来看环境角色。

class CContext
{
public:
//构造函数传递具体的算法
CContext(CIAlgorithm *opAlgorithm){ mopAlgotithm = opAlgorithm; };
~CContext(){}; // 执行压缩算法
bool mbCompress(const string &sSource, const string &sTo)
{
this->mopAlgotithm->mbCompress(sSource, sTo);
return true;
} //执行解压缩算法
bool mbUncompress(const string &sSource, const string &sTo)
{
this->mopAlgotithm->mbUncompress(sSource, sTo);
return true;
} private:
//指向抽象算法
CIAlgorithm *mopAlgotithm;
};

  也是非常简单,指定一个算法,执行该算法,一个标准的策略模式就编写完毕了。

2.2.5 调用

  请注意,这里虽然有两个算法Zip和Gzip,但是对调用者来说,这两个算法没有本质上的区别,只是“形式”上不同,什么意思呢?从调用者来看,使用哪一个算法都无所谓,两者完全可以互换,甚至用一个算法替代另外一个算法。

int main()
{
//对文件执行zip压缩算法
CContext *op_context = new CContext(new CZip); //执行压缩算法
op_context->mbCompress("c:\\windows", "d:\\windows.zip"); //执行解压缩算法
op_context->mbUncompress("c:\\windows.zip", "d:\\windows"); return ;
}

2.2.6 执行结果

  要使用gzip算法吗?在用户调用换成new CGzip可以了,其他的模块根本不受任何影响,策略模式关心的是算法是否可以相互替换。策略模式虽然简单,但是在项目组使用得非常多,可以说随手拈来就是一个策略模式。

3、命令模式实现压缩算法

3.1 类图

  命令模式的主旨是封装命令,使请求者与实现者解耦。例如,到饭店点菜,客人(请求者)通过服务员(调用者)向厨师(接收者)发送了订单(行为的请求),该例子就是通过封装命令来使请求者和接收者解耦。我们继续来看压缩和解压缩的例子,怎么使用命令模式来完成该需求呢?我们先画出类图。

  类图看着复杂,但是还是一个典型的命令模式,通过定义具体命令完成文件的压缩、解压缩任务,注意我们这里对文件的每一个操作都是封装好的命令,对于给定的请求,命令不同,处理的结果当然也不同,这就是命令模式要强调的。

3.2 代码

3.2.1 抽象命令

class CCMd
{
public:
virtual bool mbExecute(const string &sSource, const string &sTo) = ; protected:
CCMd()
{
mopZip = new CZipReceiver;
mopGzip = new CGzipReceiver;
} ~CCMd(){};
protected:
CIReceiver *mopZip;
CIReceiver *mopGzip;
};

  抽象命令定义了两个接收者的引用:zip接收者和gzip接收者,它们完全是受众,人家让它干啥它就干啥,具体使用哪个接收者是命令决定的。具体命令有4个:zip压缩、zip解压缩、gzip压缩、gzip解压缩。

3.2.2 zip压缩命令

class CZipCompressCmd : public CCMd
{
public:
CZipCompressCmd(){};
~CZipCompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopZip->mbCompress(sSource, sTo); }
};

3.2.3 zip解压缩命令

class CZipUncompressCmd : public CCMd
{
public:
CZipUncompressCmd(){};
~CZipUncompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopZip->mbUncompress(sSource, sTo); }
};

3.2.4 gzip压缩命令

class CGzipCompressCmd : public CCMd
{
public:
CGzipCompressCmd(){};
~CGzipCompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopGzip->mbCompress(sSource, sTo); }
};

3.2.5 gzip解压缩命令

class CGzipUncompressCmd : public CCMd
{
public:
CGzipUncompressCmd(){};
~CGzipUncompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopGzip->mbUncompress(sSource, sTo); }
};

  它们非常简单,都只有一个方法,坚决地执行命令,使用了委托的方式,由接收者来实现。

3.2.6 抽象接收者

class CIReceiver
{
public:
CIReceiver(){};
~CIReceiver(){}; //压缩
virtual bool mbCompress(const string &sSource, const string &sTo) = ; //解压缩
virtual bool mbUncompress(const string &sSource, const string &sTo) = ;
};

  抽象接收者与策略模式的抽象策略完全相同,具体的实现也完全相同,只是类名做了改动。

3.2.7 zip接收者

class CZipReceiver :public CIReceiver
{
public:
CZipReceiver(){};
~CZipReceiver(){}; //zip格式的压缩算法
bool mbCompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP压缩成功!" << endl;
return true;
} //zip格式的解压缩算法
bool mbUncompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP解压缩成功!" << endl;
return true;
}
};

  这就是一个具体动作执行者,它在策略模式中是一个具体的算法,关心的是是否可以被替换;而在命令模式中,它则是一个具体、真实的命令执行者。

3.2.8 gzip接收者

class CGzipReceiver :public CIReceiver
{
public:
CGzipReceiver(){};
~CGzipReceiver(){}; //zip格式的压缩算法
bool mbCompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP压缩成功!" << endl;
return true;
} //zip格式的解压缩算法
bool mbUncompress(const string &sSource, const string &sTo)
{
cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP解压缩成功!" << endl;
return true;
}
};

3.2.9 调用者

  命令、 接收者都具备了, 我们再来封装一个命令的调用者。

class CInvoker
{
public:
CInvoker(CCMd *opCmd) { mopCmd = opCmd; }
~CInvoker(){}; //执行命令
bool mbExecute(const string &sSource, const string &sTo)
{
mopCmd->mbExecute(sSource, sTo);
return true;
} private:
//抽象命令的引用
CCMd *mopCmd;
};

  调用者非常简单,只负责把命令向后传递,当然这里也可以进行一定的拦截处理,我们暂时用不到就不做处理了。

3.2.10 场景调用

int main()
{
//定义一个命令,压缩一个文件
CCMd *op_cmd = new CZipCompressCmd; //定义调用者
CInvoker *op_invoker = new CInvoker(op_cmd); cout << "========执行压缩命令========" << endl;
op_invoker->mbExecute("c:\\windows", "d:\\windows.zip"); return ;
}

3.2.11 执行结果

3.3 小结

  想新增一个命令?当然没有问题,只要重新定义一个命令就成,命令改变了,高层模块只要调用它就成。请注意,这里的程序还有点欠缺,没有与文件的后缀名绑定,不应该出现使用zip压缩命令产生一个.gzip后缀的文件名,读者在实际应用中可以考虑与文件后缀名之间建立关联。

  通过以上例子,我们看到命令模式也实现了文件的压缩、解压缩的功能,它的实现是关注了命令的封装,是请求者与执行者彻底分开,看看我们的程序,执行者根本就不用了解命令的具体执行者,它只要封装一个命令——“给我用zip格式压缩这个文件”就可以了,具体由谁来执行,则由调用者负责,如此设计后,就可以保证请求者和执行者之间可以相互独立,各自发展而不相互影响。

  同时,由于是一个命令模式,接收者的处理可以进行排队处理,在排队处理的过程中,可以进行撤销处理,比如客人点了一个菜,厨师还没来得及做,那要撤回很简单,撤回也是命令,这是策略模式所不能实现的。

4、总结

  命令模式和策略模式的类图完全一样,代码实现也比较类似,但是两者还是有区别的。

  ● 关注点不同

  策略模式关注的是算法替换的问题,一个新的算法投产,旧算法退休,或者提供多种算法由调用者自己选择使用,算法的自由更替是它实现的要点。换句话说,策略模式关注的是算法的完整性、封装性,只有具备了这两个条件才能保证其可以自由切换。

  命令模式则关注的是解耦问题,如何让请求者和执行者解耦是它需要首先解决的,解耦的要求就是把请求的内容封装为一个一个的命令,由接收者执行。由于封装成了命令,就同时可以对命令进行多种处理,例如撤销、记录等。

  ● 角色功能不同
  在我们的例子中,策略模式中的抽象算法和具体算法与命令模式的接收者非常相似,但是它们的职责不同。策略模式中的具体算法是负责一个完整算法逻辑,它是不可再拆分的原子业务单元,一旦变更就是对算法整体的变更。

  而命令模式则不同,它关注命令的实现,也就是功能的实现。例如我们在分支中也提到接收者的变更问题,它只影响到命令族的变更,对请求者没有任何影响,从这方面来说,接收者对命令负责,而与请求者无关。命令模式中的接收者只要符合六大设计原则,完全不用关心它是否完成了一个具体逻辑,它的影响范围也仅仅是抽象命令和具体命令,对它的修改不会扩散到模式外的模块。

  当然,如果在命令模式中需要指定接收者,则需要考虑接收者的变化和封装,例如一个老顾客每次吃饭都点同一个厨师的饭菜,那就必须考虑接收者的抽象化问题。

  ● 使用场景不同
  策略模式适用于算法要求变换的场景,而命令模式适用于解耦两个有紧耦合关系的对象场合或者多命令多撤销的场景。

【设计模式】 模式PK:命令模式VS策略模式的更多相关文章

  1. 设计模式(一):“穿越火线”中的“策略模式”(Strategy Pattern)

    在前段时间呢陆陆续续的更新了一系列关于重构的文章.在重构我们既有的代码时,往往会用到设计模式.在之前重构系列的博客中,我们在重构时用到了“工厂模式”.“策略模式”.“状态模式”等.当然在重构时,有的地 ...

  2. Java 设计模式系列(十二)策略模式(Strategy)

    Java 设计模式系列(十二)策略模式(Strategy) 策略模式属于对象的行为模式.其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换.策略模式使得算法可以 ...

  3. 设计模式学习总结(八)策略模式(Strategy)

    策略模式,主要是针对不同的情况采用不同的处理方式.如商场的打折季,不同种类的商品的打折幅度不一,所以针对不同的商品我们就要采用不同的计算方式即策略来进行处理. 一.示例展示: 以下例子主要通过对手机和 ...

  4. 学C#之设计模式系列笔记(1)策略模式

    一.借鉴说明 1.<Head First Design Patterns>(中文名<深入浅出设计模式>) 2.维基百科,策略模式,https://zh.wikipedia.or ...

  5. Java设计模式从精通到入门三 策略模式

    介绍 我尽量用最少的语言解释总结: Java23种设计模式之一,属于行为型模式.一个类的行为或者算法可以在运行时更改,策略对象改变context对象执行算法. 应用实例: ​ 以周瑜赔了夫人又折兵的例 ...

  6. 《大话设计模式》ruby版代码:策略模式

    需求: 商场收银软件,根据客户购买物品的单价和数量,计算费用,会有促销活动,打八折,满三百减一百之类的. 一,使用工厂模式. # -*- encoding: utf-8 -*- #现金收费抽象类 cl ...

  7. Android设计模式(二)--策略模式

    1.定义: The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them inter ...

  8. LVOOP设计模式在路上(二)-- 策略模式

    前言 最近工作还挺忙的,连着好些周都是单休了,今天休息在家就来写写关于策略模式的理解和labivew的实现. 正文 1.什么是策略模式 定义是这样描述的:它定义了算法家族,分别封装起来,让它们之间可以 ...

  9. JAVA设计模式详解(一)----------策略模式

    策略模式,顾名思义就是设计一个策略算法,然后与对象拆分开来将其单独封装到一系列策略类中,并且它们之间可以相互替换.首先LZ举一个例子为大家引出这一个模式. 例子:某公司的中秋节奖励制度为每个员工发放2 ...

  10. Java设计模式之(十四)——策略模式

    1.什么是策略模式? Define a family of algorithms, encapsulate each one, and make them interchangeable. Strat ...

随机推荐

  1. Bus of Characters(栈和队列)

    In the Bus of Characters there are nn rows of seat, each having 22 seats. The width of both seats in ...

  2. coding.net 版本控制

    这是版本测试的所有内容,其中用到了  git 和coding的远程连接. 代码及版本控制 git地址:https://git.coding.net/tianjiping/11111.git

  3. mysql入门 — (1)

    使用cd进入到mysql/bin文件夹下面,或者配置完环境之后,直接在cmd中使用mysql,然后回车开启mysql. 登录 为了安全考虑,在这里只设置了本地root用户可以连接上数据库.使用的指令是 ...

  4. URL中编码问题

    1.http协议传输统一iso-8859-1传输 jsp中用request.getparameter("keword");得到的是iso-8859-1翻译过来的,要用 keywor ...

  5. solr 学习之数据导入

    将数据库中的数据导入到我们的solr索引库中(DataImportHandler) 1.将jdbc的jar包和solr包中的DataImport的jar包拷贝到webapp中solr/WEB-INF/ ...

  6. BZOJ 1040 骑士(环套树DP)

    如果m=n-1,显然这就是一个经典的树形dp. 现在是m=n,这是一个环套树森林,破掉这个环后,就成了一个树,那么这条破开的边连接的两个顶点不能同时选择.我们可以对这两个点进行两次树形DP根不选的情况 ...

  7. 前端基础:JavaScript BOM对象

    JavaScript BOM对象 JavaScript Window - 浏览器对象模型 浏览器对象模型(BOM)使JavaScript有能力与浏览器"对话". 浏览器对象模型(B ...

  8. JS执行上下文(执行环境)详细图解

    JS执行上下文(执行环境)详细图解 先随便放张图 我们在JS学习初期或者面试的时候常常会遇到考核变量提升的思考题.比如先来一个简单一点的. console.log(a); // 这里会打印出什么? v ...

  9. [bzoj] 2002 弹飞绵羊 || LCT

    原题 简单的LCT练习题. 我们发现对于一个位置x,他只能跳到位置x+k,也就是唯一的父亲去.加入我们将弹飞的绵羊定义为跳到了n+1,那么这就形成了一棵树.而因为要修改k,所以这颗树是动态连边的,那么 ...

  10. POSIX.2 正则表达式

    By francis_hao    Oct 1,2017   这里的正则表达式主要是指扩展正则,也就是egrep(grep -e)用到的正则表达式. 字符 含义 类别说明 | 分割分支,正则表达式会去 ...