菜菜哥,又來找你了


YY妹子,沒和你男票分吧?


暫時還沒有....找你還是因為前幾天和你說的那個游戲的事


產品經理又要改?


you are right! 這次產品狗要給玩家(player)加升級機制,例如經驗值到100升級到二級


這個好辦呀,不就是修改一下任務的級別信息嗎?


麻煩的不是這里,除了級別信息變動之外,有可能還獲取到技能,比如:升到10級獲取了跳躍的技能


這樣的功能在盡量不修改源代碼的基礎上還是需要好好設計一下,來靠近一點,菜菜哥詳細和你說


如果你對問題的背景不太熟悉,不如復習一下上一篇,入口》.
這是玩家的抽象基礎類,這個設計很好,把一些玩家共有的特性抽象出來
//玩家的基礎抽象類
abstract class Player
{
//玩家的級別
public int Level { get; set; }
//其他屬性代碼省略一萬字
}
這是新加需求:10級可以跳躍,具體跳躍動作是客戶端做處理
//玩家的基礎抽象類
abstract class Player
{
//玩家的級別
public int Level { get; set; }
//其他屬性代碼省略一萬字
//新加玩家跳躍動作,由於需要到達10級所以需要判斷level
public virtual bool Jump()
{
if (Level >= 10)
{
return true;
}
return false;
}
}
這種代碼初級人員很容易犯,有什么問題呢?
1. 跳躍的動作被添加到了基類,那所有的子類就都有了這個行為,如果子類機器人玩家不需要這個跳躍的行為呢?
2. 為了新需求,修改了基類,如果每次需求都需要修改基類,時間長了,項目大了,這個是比較要命的。
由於需求是增加玩家一個行為,根據上一節的介紹,我們應該了解到,行為在代碼級別更傾向於用接口來表示。而且不是所有的玩家類型都需要附加跳躍這個行為。據此優化如下:
//玩家跳躍的行為
interface IJump
{
bool Jump();
}
//玩家的基礎抽象類
abstract class Player
{
//玩家的級別
public int Level { get; set; }
//其他屬性代碼省略一萬字
}
//真實玩家
class PersonPlayer : Player, IJump
{
public bool Jump()
{
if (Level >= 10)
{
return true;
}
return false;
}
}
不錯,到此我們已經避免了初級人員所犯的錯誤了,每種玩家類型可以根據需要自行去擴展行為,改天產品狗在加一個10級玩家可以飛的行為,頂多在加一個IFly的行為接口,然后實現即可。但是這樣的設計就沒有問題了嗎?有,當然有
1. 每次需求其實還是改動了已經存在的並且穩定運行的老代碼,這是不可取的。而且修改老代碼,大大增加了bug出現的概率。
2. 假如現在我們的游戲有20種玩家類型,其中19種需要添加跳躍的行為,那我們需要修改19個玩家的子類,工作量是如此之大。
3. 利用類似繼承的方式擴展對象的行為,是在編譯期就把對象的行為確定了。也就是說在設計層面,其實你已經把代碼寫死了。

假設以下為產品狗一個月之后的新需求:
1. 能跳躍的等級調整為11級
2. 玩家添加能遁地的行為
3. 新加了10種玩家類型
如果你讀到了這里,說明大家都是對於設計追求卓越的技術人。這里菜菜再強調一遍架構設計的一項重要原則
類應該對修改關閉,對擴展開放。
這里需要強調一點,設計的每個部分想要都遵循開放-關閉原則,通常很難做到。因為要想在不修改現有代碼的情況下,你需要花費許多時間和精力。遵循開放關閉原則,通常需要引入更多的抽象,增加更多的層次,增大代碼的復雜度。因此菜菜建議把注意力集中在業務中最有可能變化的點上,這些地方應用開放關閉原則。至於怎么確定哪些是變化的點,這需要對業務領域很強的理解和經驗了。
現在我們分析一下我們要做的事情,我們希望一個對象(player)在不改動的情況下動態的給它賦予新的行為,在業務上實現的功能和用繼承的結果類似。總之一句話:
現有的類型優雅的添加新行為,並且可以靈活疊加和替換
理想中的設計圖大致如下:
現在我們認真分析一下,如果每個新的行為要想擴展對象而又能保持該對象的自身特性,新行為對象必須是擴展對象的子類,還必須包含對象的一個引用才能實現。
1. 在系統設計過程中,實現一個接口泛指實現某個對象的超類型,也就是說可以是類或者接口。
2. 在你系統設計中,如果你的代碼依賴於某個具體的類型,並非抽象的超類型,應用此篇介紹的設計方法可能會受到影響。
3. 附加在對象最外層的行為,不應該窺視被包裝的類型內部的一些特性。
4. 附加在對象外層的行為,可以在內層對象的行為前后加入自己的行為,甚至可以覆蓋掉內層對象的行為。
5. 如果擴展的行為過多,會出現很多小對象,過度使用會使程序變的很復雜,所以設計擴展行為時候需要注意。
假設現在真實玩家的定義如下:
//玩家的基礎抽象類
public abstract class Player
{
//玩家的級別
public int Level { get; set; }
//其他屬性代碼省略一萬字
}
//真實玩家
public class PersonPlayer : Player
{
}
現在的需求是給真實玩家添加一個10級能跳躍的行為,在不修改原有玩家代碼的情況下,擴展跳躍行為代碼如下
//玩家行為的擴展積累
public class PlayerExtension : Player
{
protected Player player;
}
//跳躍玩家的行為擴展類
public class PlayerJumpExtension: PlayerExtension
{
public PlayerJumpExtension(Player _player)
{
player = _player;
}
public bool Jump()
{
if (player. Level >= 10)
{
return true;
}
return false;
}
}
測試代碼如下:
PersonPlayer player = new PersonPlayer();
//給用戶動態添加跳躍的行為
PlayerJumpExtension jumpPlayer = new PlayerJumpExtension(player);
var ret= jumpPlayer.Jump();
Console.WriteLine("玩家能不能跳躍:"+ret);
//現在玩家升級到10級了
player.Level = 10;
ret = jumpPlayer.Jump();
Console.WriteLine("玩家能不能跳躍:" + ret);
測試加過如下:
玩家能不能跳躍:False
玩家能不能跳躍:True
一個月后產品狗新加一個需求:真實玩家20級獲得飛行的行為,無序改動現有代碼,只需繼續添加一個可以飛行的新擴展
//玩家可以飛行的擴展
public class PlayerFlyExtension : PlayerExtension
{
public PlayerFlyExtension(Player _player)
{
player = _player;
}
public bool Fly()
{
if (player.Level >= 20)
{
return true;
}
return false;
}
}
測試代碼如下:
PlayerFlyExtension flyPlayer = new PlayerFlyExtension(player);
Console.WriteLine( "玩家能不能飛行"+flyPlayer.Fly());
player.Level = 20;
Console.WriteLine("玩家能不能飛行" + flyPlayer.Fly());
測試結果:
玩家能不能飛行False
玩家能不能飛行True
以上代碼級別上屬於演示代碼,但是設計的理念卻很重要。基於以上的設計思想,擴展的行為完全有能力修改,覆蓋玩家的某些行為。比如玩家對象本身有一個喊話的行為,那擴展類根據業務完全可以讓喊話行為執行兩次等等修改。