[design-patterns]设计模式之一策略模式

设计模式

从今天开始开启设计模式专栏,我会系统的分析和总结每一个设计模式以及应用场景。那么首先,什么是设计模式呢,作为一个软件开发人员,程序人人都会写,但是写出一款逻辑清晰,扩展性强,可维护的程序就不是那么容易做到了。现实世界的问题复杂多样,如何将显示问题映射到我们编写的程序中本就是困难重重。另一方面,软件开发中一个不变的真理就是“一切都在变化之中”,这种变化可能来自于程序本身的复杂度,也可能来自于客户不断变化的需求,这就要求我们在编写程序中一定要考虑变化的因素,将变化的因素抽离出来,设计出一款低耦合的程序就成了程序设计中的难点。

设计模式就是用来解决以上的这些问题的一种方法。应用好设计模式,可以让我们免受程序设计中的变化的苦恼。从设计初至今,设计模式不是一成不变的,也并不是库,它比库更加高级,而且并不是用来解决某一种特定的问题,而是广泛的面向我们在程序开发中遇到的困境。在我们学习完一门语言的相关语法后,我相信设计模式应该作为紧接着学习的重点,它会让你对相关语法的认识更加深刻。比如说java中的接口就是一例,接口是让java流行的重要原因,但是初学完接口的使用,我们并不能很好的掌握接口,只有应用好设计模式,接口才能够大放异彩,我们也才能够真正的体会到接口给面向对象设计带来的魔力。实际上,在很多流行的开源库或者框架中,我们可以看到大量的使用设计模式的例子。

在网络中,有关于设计模式的讲解不在少数,在这里我并不会引出复杂的定义,而是以例子作为基础,道出我们在软件开发中面临的问题,然后引出设计模式,看看设计模式是如何帮助我们解决这些问题的。在之后的讲解中,我都会以java为主要语言,因为它的语法足够简单,使用其他语言作为主语言的开发者触类旁通即可。

最后,关于学习设计模式,希望大家能看一些经典的书籍,不要以其他网路上的博客(包括我的)做抓手学习。毕竟别人嚼过的不香,只有自己品味才能真正理解。书籍最经典的莫过于四人组(经常简称为GoF,即Group of Four)的《设计模式:可复用面向对象软件的基础》,初学可以看《Head First设计模式》,另有一本《大话设计模式》是国人写的评价也很高,同样适合初学者。

啰嗦了这么多,接下来开始步入正题。

设计模式之一策略模式

现在的你,在一家游戏开发公司供职。某一天,你的上司让你开发出一套简单的游戏,游戏中有多个角色,每个角色可以使用武器进行攻击。你打算先以角色入手,很快你设计出了以下的模型:

但是很快,你就发现这样设计并没有一开始想象的那么美好,因为你的上司让你给角色新增一个聊天功能,这时候问题出现了。

你很快想到因为所有角色都继承于Role,所以给Role添加一个聊天方法是理所应当的:

但是这个时候,你的同事告诉你不要这样做,因为他之前设计了一些怪物模型,同样继承于Role类,很明显,怪物也可以使用武器战斗,但是他们不能参与聊天。

你很郁闷,你的同事继承了你的类却没有告诉你,不过你告诉他不用大惊小怪,只要在怪物中重写聊天方法,让他们什么都不做就好了:

但是你的同事告诉你不要逗他,因为他写的怪物少说已经有了二十几个,你不能残忍的让你的同事挨个去重写chat方法。

当你们正在商讨解决方案时,你的上司告诉你,现在角色的武器太单一了,也就是说你不能把所有的战斗实现都放在父类中,我们需要更加多样化的武器。

你很苦恼,但是作为有经验的开发人员,你很快想到可以采用接口去实现,将所有的动作都抽象成一个个接口,让子类实现需要的接口,这样可以解决上司的需求和同事的问题:

但是,这一次是你自己将自己否定了,因为你的角色设计已经进行了很多,你不想将每个角色的方法都重新实现一遍,而且,这些武器很多都是重复的,作为受过良好的OO设计课程的开发人员,你不会强迫自己去写这些重复的代码。最重要的是,你的开发经验告诉你,这些武器很可能会发生改变,如果这样设计,当发生变化时,你就不得不去检查每一个角色类,并修改其中的代码。同样的,你的同事也同意你的看法。

设计原则

现在,你迫切的希望有一种设计模式能够解救你于水火之中。不过在揭晓这个设计模式之前,我们先回到原点,看一下我们遇到的问题到底是由什么原因造成的。

我们可以看到,继承不能很好的解决我们的问题,因为角色的行为是不断发生变化的,他们使用不同的武器和技能,并且你的上司很可能会不断地要求你添加新的行为。接口看起来不错,但是接口不具有实现代码,无法做到代码的复用,这意味着如果你想将某一种行为做统一的变化,就需要一个一个类去检查方法的实现。

要解决这个问题,首先我们需要明确一些设计原则,掌握了这些设计原则,我们才能更好的运用设计模式,解决问题。

面对以上的情况,我们有一个原则正好适用,那就是“封装变化”:

找出应用中可能需要变化的地方,把它们独立出来,不要和那些固定的代码混在一起。

那么对于当前的应用来讲,战斗以及聊天功能都是变化的功能,我们就应该将他们独立出来,分别设计战斗功能和聊天功能。接下来就是如何实现这两个功能了。这又会用到一个设计原则,那就是“接口编程”:

针对接口编程,而不是针对实现编程。

相信这个原则大家都非常清楚,我就不多讲了,接下来直接上代码,这里我只分析战斗功能,其他功能也是类似的:

设计实现

首先是接口设计:

1 public interface IFight {
2     void fight();
3 }

这个接口很简单,只是提供了fight方法,接下来我们实现几个用各种武器战斗的类:

 1 public class FightUseAxe implements IFight {
 2     @Override
 3     public void fight() {
 4         System.out.println("使用斧子战斗");
 5     }
 6 }
 7 ===============================================
 8 public class FightUseBlade implements IFight {
 9     @Override
10     public void fight() {
11         System.out.println("使用剑战斗");
12     }
13 }
14 ===============================================
15 public class FightUseKnife implements IFight {
16     @Override
17     public void fight() {
18         System.out.println("使用匕首战斗");
19     }
20 }

注意上面的代码不能写在一个同一个文件中。

接下来我们对角色类进行重构:

 1 public abstract class Role {
 2
 3     private IFight weapon;
 4
 5     public void fight() {
 6         weapon.fight();
 7     }
 8
 9     public void setWeapon(IFight weapon) {
10         this.weapon = weapon;
11     }
12
13     public abstract void display();
14 }

父类中我们实现了fight()方法,做到了统一管理,但是具体方法的实现实际上是交给子类去完成了,也就是IFight属性的赋值是在子类中完成的。接下来我们来看其中一个子类:

 1 public class King extends Role {
 2     @Override
 3     public void display() {
 4         System.out.println("显示国王的样子");
 5     }
 6
 7     public static void main(String[] args) {
 8         Role role = new King();
 9         role.display();
10         role.setWeapon(new FightUseAxe());
11         role.fight();
12         role.setWeapon(new FightUseBlade());
13         role.fight();
14     }
15     /**
16      * 运行结果:
17      * 显示国王的样子
18      * 使用斧子战斗
19      * 使用剑战斗
20      */
21 }

我们可以看到,通过将战斗独立出来,我们实现了使用者(King)和武器的松耦合,即我们可以动态的改变角色使用的武器。同样的对于其他的角色也是一样,实际上,这是一种方法的委托,角色类不再实现战斗的具体方式,而是交给了成员属性IFight去执行。从另一方面来说,这是一种组合的概念,它和继承不同的地方在于,角色的战斗方式并不是继承而来的,而是和适当的战斗类来组合而成。在这个实例中,我们可以明显的看到组合的优势明显大于继承。

同样,这是一个很重要的设计原则,“多用组合,少用继承”。

其实,从更加抽象的角度来讲,例子中的战斗方式实际上是一种算法,通过策略模式,我们分离了使用算法的角色和算法之间的联系。因此我们可以给出策略模式的定义:

策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

总结

今天我们学习了第一个设计模式即策略模式,当我们面对一些程序中可能会频繁变化的部分时可以采用此模式,它让算法的实现独立于使用算法的客户。同时我们还学习了三个面向对象设计中的重要原则:

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,而不针对实现编程

最后,为了巩固我们的学习成果,下面我们思考这样一个问题。在一片草原中,一群游牧民族养了一群羊和一群马,也就是说在这个草原中有三种生物,分别是人、马、羊、他们的动作有吃(eat)和睡(sleep),不过吃和睡的方式不同,马是站着吃站着睡,羊是站着吃趴着睡,人是坐着吃躺着睡。那么我们该怎样设计该OO模型,在实现功能的基础上,达到低耦合的要求,满足不断变化的需求呢?

时间: 2024-08-05 15:21:09

[design-patterns]设计模式之一策略模式的相关文章

设计模式之策略模式(Strategy)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

Head First 设计模式之一 策略模式

策略模式 定义 策略模式定义了算法族,分别封装起来,让他们之间可以相互转换,此模式让算法的变化独立于使用算法的客户. 实例 上面的定义看起来说的不太清楚,记定义无意义,理解策略模式还是要看书中的鸭子例子.假设设计一个模拟鸭子的游戏,鸭子的种类有很多,有红头鸭.绿头鸭等等,鸭子可以划水,可以呱嘎叫.在这个模拟游戏的实现上,自然会想到用继承的方法,定义一个鸭子基类,具体的鸭子类型继承自鸭子基类.如下图所示 所有鸭子都会飞.呱呱叫和游泳,这些功能由基类来实现,display函数用来输出鸭子实例的类型,

JavaScript设计模式之策略模式(学习笔记)

在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composite).所以我今天选择学习策略模式. 策略模式:定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间可以相互替换,独立于使用算法的客户. 通常我并不会记得“牛顿第一定律”的具体内容,所以我也难保证我会对这个定义记得多久……用FE经常见到的东西来举个例子说明一下: $("div").animation(

大话设计模式_策略模式(Java代码)

策略模式:定义算法家族,分别封装,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户 简单描述:一个父类,多个子类实现具体方法.一个Context类持有父类的引用(使用子类实例化此引用),客户端代码只需要与此Context类交互即可 大话设计模式中的截图: 例子代码: 策略类: 1 package com.longsheng.strategy; 2 3 public abstract class Strategy { 4 5 public abstract double getR

设计模式之策略模式20170720

行为型设计模式之策略模式: 一.含义 策略模式是一种比较简单的模式,也叫做政策模式,其定义如下: 定义一组算法(可抽象出接口),将每个算法都封装起来,并且使它们之间可以互换(定义一个类实现封装与算法切换) 二.代码说明 1.主要有两个角色 1)Context封装角色 它也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略,算法的直接访问,封装可能存在的变化. 2)策略角色 该类含有具体的算法 2.在用C实现过程中也是参考这种思想,以压缩,解压算法举例,具体实现如下: 1)策略模式使用场景

<二>读<<大话设计模式>>之策略模式

又和大家见面了,能够坚持写出第二篇文章真不错,好好加油. <<大话设计模式>>讲解策略模式是以商场收银软件程序开头的,那么问题来了,哪家商场收银软件强,开玩笑了.读过上篇文章<<简单工厂模式>>的知道,它有两个缺点:1.客户端依赖两个类,耦合性高:2.如果算法过多则需要写很多类.解决上面问题的就是策略模式了. 策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户. 商场收银软件:单价*打折算法=售价.

设计模式之策略模式C++实现

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. 策略模式UML图如下: 举例: 游泳池中有不同种类的鸭子,有绿头鸭,红头鸭,橡皮鸭,木头鸭等.不同鸭子的特征或行为不同.绿头鸭(MallardDuck)可以叫声是"quack",会飞:橡皮鸭叫声是"queak",不会飞:木头鸭不会叫,也不会飞.利用面向对象原理来设计来泳池中的各种鸭.要求:1.可扩展性好,当有新鸭加入时或鸭的行为有变动时,不用大量改动代码:2.复用性

如何让孩子爱上设计模式 ——14.策略模式(Strategy Pattern)

如何让孩子爱上设计模式 --14.策略模式(Strategy Pattern) 描述性文字 本节讲解的是行为型设计模式中的第一个模式: 策略模式, 这个模式非常简单,也很好理解. 定义一系列的算法,把每个算法封装起来,并使得他们可以相互替换, 让算法独立于使用它的客户而变化. 一般用来替换if-else,个人感觉是面向过程与面向对象思想的 过渡,这里举个简易计算器的栗子,帮助理解~ 普通的if-else/switch计算器 普通的面向过程if-else简易计算器代码如下: 运行结果如下: 这里我

【设计模式】策略模式(经过改进的)

和前文讲到的经过改进的简单工厂模式类似,这里把策略模式和简单工厂模式结合起来. 改进的关键是改写[TodoContext]上下文类,要在该类中产生对象([干活]类的动态类型),对外提供的干活函数不变. 如下: 最大的不同就是CTodoContext类的构造函数参数不同了,构造函数成为类似简单工厂模式类的参数, 构造函数根据此参数去实例化不同的子类 //[ToDo上下文]类 class CTodoContext { public: //构造函数的参数为一个[活的名字] //构造函数根据这个名字去实