java設計模式之模板模式【通過LOL選英雄案例】
初衷
設計模式(Design Pattern)引用百度百科中的一句話,就是一套被反覆使用、多數人知曉的、經過分類的、程式碼設計經驗的總結。實際上在我們的實際編碼中到處都有設計模式的影子,比如最常用的單例模式,工廠模式,代理模式,觀察者模式等等。其實每種設計模式都有自己的用法和體系,它讓程式碼編寫實現真正的工程化,如果使用得當會極大的優化我們的編碼效率和規範。所以對於每一個軟體工程師來說,掌握幾種常用的設計模式已經變得必不可少。博主之前也學過很多設計模式,但是過一段時間又忘了,因為這個東西本身比較抽象,加上之前學的時候只是理解了一個概念和看了一些文章,而且工作中實際用的頻率不是很高。後來樓主頓悟,“紙上得來終覺淺,絕知此事要躬行”,要靠自己總結的才是記憶最深刻的。而且有些設計模式是可以通過一些場景來加深記憶的,更重要的,能增加學習的趣味性。面向物件,源於生活,又高於生活。
什麼是模板模式?(Template Pattern)
官方說法:一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行。這種型別的設計模式屬於行為型模式。
通俗說法:將程式的執行邏輯以及演算法邏輯交由父類進行管理,子類只需要實現其中功能模組即可。
精簡說法:父類實現演算法,子類實現細節 。
模板模式其實是一種很簡單的設計模式,通俗的理解就是我規定一套模板或者方法,你可以通過這套模板或者方法實現不一樣的東西,但是前提是你必須按照我的規定來。就相當於父類規定一套演算法邏輯,子類實現父類中的抽象方法,但是你具體成型的時候實現方式的必須按父類的演算法邏輯來,當然,我們要記住它的核心思想:父類實現演算法,子類實現細節!
通過LOL選擇英雄初始化過程來模擬模板模式
-
場景描述
相信大家幾乎都玩過電子競技類遊戲,什麼dota,王者,LoL呀。如果沒有玩過的小夥伴也沒關係,這裡只是一個例子,很好理解的。博主想了一下,就用LOL初始化英雄時作為模板模式的案例。假如我們進入遊戲按以下順序步驟進行,首先是選擇自己喜歡的英雄,然後配置相應的天賦符文,再選擇相應的召喚師技能,這些都是必須的步驟。最後如果有面板的小夥伴們可以選擇自己擁有的面板,這個是可選步驟(這裡我們先不說,後面擴充套件再深入)。 -
類圖結構
這裡的類圖並沒有嚴格按照類圖的要求來畫,主要是考慮便於理解。 -
程式碼實現
初始化英雄抽象類
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通用符文天賦!
你已經選擇了召喚師技能閃現和治療!
-------------------------------------
你已經選擇並鎖定了英雄德瑪西亞之力蓋倫!
你已經配置了上單通用符文天賦!
你已經選擇了召喚師技能閃現和點燃!
你已經選擇了暴力德瑪面板!
致此,我們的模板模式的東西基本已經挖掘的差不多了,加上鉤子方法的模板模式才更加完美。