Java设计模式之模板模式【通过LOL选英雄案例】

初衷

设计模式(Design Pattern)引用百度百科中的一句话,就是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。实际上在我们的实际编码中到处都有设计模式的影子,比如最常用的单例模式,工厂模式,代理模式,观察者模式等等。其实每种设计模式都有自己的用法和体系,它让代码编写实现真正的工程化,如果使用得当会极大的优化我们的编码效率和规范。所以对于每一个软件工程师来说,掌握几种常用的设计模式已经变得必不可少。
博主之前也学过很多设计模式,但是过一段时间很快又忘了,因为这个东西本身比较抽象,加上之前学的时候只是理解了一个概念和看了一些文章,而且工作中实际用的频率不是很高。后来楼主顿悟,“纸上得来终觉浅,绝知此事要躬行”,要靠自己总结的才是记忆最深刻的。而且有些设计模式是可以通过一些场景来加深记忆的,更重要的,能增加学习的趣味性。面向对象,源于生活,又高于生活。

什么是模板模式?(Template Pattern)

官方说法:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
通俗说法:将程序的运行逻辑以及算法逻辑交由父类进行管理,子类只需要实现其中功能模块即可。
精简说法父类实现算法,子类实现细节
模板模式其实是一种很简单的设计模式,通俗的理解就是我规定一套模板或者方法,你可以通过这套模板或者方法实现不一样的东西,但是前提是你必须按照我的规定来。就相当于父类规定一套算法逻辑,子类实现父类中的抽象方法,但是你具体成型的时候实现方式的必须按父类的算法逻辑来,当然,我们要记住它的核心思想:父类实现算法,子类实现细节!

通过LOL选择英雄初始化过程来模拟模板模式

  1. 场景描述
    相信大家几乎都玩过电子竞技类游戏,什么dota,王者,LoL呀。如果没有玩过的小伙伴也没关系,这里只是一个例子,很好理解的。博主想了一下,就用LOL初始化英雄时作为模板模式的案例。假如我们进入游戏按以下顺序步骤进行,首先是选择自己喜欢的英雄,然后配置相应的天赋符文,再选择相应的召唤师技能,这些都是必须的步骤。最后如果有皮肤的小伙伴们可以选择自己拥有的皮肤,这个是可选步骤(这里我们先不说,后面扩展再深入)。

  2. 类图结构
    Java设计模式之模板模式【通过LOL选英雄案例】_第1张图片
    这里的类图并没有严格按照类图的要求来画,主要是考虑便于理解。

  3. 代码实现

初始化英雄抽象类

package com.soft.chapter8;

/**
 * LOL选择英雄并配置相应属性的过程
 *
 * @author zxlei1
 * @date 2018/11/15 17:17
 */
public abstract class LolGameHeroSelect {

    /**
     * 选择英雄
     */
    protected abstract void selectHero();

    /**
     * 选择天赋和符文
     */
    protected abstract void selectTalnet();

    /**
     * 选择召唤师技能
     */
    protected abstract void selectSkill();

    /**
     * 模板方法,不能被继承
     */
    public final void initGame() {
        selectHero();
        selectTalnet();
        selectSkill();
    }
    
}

选择英雄寒冰射手的初始化类

package com.soft.chapter8;

/**
 * 选择英雄寒冰射手并配置相应属性
 *
 * @author zxlei1
 * @date 2018/11/15 18:41
 */
public class AsheHeroSelect extends LolGameHeroSelect{

    @Override
    protected void selectHero() {
        System.out.println("你已经选择并锁定了英雄寒冰射手艾希!");
    }

    @Override
    protected void selectTalnet() {
        System.out.println("你已经配置了ADC通用符文天赋!");
    }

    @Override
    protected void selectSkill() {
        System.out.println("你已经选择了召唤师技能闪现和治疗!");
    }

}

选择英雄德玛西亚之力的初始化类

package com.soft.chapter8;

/**
 * 选择英雄德玛西亚之力并配置相应属性
 *
 * @author zxlei1
 * @date 2018/11/15 18:46
 */
public class GarenHeroSelect extends LolGameHeroSelect {

    @Override
    protected void selectHero() {
        System.out.println("你已经选择并锁定了英雄德玛西亚之力盖伦!");
    }

    @Override
    protected void selectTalnet() {
        System.out.println("你已经配置了上单通用符文天赋!");
    }

    @Override
    protected void selectSkill() {
        System.out.println("你已经选择了召唤师技能闪现和点燃!");
    }
}

测试类

package com.soft.chapter8;

/**
 * 选择英雄测试类
 *
 * @author zxlei1
 * @date 2018/11/15 18:51
 */
public class SelectHeroTest {
    public static void main(String[] args) {
        LolGameHeroSelect heroSelect = new AsheHeroSelect();
        heroSelect.initGame();

        System.out.println("-------------------------------------");

        heroSelect = new GarenHeroSelect();
        heroSelect.initGame();
    }

}

打印结果

你已经选择并锁定了英雄寒冰射手艾希!
你已经配置了ADC通用符文天赋!
你已经选择了召唤师技能闪现和治疗!
-----------------------------
你已经选择并锁定了英雄德玛西亚之力盖伦!
你已经配置了上单通用符文天赋!
你已经选择了召唤师技能闪现和点燃!

以上就是通用的模板模式的写法,即父类实现算法,子类实现细节。

模板模式的细节及注意点

  • 为什么实现模块的抽象方法都是 protected 的呢?
    因为我们不想让调用者关注到我们实现的细节,这也是面向对象思想封装的一个体现;
  • 为什么 initGame()方法是不可继承(final关键字修饰)的呢?
    因为算法一旦确定就不允许更改,更改也只允许算法的所有者也就是他的主人更改,如果调用者都可通过继承进行修改,那么算法将没有严谨性可言;

模板模式的优缺点

优点:
1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。

缺点:
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

模板模式的拓展

前面我们说过,选择完召唤师技能后如果有皮肤的小伙伴们说不定还会选择他们所喜爱的皮肤,而没有皮肤的小伙伴们则只能使用默认皮肤了。所以这里涉及一个拓展性的问题,即实现父类模板的子类表现行为可能有差异。那么怎么去用模板模式实现呢?这里要用到一个钩子方法,其实很简单,就是加一个boolean类型的判断方法去判断它是否执行就可以了,下面完善下我们的程序。

初始化英雄抽象类

package com.soft.chapter8;

/**
 * LOL选择英雄并配置相应属性的过程
 *
 * @author zxlei1
 * @date 2018/11/15 17:17
 */
public abstract class LolGameHeroSelect {

    /**
     * 选择英雄
     */
    protected abstract void selectHero();

    /**
     * 选择天赋和符文
     */
    protected abstract void selectTalnet();

    /**
     * 选择召唤师技能
     */
    protected abstract void selectSkill();

    /**
     * 选择英雄皮肤
     */
    protected abstract void selectSkin();

    /**
     * 是否拥有皮肤
     *
     * @return
     */
    protected boolean isHaveSkin() {
        return false;
    }

    /**
     * 模板方法,不能被继承
     */
    public final void initGame() {
        selectHero();
        selectTalnet();
        selectSkill();
       if (isHaveSkin()) {
            selectSkin();
        }
    }

}

选择英雄寒冰射手的初始化类

package com.soft.chapter8;

/**
 * 选择英雄寒冰射手并配置相应属性
 *
 * @author zxlei1
 * @date 2018/11/15 18:41
 */
public class AsheHeroSelect extends LolGameHeroSelect{

    @Override
    protected void selectHero() {
        System.out.println("你已经选择并锁定了英雄寒冰射手艾希!");
    }

    @Override
    protected void selectTalnet() {
        System.out.println("你已经配置了ADC通用符文天赋!");
    }

    @Override
    protected void selectSkill() {
        System.out.println("你已经选择了召唤师技能闪现和治疗!");
    }

    @Override
    protected void selectSkin() {

    }

    @Override
    protected boolean isHaveSkin() {
        return super.isHaveSkin();
    }
}

选择英雄德玛西亚之力的初始化类

package com.soft.chapter8;

/**
 * 选择英雄德玛西亚之力并配置相应属性
 *
 * @author zxlei1
 * @date 2018/11/15 18:46
 */
public class GarenHeroSelect extends LolGameHeroSelect {

    private boolean isHaveSkin=true;

    @Override
    protected void selectHero() {
        System.out.println("你已经选择并锁定了英雄德玛西亚之力盖伦!");
    }

    @Override
    protected void selectTalnet() {
        System.out.println("你已经配置了上单通用符文天赋!");
    }

    @Override
    protected void selectSkill() {
        System.out.println("你已经选择了召唤师技能闪现和点燃!");
    }


    @Override
    protected void selectSkin() {
        System.out.println("你已经选择了暴力德玛皮肤!");
    }

    @Override
    protected boolean isHaveSkin() {
        return isHaveSkin;
    }
}

测试类

package com.soft.chapter8;

/**
 * 选择英雄测试类
 *
 * @author zxlei1
 * @date 2018/11/15 18:51
 */
public class SelectHeroTest {
    public static void main(String[] args) {
        LolGameHeroSelect heroSelect = new AsheHeroSelect();
        heroSelect.initGame();

        System.out.println("-------------------------------------");

        heroSelect = new GarenHeroSelect();
        heroSelect.initGame();
    }

}

打印结果

你已经选择并锁定了英雄寒冰射手艾希!
你已经配置了ADC通用符文天赋!
你已经选择了召唤师技能闪现和治疗!
-------------------------------------
你已经选择并锁定了英雄德玛西亚之力盖伦!
你已经配置了上单通用符文天赋!
你已经选择了召唤师技能闪现和点燃!
你已经选择了暴力德玛皮肤!

致此,我们的模板模式的东西基本已经挖掘的差不多了,加上钩子方法的模板模式才更加完美。

你可能感兴趣的:(java,设计模式)