前言
本周五参加了Strategy(策略)模式研讨会,本文的目的是对这个模式的总结和分享,希望对需要学习这个设计模式的同学有所帮助。本文一共分为两个部分,第一个部分是策略模式的基础知识总结;第二个部分是分享策略模式简单示例。一、策略模式总结
1、模式意图
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。2、参与者
Strategy(策略)
– 定义所有持的算法的公共接口。context使用这个接口来调用某concreteStrategy定义的算法;
ConcreteStrategy(具体策略)
– 以Strategy接口实现某具体算法;
Context(上下文)
– 用一个ConcreteStrategy对象来配置;
– 维护一个对Strategy对象的引用;
– 可定义一个接口来让Strategy访问它的数据;
3、结构、协作、适用性及效果
结构图: 协作:1、Strategy和Context相互作用以实现选定的算法。当算法被调用时 , Context可以将该算法 所需要的所有数据都传递给该 Strategy。或者,Context可以将自身作为一个参数传递给 Strategy操作。这就让Strategy在需要时可以回调Context;
2、Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy 对象给该Context;这样, 客户仅与Context交互。通常有一系列的ConcreteStrategy类可供 客户从中选择。 适用性:
1、许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
2、需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3、算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4、一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
效果:
1、相关算法系列。
2、一个替代继承的方法。Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承
有助于析取出这些算法中的公共功能。.
继承提供了另一种支持多种算法或行为的方法。你可以直接生
成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到Context中,而将
算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且
还不能动态地改变算法。最后你得到一堆相关的类, 它们之间的唯一差别是它们所使用的算法
或行为。将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、
易于理解、易于扩展。
3、消除了一些条件语句。
4、实现的选择。Strategy模式提供了用条件语句选择所需的行为以外的另一种选
择。当不同的行为堆砌在一个类中时, 很难避免使用条件语句来选择合适的行为。将行为封装
在一个个独立的Strategy类中消除了这些条件语句。
5、客户必须了解不同的Strategy。Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间/空间
权衡取舍要求从不同策略中进行选择。
6、Strategy和Context之间的通信开销。本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时, 才需要使用Strategy模式。
无论各个ConcreteStrategy实现的算法是简单还是复
杂, 它们都共享Strategy定义的接口。因此很可能某些ConcreteStrategy不会都用到所有通过这
个接口传递给它们的信息;简单的ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题, 那么将需要在Strategy和Context之间更进行紧密的耦合。
二、策略模式简单示例
1、用例描述
策略的主要意图是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。所以,这个模式主要是使用场景是算法。大家知道,二叉树的遍历分为前序遍历、中序遍历、后序遍历,我们把这三种算法封装起来,这样,在遍历树的时候,就可以按需要切换不同的遍历算法,这是一个典型的策略模式应用场景。 前序遍历:根节点+左子树+右子树中序遍历:左子树+根节点+右子树在遍历左子树和右子树时,仍然先访问根节点,然后遍历左子树,最后遍历右子树。
后序遍历:左子树+右子树+根节点在遍历左右子树时,仍然先遍历左子树,再遍历根节点,后遍历右子树。
在遍历左右子树时,仍然先遍历左子树,在遍历右子树,后访问根节点。
2、示例代码
typedef struct tag_tree_node
{
//节点的数据
char data;
//左子节点指针
struct tag_tree_node* left_child;
//右子节点指针
struct tag_tree_node* right_child;
}tree_node_t;
class show_tree_base_t {
public:
virtual void show_tree(tree_node_t* root) = 0;
void visit(tree_node_t* root);
};
void show_tree_base_t::visit(tree_node_t *node){
}
class show_tree_pre_t : public show_tree_base_t{
public:
virtual void show_tree(tree_node_t* root);
};
void show_tree_pre_t::show_tree(tree_node_t *root){
visit(root);
this->show_tree(root->left_child);
this->show_tree(root->right_child);
}
class show_tree_in_t : public show_tree_base_t{
public:
virtual void show_tree(tree_node_t* root);
};
void show_tree_in_t::show_tree(tree_node_t *root){
this->show_tree(root->left_child);
visit(root);
this->show_tree(root->right_child);
}
class show_tree_post_t : public show_tree_base_t{
public:
virtual void show_tree(tree_node_t* root);
};
void show_tree_post_t::show_tree(tree_node_t *root){
this->show_tree(root->left_child);
this->show_tree(root->right_child);
visit(root);
}
tree_node_t* create_tree(){
}
void delete_tree(tree_node_t* root) {
}
class context_t {
public:
void set_show_strategy(show_tree_base_t* obj) {
m_show_tree_obj = obj;
}
void show(tree_node_t *root) {
if (m_show_tree_obj != nullptr && root != nullptr) {
m_show_tree_obj->show_tree(root);
}
}
private:
show_tree_base_t* m_show_tree_obj = nullptr;
};
int main(int argc, char *argv[])
{
show_tree_pre_t* pre = new show_tree_pre_t;
show_tree_in_t* in = new show_tree_in_t;
show_tree_post_t* post = new show_tree_post_t;
context_t* ctx = new context_t;
tree_node_t *root = create_tree();
//先序遍历
ctx->set_show_strategy(pre);
ctx->show(root);
//中序遍历
ctx->set_show_strategy(in);
ctx->show(root);
//后序遍历
ctx->set_show_strategy(post);
ctx->show(root);
delete pre;
delete in;
delete post;
delete_tree(root);
return 0;
}