当前位置: 首页 > 编程日记 > 正文

【设计模式】 模式PK:策略模式VS状态模式

1、概述

行为类设计模式中,状态模式和策略模式是亲兄弟,两者非常相似,我们先看看两者的通用类图,把两者放在一起比较一下。

策略模式(左)和状态模式(右)的通用类图。

两个类图非常相似,都是通过Context类封装一个具体的行为,都提供了一个封装的方法,是高扩展性的设计模式。但根据两者的定义,我们发现两者的区别还是很明显的:策略模式封装的是不同的算法,算法之间没有交互,以达到算法可以自由切换的目的;而状态模式封装的是不同的状态,以达到状态切换行为随之发生改变的目的。这两种模式虽然都有变换的行为,但是两者的目标却是不同的。我们举例来说明两者的不同点。

人只要生下来就有工作可做,人在孩童时期的主要工作就是玩耍(学习只是在人类具有了精神意识行为后才产生的);成人时期的主要工作是养活自己,然后为社会做贡献;老年时期的主要工作就是享受天伦之乐。按照策略模式来分析,这三种不同的工作方式就是三个不同的具体算法,随着时光的推移工作内容随之更替,这和对一堆数组的冒泡排序、快速排序、插入排序一样,都是一系列的算法;而按照状态模式进行设计,则认为人的状态(孩童、成人、老人)产生了不同的行为结果,这里的行为都相同,都是工作,但是它们的实现方式确实不同,也就是产生的结果不同,看起来就像是类改变了。

2、策略模式实现人生

2.1 类图

这是非常典型的策略模式,没有太多的玄机,它定义了一个工作算法,然后有三个实现类:孩童工作、成年人工作和老年人工作。

2.2 代码

2.2.1 抽象工作算法

class CWorkAlgorithm
{
public:CWorkAlgorithm(){};~CWorkAlgorithm(){};virtual void mvWork() = 0;
};

2.2.2 工作算法

无论如何,每个算法都必须实现work方法,完成对工作内容的定义,三个具体的工作算法。

//孩童工作
class CChildWork : public CWorkAlgorithm
{
public:CChildWork(){};~CChildWork(){};void mvWork() { cout << "儿童的工作是玩耍!" << endl; }
};//成年人工作
class CAdultWork : public CWorkAlgorithm
{
public:CAdultWork(){};~CAdultWork(){};void mvWork() { cout << "成年人的工作就是先养活自己, 然后为社会做贡献!" << endl; }
};//老年人工作
class COldWork : public CWorkAlgorithm
{
public:COldWork(){};~COldWork(){};void mvWork() { cout << "老年人的工作就是享受天伦之乐!" << endl; }
};

2.2.3 环境角色

class CContext
{
public:CContext(){};~CContext(){};CWorkAlgorithm *mopGetWorkAlgorithm() { return mopWork; }void mvSetWorkAlgorithm(CWorkAlgorithm *opWork) { mopWork = opWork; }// 每个算法都有必须具有的功能void mvWork() { mopWork->mvWork(); }private:CWorkAlgorithm *mopWork;
};

2.2.4 场景调用

int main()
{//定义一个环境角色CContext *op_context = new CContext;cout << "====儿童的主要工作=====" << endl;op_context->mvSetWorkAlgorithm(new CChildWork);op_context->mvWork();cout << "====成年人的主要工作=====" << endl;op_context->mvSetWorkAlgorithm(new CAdultWork);op_context->mvWork();cout << "====老年人的主要工作=====" << endl;op_context->mvSetWorkAlgorithm(new COldWork);op_context->mvWork();return 0;
}

2.2.5 执行结果

在这里我们把每个不同的工作内容作为不同的算法,分别是孩童工作、成年人工作、老年人工作算法,然后在场景类中根据不同的年龄段匹配不同的工作内容,其运行结果如下所示。

2.3 小结

通过采用策略模式我们实现了“工作”这个策略的三种不同算法,算法可以自由切换,到底用哪个算法由调用者(高层模块)决定。策略模式的使用重点是算法的自由切换——老的算法退休,新的算法上台,对模块的整体功能没有非常大的改变,非常灵活。而如果想要增加一个新的算法,比如未出生婴儿的工作,只要继承WorkAlgorithm就可以了。

3、状态模式实现人生

3.1 类图

我们再来看看使用状态模式是如何实现该需求的。随着时间的变化,人的状态变化了,同时引起了人的工作行为改变,完全符合状态模式。这与策略模式非常相似,基本上就是几个类名称的修改而已,但是其中蕴藏的玄机就大了,看看代码你就会明白。

3.2 代码

3.2.1 人的抽象状态

// HumanState.h
class CHuman;class HumanState
{
public:HumanState();~HumanState();// 设置一个具体的人void mvSetHuman(CHuman *opHuman);// 不管人是什么状态都要工作virtual  void mvWork() = 0;
protected:CHuman *mopHuman;
};// HumanState.cpp
#include "HumanState.h"
#include "Human.h"HumanState::HumanState(){}HumanState::~HumanState(){}void HumanState::mvSetHuman(CHuman *opHuman)
{mopHuman = opHuman;
}

抽象状态定义了一个具体的人(human)必须进行工作(work),但是一个人在哪些状态下完成哪些工作则是由子类来实现的。

3.2.2 孩童状态

// HumanState.hclass CChildState : public HumanState
{
public:CChildState();~CChildState();void mvWork();
};// HumanState.cpp

CChildState::CChildState(){}CChildState::~CChildState(){}void CChildState::mvWork()
{cout << "儿童的工作是玩耍!" << endl;mopHuman->mvSetState(new CAdultState);
}

CChildState类代表孩童状态,在该状态下的工作就是玩耍。看着可能有点惊奇,在work方法中为什么要设置下一个状态?因为我们的状态变化都是单方向的,从孩童到成年人,然后到老年人,每个状态转换到其他状态只有一个方向,因此会在这里看到work有两个职责:完成工作逻辑和定义下一状态。

3.2.3 成年人和老年人

//成年人状态
// HumanState.hclass CAdultState : public HumanState
{
public:CAdultState();~CAdultState();void mvWork();
};// HumanState.cpp

CAdultState::CAdultState(){}CAdultState::~CAdultState(){};void CAdultState::mvWork()
{cout << "成年人的工作就是先养活自己, 然后为社会做贡献!" << endl;mopHuman->mvSetState(new COldState);
}
//老年人状态
// HumanState.hclass COldState : public HumanState
{
public:COldState();~COldState();void mvWork();
};// HumanState.cpp

COldState::COldState(){}COldState::~COldState(){}void COldState::mvWork()
{cout << "老年人的工作就是享受天伦之乐!" << endl;
}

每一个HumanState的子类都代表了一种状态,虽然实现的方法名work都相同,但是实现的内容却不同,也就是在不同的状态下行为随之改变。

3.2.4 环境角色

//Human.h
class HumanState;class CHuman
{
public:CHuman();~CHuman();void mvSetState(HumanState *opHumanState);void mvWork();private:HumanState *mopHumanState;
};//Human.cpp
#include "Human.h"
#include "HumanState.h"CHuman::CHuman(){}CHuman::~CHuman(){}void CHuman::mvSetState(HumanState *opHumanState)
{mopHumanState = opHumanState;mopHumanState->mvSetHuman(this);
}void CHuman::mvWork()
{mopHumanState->mvWork();
}

定义一个Human类代表人类,也就是状态模式中的环境角色,每个人都会经历从孩童到成年人再到老年人这样一个状态过渡(当然了,老顽童周伯通的情况我们就没有考虑进来),随着状态的改变,行为也改变。

3.2.5 场景调用

int main()
{//定义一个普通的人CHuman *op_human = new CHuman;
//设置一个人的初始状态op_human->mvSetState(new CChildState);cout << "====儿童的主要工作=====" << endl;op_human->mvWork();cout << "====成年人的主要工作=====" << endl;op_human->mvWork();cout << "====老年人的主要工作=====" << endl;op_human->mvWork();return 0; }

3.2.6 执行结果

3.3 小结

运行结果与策略模式相同,但是两者的分析角度是大相径庭的。策略模式的实现是通过分析每个人的工作方式的不同而得出三个不同的算法逻辑,状态模式则是从人的生长规律来分析,每个状态对应了不同的行为,状态改变后行为也随之改变。从以上示例中我们也可以看出,对于相同的业务需求,有很多种实现方法,问题的重点是业务关注的是什么,是人的生长规律还是工作逻辑?找准了业务的焦点,才能选择一个好的设计模式。

4、总结

从例子中我们可以看出策略模式和状态模式确实非常相似,称之为亲兄弟亦不为过,但是这两者还是存在着非常大的差别,而且也是很容易区分的。

● 环境角色的职责不同

两者都有一个叫做Context环境角色的类,但是两者的区别很大,策略模式的环境角色只是一个委托作用,负责算法的替换;而状态模式的环境角色不仅仅是委托行为,它还具有登记状态变化的功能,与具体的状态类协作,共同完成状态切换行为随之切换的任务。

● 解决问题的重点不同

策略模式旨在解决内部算法如何改变的问题,也就是将内部算法的改变对外界的影响降低到最小,它保证的是算法可以自由地切换;而状态模式旨在解决内在状态的改变而引起行为改变的问题,它的出发点是事物的状态,封装状态而暴露行为,一个对象的状态改变,从外界来看就好像是行为改变。

● 解决问题的方法不同

策略模式只是确保算法可以自由切换,但是什么时候用什么算法它决定不了;而状态模式对外暴露的是行为,状态的变化一般是由环境角色和具体状态共同完成的,也就是说状态模式封装了状态的变化而暴露了不同的行为或行为结果。

● 应用场景不同

两者都能实现前面例子中的场景,但并不表示两者的应用场景相同,这只是为了更好地展示出两者的不同而设计的一个场景。我们来想一下策略模式和状态模式的使用场景有什么不同,策略模式只是一个算法的封装,可以是一个有意义的对象,也可以是一个无意义的逻辑片段,比如MD5加密算法,它是一个有意义的对象吗?不是,它只是我们数学上的一个公式的相关实现,它是一个算法,同时DES算法、RSA算法等都是具体的算法,也就是说它们都是一个抽象算法的具体实现类,从这点来看策略模式是一系列平行的、可相互替换的算法封装后的结果,这就限定了它的应用场景:算法必须是平行的,否则策略模式就封装了一堆垃圾,产生了“坏味道”。状态模式则要求有一系列状态发生变化的场景,它要求的是有状态且有行为的场景,也就是一个对象必须具有二维(状态和行为)描述才能采用状态模式,如果只有状态而没有行为,则状态的变化就失去了意义。

● 复杂度不同

通常策略模式比较简单,这里的简单指的是结构简单,扩展比较容易,而且代码也容易阅读。当然,一个具体的算法也可以写得很复杂,只有具备很高深的数学、物理等知识的人才可以看懂,这也是允许的,我们只是说从设计模式的角度来分析,它是很容易被看懂的。而状态模式则通常比较复杂,因为它要从两个角色看到一个对象状态和行为的改变,也就是说它封装的是变化,要知道变化是无穷尽的,因此相对来说状态模式通常都比较复杂,涉及面很多,虽然也很容易扩展,但是一般不会进行大规模的扩张和修正。

转载于:https://www.cnblogs.com/ChinaHook/p/7475962.html

相关文章:

vs2008与IIS 7.0使用在vista上时出现的问题及解决方法(Internet Explorer 无法显示该页面)(VS2008: IE Cannot Display Web Page)...

我的系统是Vista Ultimate SP1,先安装了vs2008 ,然后再安装了IIS7.0之后就出现了一系列的问题。 问题&#xff1a;通过vs2008启动程序调试时报错。错误提示为&#xff1a;Internet Explorer 无法显示该页面 解决方法&#xff1a; 首先是安装一些必要的附件程序。 1.打开控制面板…

云服务中IaaS、PaaS、SaaS的区别

越来越多的软件&#xff0c;开始采用云服务。 云服务只是一个统称&#xff0c;可以分成三大类。 IaaS&#xff1a;基础设施服务&#xff0c;Infrastructure-as-a-servicePaaS&#xff1a;平台服务&#xff0c;Platform-as-a-serviceSaaS&#xff1a;软件服务&#xff0c;Softwa…

Android项目框架综合实例

综合使用ViewPager、Fragment、RecycleView等&#xff0c;实现类似“网易新闻浏览器 ”的项目综合框架&#xff0c;要求实现&#xff1a; 底部导航&#xff0c;分别是“首页”&#xff0c;“视频”&#xff0c;“讲讲”&#xff0c;“我的”&#xff1b;底部导航不要求滑动翻页…

配置Windows Server 2003 的RADIUS Server的方法

配置Windows Server 2003 的RADIUS Server的方法1、安装Windows 2003操作系统&#xff1b;2、添加角色&#xff08;须插网线&#xff09;&#xff1b;3、添加组件->网络服务、证书服务&#xff1b;4、管理工具->域安全策略->帐户策略->密码策略&#xff1b;&#x…

Y15BeTa蜂鸣器唱歌程序-演奏版

最优版&#xff0c;自由演奏你的音乐&#xff01; 每天进步一点点&#xff01; 2018-12-09最新版 #include<bits/stdc.h> #include<windows.h> using namespace std; int md[8]{0,262,294,330,349,392,440,494}, mz[8]{0,523,587,659,698,784,880,988}, mg[8]{0,10…

实验6 触发器的使用

实验6 触发器的使用 实验目的 掌握触发器的创建、修改和删除操作。 掌握触发器的触发执行。 掌握触发器与约束的不同。二、实验要求 1.创建触发器。 2.触发器执行触发器。 3.验证约束与触发器的不同作用期。 4.删除新创建的触发器。 三、实验内容 &#xff08;一&#x…

神经网络二(Neural Network)

#!/usr/bin/env python # -*- coding: utf-8 -*- """ __title__ __author__ wlc __mtime__ 2017/9/04 """ import numpy as np import randomclass Network(object):def __init__(self,sizes):#size神经元个数list[3,2,4]self.num_layers l…

要想成功 需要了解的东西

凭我工作的经历来看 在it界要想成功必须要做到以下几点。 1 基本的开发语言不一定精通&#xff0c;但是一定要熟练的使用。 2 对公的主营业务一定要熟悉&#xff0c;不但要熟悉&#xff0c;而且要烂熟于心。如果不能做到这一点&#xff0c;那么起码对自己负责的工作要做到烂熟…

合并下载的Solaris镜像为DVD文件的方法

有很多朋友想安装solaris10操作系统&#xff0c;但是没有系统盘或者在官方网站下载之后不会合成。经过多次试验之后现在把正确的方法写下&#xff0c;以方便大家的学习之用。1。先到官方网站下载最新的系统包&#xff0c;下载之后的软件包为&#xff1a;sol-10-u4-ga-x86-dvd-i…

oracle测试环境表空间清理

测试场景下&#xff0c;使用的oralce遇到表空间的占用超大&#xff0c;可以采用如下的方式进行空间的清理 首先使用sqlplus连接数据库sqlplus sys/passwordorcl as sysdba 之类进行数据库的连接没然后进行如下的操作 ##创建表空间对于自己的测试库和表等最好都建立自己的表空间…

Google Chrome(谷歌浏览器) 发布下载

Google Chrome 下载地址&#xff1a;http://www.google.com/chrome 刚刚装上&#xff0c;还没怎么用&#xff0c;说一下大概印象&#xff0c;整体非常简洁&#xff0c;只有两个菜单选项。访问上明显感觉很快&#xff0c;比 Firefox 快&#xff0c;也比 IE7快&#xff1b;对网页…

实验 5   数据的完整性管理

实验 5 数据的完整性管理 一、实验目的 掌握实体完整性的实现方法。掌握用户定义完整性的实现方法。掌握参照完整性的方法。二、实验内容 数据库的完整性设置。三、实验步骤 可视化界面的操作方法&#xff1a;实体完整性 将 student 表的“sno”字段设为主键&#xff1a;在表…

16-acrobat por 简单使用指南

用于pdf编辑&#xff0c;这里我主要讲下图片的切割和保存&#xff0c;以及合并&#xff1a; 切割选中区域双击 合并的话&#xff0c;在编辑界面选中对象&#xff0c;复制&#xff0c;在另一个pdf的编辑界面粘贴&#xff0c;并挪动位置&#xff1a; 转载于:https://www.cnblogs.…

可突破任意ARP防火墙,以限制流量为目标的简单网络管理软件

以下消息来自幻影论坛[Ph4nt0m]邮件组软件说明&#xff1a;可突破任意ARP防火墙&#xff0c;以限制流量为目标的简单网络管理软件。使用方法&#xff1a;1.在参数设置中选择好工作网卡&#xff1b;2.检查网关信息和本机信息是否正确&#xff0c;如果不正确&#xff0c;请手动输…

OpenCV 学习笔记03 boundingRect、minAreaRect、minEnclosingCircle、boxPoints、int0、circle、rectangle函数的用法...

函数中的代码是部分代码&#xff0c;详细代码在最后 1 cv2.boundingRect 作用&#xff1a;矩形边框&#xff08;boundingRect&#xff09;&#xff0c;用于计算图像一系列点的外部矩形边界。 cv2.boundingRect(array) -> retval 参数&#xff1a; array - 灰度图像&#xff…

实验1 应用SQL Server进行数据定义和管理

实验1 应用SQL Server进行数据定义和管理 【实验目的】 1&#xff09;熟悉SQL Server的配置和管理。 2&#xff09;掌握数据库的定义和修改方法。 3&#xff09;掌握表的定义和修改方法。 4&#xff09;掌握使用SQL语句进行数据管理的方法。 【实验环境】 SQL Server 20…

谷歌Chrome浏览器发布

谷歌已提前启用了浏览器Google Chrome的官方网站gears.google.com/chrome/&#xff0c;今天该浏览器的Windows版本首发。在此以前&#xff0c;谷歌与微软之间的斗争更象是“冷战”&#xff0c;大多局限于谷歌开发小型的、基于网络的软件&#xff0c;与微软占主导地位的Word、Po…

【bzoj1853】[Scoi2010]幸运数字 容斥原理+搜索

题目描述 在中国&#xff0c;很多人都把6和8视为是幸运数字&#xff01;lxhgww也这样认为&#xff0c;于是他定义自己的“幸运号码”是十进制表示中只包含数字6和8的那些号码&#xff0c;比如68&#xff0c;666&#xff0c;888都是“幸运号码”&#xff01;但是这种“幸运号码”…

Creating a LINQ Enabled ASP.NET Web application template using C#.[转]

原文地址&#xff1a;http://www.wwwcoder.com/Weblogs/tabid/283/EntryID/839/Default.aspx其他相关地址&#xff1a;Building and using a LINQ for SQL Class Library with ASP.NET 2.0 1. Install Visual Studio 2005 RTM. 2. Download and install "…

深入理解Java线程池:ThreadPoolExecutor

线程池介绍 在web开发中&#xff0c;服务器需要接受并处理请求&#xff0c;所以会为一个请求来分配一个线程来进行处理。如果每次请求都新创建一个线程的话实现起来非常简便&#xff0c;但是存在一个问题&#xff1a; 如果并发的请求数量非常多&#xff0c;但每个线程执行的时间…

[zt]petshop4.0 详解之八(PetShop表示层设计)

代码中&#xff0c;InsertUser()方法就是负责用户的创建&#xff0c;而在之前则需要判断创建的用户是否已经存在。InsertUser()方法的定义如下&#xff1a; privatestaticboolInsertUser(OracleTransaction transaction, intuserId, stringemail, stringpassword, intpassForma…

Install Java 8 Ubuntu

sudo add-apt-repository ppa:webupd8team/javasudo apt-get -y update sudo apt-get -y install oracle-java8-installer sudo vim /etc/environment Add this at the end of the file JAVA_HOME"/usr/lib/jvm/java-8-oracle" source /etc/environment转载于:https:…

实验2  使用T-SQL编写程序

实验2 使用T-SQL编写程序 【实验目的】 &#xff11;&#xff09;掌握常用函数的使用方法。 &#xff12;&#xff09;掌握流程控制语句的使用方法。 【实验环境】 SQL Server 2012 Express&#xff08;或SQL Server 2017 Express&#xff09; 【实验重点及难点】 1&…

超酷flash光芒光线特效

http://thefwa.com/ 一个不错的英文设计展示站点 超酷flash光芒光线特效 http://www.zcool.com.cn/flash/light/page_1.html

实验3  数据库综合查询

实验3 数据库综合查询 一、实验目的 掌握SELECT语句的基本语法和查询条件表示方法&#xff1b;掌握查询条件种类和表示方法&#xff1b;掌握连接查询的表示及使用&#xff1b;掌握嵌套查询的表示及使用&#xff1b;了解集合查询的表示及使用。 二、实验环境 已安装SQL Serv…

Find Large Files in Linux

https://www.rosehosting.com/blog/find-large-files-linux/转载于:https://www.cnblogs.com/WCFGROUP/p/10328469.html

Linux统计行数命令wc(转)

Linux wc命令用于计算字数。 利用wc指令我们可以计算文件的Byte数、字数、或是列数&#xff0c;若不指定文件名称、或是所给予的文件名为"-"&#xff0c;则wc指令会从标准输入设备读取数据。 语法 wc [-clw][--help][--version][文件...] 参数 -c或--bytes或--chars …

There is no Citrix MetaFrame server configured on the specified address错误的解决方法

环境:windows server 2003 enterprise Citrix MetaFrame XP Server for Windows with Feature Release 3MetaFrame XP 1.0 Service Pack 4 for Windows 2003公网IP内网IP(有防火墙) 客户端:windows xp sp2Citrix MetaFrame Program Neighborhood Version 9.00.32649 错误描述:使…

Cisco交换机解决网络蠕虫病毒***问题

Cisco交换机解决网络蠕虫病毒***问题今年来网络蠕虫泛滥给ISP和企业都造成了巨大损失&#xff0c;截至目前已发现近百万种病毒及***。受感染的网络基础设施遭到破坏&#xff0c;以Sql Slammer为例&#xff0c;它发作时会造成丢包率为30%。我们如何在LAN上防范蠕虫&#xff1f;大…

实验4  数据的安全性管理

实验4 数据的安全性管理 一、实验目的 掌握SQL Server身份验证模式。掌握创建登录账户、数据库用户的方法。掌握使用角色实现数据库安全性的方法。掌握权限的分配。 二、实验内容 1、设置身份验证模式&#xff1a;Windows身份验证模式和混合模验证模式。 2、设置登录账户 …