http://www.tracefact.net/Design-Pattern/Strategy.aspx
引言
看过一些设计模式方面的书籍和文章,虽然很正式,很权威,(也觉得有那么一点刻板),总是觉得让人不那么好靠近。于是,我思考着像写故事一样来写下自己对设计模式的理解。我们将以一款奇幻角色扮演游戏(D&D)为蓝本,通过游戏中的模块创建或者功能实现来展示GOF的设计模式。当然,这不是一款真正意义上的游戏,只是为了了解设计模式,所以,我会尽可能的使游戏简单。废话不多说了,我们Start off吧。
继承及其问题
在开始我们的游戏之旅之前,我们需要定义玩家可以选择的角色。我们首先想到了四个角色职业:野蛮人(Barbarian)、佣兵(Soldier)、圣骑士(Paladin)、法师(Wizard)。
按照OO的思想,我们需要先定义一个抽象类作为基类,然后供这四个职业继承,以实现代码的重用。在此之前,我来分析一下角色拥有的能力(方法):
- DisplayInfo():显示角色的基本信息。(比如圣骑士:追求至善的热情、维护法律的意志、击退邪恶的力量 -- 这就是圣骑士的三件武器 ... )
- Walk():让角色行走。
- Stay():让角色站立。
一般来说,设计时会遵循这样的原则:
- 对于所有继承类都有,但是每个继承类的实现各不相同的方法,我们在基类中只给出定义,不给出实现,而在继承类中予以实现。换言之,就是在基类中定义一个抽象方法。
- 对于所有继承类都有,并且每个继承类的实现完全相同的方法,我们直接在基类中实现它,而由子类去继承,以实现代码重用。
很显然,每个角色都拥有DisplayInfo()、Walk() 和 Stay()的能力。其中,DisplayInfo()对于每个角色都不同;而Walk()和Stay()对每个角色都相同。于是,我们构建基类Charactor,实现了这样的设计:
在基类中实现的问题
到目前为止,我们的程序仅实现了四个角色样子各不相同,并且都能行走和站立。为了让角色更丰富一些,现在我们让角色可以装配武器,所以,我们需要新添一个方法,我们给它命名为 UseWeapon(),它的实现效果是角色手中拿起一把剑。我们首先想到的是可以将UseWeapon()放到基类中,这样可以实现代码的重用。
于是,我们的设计变成下图:
这样看上去很不错,我们利用了面向对象四大思想(抽象、封装、继承、多态)中的继承。可是好景不长,没过几天,我们觉得这样的角色设置有些单调,个性不鲜明,我们想要对游戏规则做如下修改:
- 野蛮人用斧。
- 法师不用武器。
这样,便引出了在本例中使用继承的问题:我们发现法师、野蛮人都可以用剑,而这不符合我们定义的游戏规则。
我们将上面的问题抽象化,得到的结论是:给基类添加实体方法,使得不应该拥有此方法的子类也拥有了此方法,也使得所有子类方法拥有了完全一样的实现。
覆盖基类方法的问题
这个问题似乎很好解决,既然野蛮人和法师不同,那我们只需要让野蛮人和法师覆盖基类的 UseWeapon() 方法就可以了,与此同时,我们将基类方法声明为 virtual 虚拟方法,并给出实现。这个实现,可以视为角色的默认实现(默认角色用剑)。
这一次,我们的设计变成了下面这样:
这样看上去似乎很好的解决了这个问题,直到有一天,我们又有了新的需求:
- 需要新添角色 战士(Warrior),他也使用斧。
- 需要新添角色 牧师(Cleric),他也不使用武器。
同时也带来了新的问题:
- 我们没有实现代码重用,对于 野蛮人 和战士,他们对UseWeapon()的实现是相同的(都用斧),但我们不得不将完全一样的代码在每个子类中都写一遍。
- 如果这个方法的实现需要经常改动,我们需要反复修改所有相关子类中的方法。
- 牧师 和 法师都不使用武器,但是他们都继承了UseWeapon()方法,即便是用一个什么都不做的(空的)UseWeapon()方法覆盖基类方法,他们仍会暴露出 UseWeapon() 的能力(可以从他们的实例中访问此方法)。
接口及其问题
我们发现继承并不是那么好用,尤其是使用继承 问题3 似乎难以解决,于是我们改变思路,这一次,我们使用接口来实现。
我们定义一个 IWeaponable 接口,然后从基类中去除UseWeapon()方法,然后对于可以使用武器的子类,实现这个接口;对于不可以使用武器的子类(牧师、法师),不去实现这个接口。
现在的设计变成了这样:
使用接口所产生的新问题远比它解决的问题多,我们首先看下它解决了什么问题:
- 牧师、法师 不再具有使用武器的能力,它们的实例也不会暴露出UseWeapon()方法。
再看它产生了哪些问题:
- 因为接口只是一个契约,而不包含实现,于是将有大量的子类需要实现此接口。
- 代码没有重用,所有用剑、用斧的角色,其UseWeapon()的实现方式都相同。如果将来需要修改,所有的相关子类都需要改动。
封装行为
到目前位置,我们在进行这个角色设计的时候,不管是使用继承还是使用接口,UseWeapon()方法要么是在基类中实现,要么是在子类中实现,我们实际上都是在面向实现编程。OO的一个原则是面向接口编程。面向实现编程的一个主要问题就是不够灵活,以本例而言,所有的角色,要么用剑,要么用斧,要么什么都不用。如果我想让同一个角色先用斧,再用剑,也就是动态地给他分配武器,是无法实现的。
OO有一个原则称作“Encapsulate what varies”,也就是俗称的“封装变化”。在我们当前的情况中,UseWeapon()这个行为是不断变化的,那么我们就应该想办法将它封装起来。这一次,我们依然要使用接口,但是实现此接口的类不再是我们定义的角色的基类或者子类,而是专用于UseWeapon()这个行为的类。如下图所示:
可以看出:我们将对接口的实现分放到了它自己的继承体系中,而不是放到我们的角色类中。每一个实现此接口的类完成一个特定的对UseWeapon()方法的实现。比如说,UseSword类的UseWeapon()实现是 拿起一把剑。UseAx类的UseWeapon实现是 拿起一把斧。而 UseNothing的实现是什么都不做,仅仅由角色发一句抱怨:I can't use any weapon。
现在我们要做的,就是将这个方法体系 与 我们的角色体系结合起来,具体如何做呢?就是在我们角色的基类中声明一个 IWeaponable 类型的变量,把它复合进去。如下图所示:
注意到 Character 类中仍有一个UseWeapon()方法,但是这个方法与之前的UseWeapon()方法不同:
- 它不是用来给子类去覆盖的。
- 我们通过Character的UseWeapon()方法实际去调用 IWeaponable 接口的UseWeapon()方法(实际上调用了其实体类的UseWeapon()方法)。
所以,它的代码大致是这样的:
public void UseWeapon(){
WeaponBehavior.UseWeapon();
}
而WeaponBehavior在使用之前是应该被赋值的,我们在子类的构造函数中去做这件事:
public Barbarian(){
WeaponBehavior = new UseAxe();
}
同时,因为各种UseWeapon的方法都被封装在了WeaponBehavior中,我们可以动态地改变它,我们给Character基类再添加一个更换武器的方法:
public void ChangeWeapon(IWeaponable newWeapon){
WeaponBehavior = newWeapon;
}
Strategy 模式
实际上,我们上面所做的一切,就完成了Strategy模式,现在让我们看看Stragegy模式的官方定义:
Strategy模式定义了一系列的算法,将它们每一个进行封装,并使它们可以相互交换。Strategy模式使得算法不依赖于使用它的客户端。
代码实现与测试
我们已经完成了设计,现在通过代码来实现整个过程,简单起见,我们只创建两个角色和三种武器技能(包含一个不能使用武器的实现),必要的说明会放在注释中。
using System;
using System.Collections.Generic;
using System.Text;
namespace Strategy {
#region 封装 UseWeapon() 行为
// 定义使用 武器接口
public interface IWeaponable {
void UseWeapon();
}
// 使用 剑 的类
public class UseSword : IWeaponable {
public void UseWeapon() {
Console.WriteLine("Action: Sword Armed. There is a sharp sword in my hands now.");
}
}
// 使用 斧 的类
public class UseAxe : IWeaponable {
public void UseWeapon() {
Console.WriteLine("Action: Axe Armed. There is a heavy axe in my hands now.");
}
}
// 不能使用武器的类
public class UseNothing : IWeaponable {
public void UseWeapon() {
Console.WriteLine("Speak: I can't use any weapon.");
}
}
#endregion
#region 定义角色类
// 角色基类
public abstract class Character {
protected IWeaponable WeaponBehavior; // 通过此接口调用实际的 UseWeapon方法。
// 使用武器,通过接口来调用方法
public void UseWeapon() {
WeaponBehavior.UseWeapon();
}
// 动态地给角色更换武器
public void ChangeWeapon(IWeaponable newWeapon){
Console.WriteLine("Haha, I've got a new equip.");
WeaponBehavior = newWeapon;
}
public void Walk() {
Console.WriteLine("I'm start to walk ...");
}
public void Stop() {
Console.WriteLine("I'm stopped.");
}
public abstract void DisplayInfo(); // 显示角色信息
}
// 定义野蛮人
public class Barbarian : Character {
public Barbarian() {
// 初始化继承自基类的WeaponBehavior变量
WeaponBehavior = new UseAxe(); // 野蛮人用斧
}
public override void DisplayInfo() {
Console.WriteLine("Display: I'm a Barbarian from northeast.");
}
}
// 定义圣骑士
public class Paladin : Character {
public Paladin() {
WeaponBehavior = new UseSword();
}
public override void DisplayInfo() {
Console.WriteLine("Display: I'm a paladin ready to sacrifice.");
}
}
// 定义法师
public class Wizard : Character {
public Wizard() {
WeaponBehavior = new UseNothing();
}
public override void DisplayInfo() {
Console.WriteLine("Display: I'm a Wizard using powerful magic.");
}
}
#endregion
// 测试程序
class Program {
static void Main(string[] args) {
Character barbarian = new Barbarian(); // 默认情况下野蛮人用斧
barbarian.DisplayInfo();
barbarian.Walk();
barbarian.Stop();
barbarian.UseWeapon();
barbarian.ChangeWeapon(new UseSword()) ; // 现在也可以使用剑
barbarian.UseWeapon();
barbarian.ChangeWeapon(new UseNothing());// 也可以让他什么都用不了,当然一不会这样:)
barbarian.UseWeapon();
}
}
}
值得注意的地方是:Strategy模式没有解决我们之前提到的问题3。法师不应该暴露出UseWeapon()的能力(NOTE:如果在VS2005下使用C#语言,当你在法师后面按下点号的时候,智能提示上应该找不到UseWeapon()方法)而不是提供一个什么都不做的UseWeapon()方法。
总结
在本文中,我们通过一个实现奇幻角色扮演游戏(RPG)的技能设计演示了设计模式中的Strategy模式。
我们首先以继承的方式来实现,然后分析了继承可能引起的问题;随后又使用接口实现了一遍,分析了使用接口会带来的问题。
最后,我们通过封装行为的Strategy模式完成了整个设计,并给出了它的定义。
希望这篇文章能对你有所帮助!
相关推荐
在本文中,我们将通过一个奇幻角色扮演游戏(RPG)的例子来解释这个模式,以便更好地理解其工作原理。 在RPG游戏中,我们有四种角色:野蛮人、佣兵、圣骑士和法师。这些角色各自有不同的特性和能力。在面向对象编程...
内容概要:本文详细介绍了污水处理厂的3D渲染高清工艺图,展示了从预处理到生化处理等多个工艺段的设备细节。不仅提供了视觉上的逼真效果,还深入探讨了背后的数字技术支持,如Python代码用于管理设备参数、Houdini的粒子系统模拟鸟类飞行以及Three.js实现实时交互展示。此外,文中通过实际案例(如老张的需求)展现了这些技术的实际应用场景。 适合人群:从事污水处理工程设计、投标工作的工程师和技术人员,对3D渲染和数字化工具有兴趣的相关从业者。 使用场景及目标:①为投标文件提供高质量的视觉材料;②利用代码实现设备参数的动态调整,满足不同工况下的展示需求;③通过Web端进行实时互动展示,增强项目沟通效果。 其他说明:随着技术的发展,传统工程行业也开始融入更多数字化元素,如虚拟现实(VR)巡检等新兴手段的应用前景广阔。
毕业论文-周边优惠卡券5.9.2小程序+前端-整站商业源码.zip
毕业论文-芸众圈子社区V1.7.8 开源版-整站商业源码.zip
毕业设计-erphpdown9.82美化版-整站商业源码.zip
毕业设计-java安卓原生影视APP源码-整站商业源码.zip
内容概要:本文详细介绍了风光储交直流微电网模型及其孤岛Vf(电压和频率)控制策略。首先阐述了风光储交直流微电网作为新型分布式能源系统的重要性和组成要素,包括风力发电、光伏发电、储能系统和交直流负荷。接着讨论了孤岛模式下微电网的Vf控制策略,强调了检测孤岛状态并及时切换到Vf控制模式的重要性。文中还具体分析了如何设定合理的电压和频率参考值,协调各能源系统的运行,以确保微电网在孤岛模式下的稳定供电。最后指出,完善微电网模型和有效实施孤岛Vf控制策略对促进可再生能源发展和能源结构调整有重大意义。 适用人群:从事新能源研究、微电网设计与运维的技术人员,以及关注可再生能源发展的科研工作者。 使用场景及目标:适用于希望深入了解风光储交直流微电网及其孤岛控制机制的专业人士,旨在提升微电网的稳定性和可靠性,推动智能电网建设。 其他说明:本文不仅提供了理论分析,还涉及实际应用场景和技术细节,有助于读者全面掌握相关技术和最新进展。
实训商业源码-美容美发营销版小程序 V1.8.4-论文模板.zip
内容概要:本文详细介绍了风光储并网协同运行模型及其双闭环控制策略,并探讨了单极调制技术在Matlab Simulink中的应用。首先阐述了风光储并网的重要性,指出风能和太阳能虽然具有无限的能源潜力和环保优势,但也存在间歇性和不稳定性的问题。接着介绍了一个整合风力发电、光伏发电和储能系统的协同运行模型,强调每个组件的精密协调与控制,以确保并网的效率和稳定性。然后解释了双闭环控制策略的作用机制,即内环对电流或电压进行快速响应控制,外环调节系统的能量平衡和输出,从而确保风电和光电的稳定输出及储能系统的合理充放电。此外,还讨论了单极调制技术的应用,它有助于优化能源转换和传输,减少能量损失,提高整体效率。最后,展示了如何使用Matlab Simulink进行仿真测试,以验证这些技术和方法的有效性。 适合人群:从事新能源领域的研究人员和技术人员,尤其是那些关注风能、太阳能和储能系统集成的人士。 使用场景及目标:适用于希望深入了解风光储并网系统的设计、控制和仿真的专业人士。目标是在实际项目中应用这些理论和技术,构建高效的风光储并网系统。 其他说明:随着技术的发展,风光储并网系统有望在未来提供更多绿色能源,解决传统能源带来的环境问题。
毕业论文-摇周边营销V2.8.0-整站商业源码.zip
2025年度小学手绘风格开学季班会模板
内容概要:本文详细介绍了单相三电平NPC逆变器的工作原理和技术特点,重点探讨了载波层叠技术以及两种主要的调制方法——SVPWM(空间矢量脉宽调制)和SPWM(正弦脉宽调制)。文中解释了这两种调制方式的基本概念、实现机制及其各自的优点和局限性,并提供了部分伪代码示例帮助理解。此外,还讨论了不同应用场景下如何选择最合适的调制策略以满足特定的需求。 适合人群:从事电力电子研究的技术人员、高校相关专业师生及对逆变器技术感兴趣的工程爱好者。 使用场景及目标:为理解和设计单相三电平NPC逆变器提供理论依据和技术指导,特别是在需要优化输出电压质量、降低谐波失真的情况下。 其他说明:文章不仅从理论上阐述了各种技术手段的作用机理,同时也给出了简单的代码片段辅助读者更好地掌握实际操作流程。
内容概要:本文详细探讨了无刷直流电机(BLDC)在无位置传感器控制下的启动特性和突加负载响应。文章首先介绍了启动阶段的大电流高转矩特性,展示了启动过程中电流尖峰现象及其原因。接着讨论了反电势观测器的设计与实现,特别是滑模观测器的应用,用于估算转子位置。此外,还深入讲解了速度环PI控制器的参数设置,确保系统在突加负载时能够快速恢复并保持稳定运行。最后提到了相位补偿的重要性以及其实现方法。 适合人群:对无刷直流电机控制系统感兴趣的工程师和技术人员,尤其是那些希望深入了解无位置传感器控制技术和MATLAB Simulink仿真的专业人士。 使用场景及目标:适用于需要优化BLDC电机性能的研究项目或工业应用,旨在提高系统的可靠性和效率,特别是在启动和负载变化的情况下。 其他说明:文中提供了具体的MATLAB代码片段,帮助读者更好地理解和实现相关算法。同时提醒了一些常见的陷阱和注意事项,有助于避免实际操作中的错误。
毕业论文-在线考试系统源码 学生教师用-整站商业源码.zip
内容概要:本文介绍了CRH380B、CW-200及209HS型轨道车辆客车转向架的关键技术和3D建模方法。主要内容涵盖转向架的装配体3D图及其关键零部件如轮轴系统、构架、制动闸片、空气弹簧和减震器的介绍。文中还展示了利用SolidWorks软件进行转向架3D建模的具体步骤,包括轮轴系统的草图绘制和构架的拉伸特征创建。此外,文章强调了构架结构强度仿真分析的重要性,并指出部分模型为简化版本,旨在帮助读者快速理解和掌握转向架的基本结构和原理。 适合人群:对轨道交通工程感兴趣的技术爱好者、学生以及从事相关领域的工程师。 使用场景及目标:适用于希望深入了解轨道车辆转向架设计和仿真的技术人员,目标是提高他们对转向架的理解并为其后续的设计优化提供理论支持。 其他说明:文中提供的代码片段仅为示例,实际建模过程中需要考虑更多细节和参数配置。同时,简化后的3D图有助于初学者快速入门,但并不适合作为精确制造的依据。
毕业设计-婚庆摄影wordpress企业主题-整站商业源码.zip
实训商业源码-聚合客服 22.7.0 PC端插件 4.9.0-论文模板.zip
内容概要:本文详细介绍了三相并网逆变器采用PQ控制和SVPWM技术进行波形优化和参数开发的过程。文中探讨了PQ控制对有功功率和无功功率的精确管理,以及SVPWM在优化开关序列、减少谐波分量方面的优势。针对750V直流侧电压、220V交流侧电压和20kHz开关频率的具体参数,作者通过仿真和实验设计,成功实现了10e3kW有功功率的控制,并确保了良好的波形质量。此外,文章还讨论了两电平和三电平拓扑的选择及其应用场景,强调了三电平拓扑在高电压和大功率应用中的优越性。 适合人群:从事电力电子、逆变器设计和控制策略研究的专业人士和技术爱好者。 使用场景及目标:适用于需要深入了解三相并网逆变器控制技术和调制方法的研发人员,帮助他们掌握PQ控制和SVPWM算法的实际应用技巧,提升逆变器性能和效率。 其他说明:文章还展望了未来的研究方向,如引入更先进的控制策略和调制技术,以及逆变器在可再生能源并网和微电网中的应用前景。
毕业设计-多商家营销活动平台2.0.0 小程序前端+后端-整站商业源码.zip
毕业设计-表白墙网站源码 带后台管理-整站商业源码.zip