zoukankan      html  css  js  c++  java
  • 设计模式之一——从魔兽争霸的兵种和技能看策略模式

    很多人都喜欢玩魔兽争霸,里面的兵种很多,比如有Footman步兵、Knight骑士、Grunt兽人步兵,他们使用不同的武器,Footman步兵使用宝剑攻击、knight也使用sword宝剑攻击,grunt使用axe斧头攻击。我们如何来设计这几个兵种角色呢?

    1 初步设计

    首先我们想可以创建一个character角色类,他们都能够walk走路,但有不同的攻击行为,可以将character设计成抽象类,利用继承来得到具体的兵种。设计出来的类应该如图所示。

     

    这种设计通过继承实现各兵种,footman、knight、grunt可以实现攻击行为。我们现在想增加兵种kedo科多兽,kedo使用mouth嘴吞噬敌人,还可以用drum战鼓增加我军的攻击力,是个辅助兵种,character中没有kedo对应的攻击和辅助方法,kedo不能从character继承得到。如果想让kedo继承character,必须修改character的类结构,增加用嘴攻击的方法和用鼓辅助的方法。

    这里就能看到这个设计存在的问题,第一个是可扩展性不好,不能对适应变化的行为。第二个问题是footman和knight都用宝剑攻击,swordAttack方法在子类需要重复写代码,没有将代码复用。

    这里我们引入软件设计的一个原则,就是区分不变的和变化的,将变化的封装起来。具体到character类来说,就是walk方法是不变的,不用动。attack和assist行为是变化的,我们给封装起来。

    2 第一次改进

    将attack和assist行为封装起来,我们考虑使用接口,设计两个接口Attackable和Assistable,让footman、knight、grunt、kedo分别实现相应的接口。设计出来的类如下图所示。

     

    我们看这个设计方法,第一个可扩展的问题,如果还有其他的行为,我们可以再加入接口,这样可扩展的问题就解决了。第二个问题,代码重复的问题,由于footman和knight的swordAttack实现Attackable接口的attack方法,还是存在代码重复的问题。

    解决代码重复的问题,我们考虑将attack和assist两种行为从character中分离出来。

    3 第二次改进

    3.1 分离出行为

    我们设计两个行为的接口,AttackBehavior和AssistBehavior,子类来实现对应的attack和assist行为。接口和实现类的设计如图所示。

      

    此处使用了软件设计的一个原则,针对接口编程,不要针对实现编程。针对接口编程,可以使用面向对象的多态特性,比如一个兵种有AttackBehavior的attack行为,这个attack行为在实现的时候可以是SwordAttack,也可以AxeAttack或者MouthAttack,增加灵活性。

    3.2 整合兵种的行为

    在Character类中增加两个实例变量,分别是attackBehavior和assistBehavior,声明为接口类型。Character设计如图所示。

     

    3.3 代码实现

    以下是Character类的代码实现

    public class Character {
        AttackBehavior attackBehavior;
        AssistBehavior assistBehavior;
       
        public void walk(){
            System.out.println("I can walk!");
        }
       
        public void performAttack(){
            attackBehavior.attack();
        }
       
        public void performAssist(){
            assistBehavior.assist();
        }
    }

    以下是Kedo科多兽的主要代码

    public class Kedo extends Character {
        public Kedo(){
            attackBehavior = new MouthAttack();
            assistBehavior = new DrumAssist();
        }
    }

    我们在这里将兵种的攻击或者辅助行为单独设计为类,与兵种分离开来,这种做法让兵种和行为都是独立的个体,他们的结合方式变成了组合,而不是一开始的继承。这里用到了软件设计中一个重要的原则,多用组合,少用继承。使用组合建立的系统具有很大的弹性。

    我们的代码Kedo科多兽类,有用嘴吞噬和用战鼓辅助的行为,代码把他的行为固定了,如果可以不把他的行为固定,动态的设定他的行为,则可以增加代码的灵活性。我们可以进一步改进代码。

    3.4动态设定行为

    在Character类中,增加两个方法:

    public void setAttackBehavior(AttackBehavior atb){
        attackBehavior = atb;
    }

    public void setAssistBehavior(AssistBehavior asb){
        assistBehavior = asb;
    }

    这样我们如果实现新的兵种,比如 Raider狼骑士 ,他是用大刀(暂时用宝剑代替)砍,我们可以不创建raider这个兵种类,直接实现raider具有宝剑攻击的行为。代码如下。

    Character raider = new Character();
    raider.setAttackBehavior(new SwordAttack());
    raider.performAttack();

    4 策略模式

    现在我们回过头来再看看整个思路,兵种具有不同的攻击或辅助行为,我们把行为分离出来进行定义,这样就定义了一组行为(SwordAttack、AxeAttack、MouthAttack、DrumAssist),并把这些行为进行了封装,都封装到了类中,这些行为在地位上平等的,可以互相替代,行为独立于兵种之外,这就是策略模式的设计思路。

    我们把行为扩展到更广义的算法,一组行为我们扩展为算法簇。再把上面的设计思路说一次,就是策略模式,定义了算法簇,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

  • 相关阅读:
    Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt)
    算法七:广度优先搜索
    sqldependency 支持的select
    ghostDoct 使用 (转 http://www.cnblogs.com/RockyMyx/archive/2010/04/20/Project-Route-Using-GhostDoc.html)
    Jenkins 使用学习笔记
    C# 反射类型转换
    时间分割线:2016年1月的文章都是从以前chinaunix的博客文章
    Error 42 error C2784: 'bool std::operator <(const std::_Tree<_Traits>
    JNI的类路径问题
    【转】不用临时变量也可以交换变量的值
  • 原文地址:https://www.cnblogs.com/coodream2009/p/10540657.html
Copyright © 2011-2022 走看看