有很多同學都問我,老師是不是學了程式就能做魔獸了?呵呵,怎麼回答呢,也是也不是吧。魔獸大家都玩過,3C的比賽一直關注度都是最高的,暴雪在商業運作上經常也是搞一些高額獎金的比賽來吸引觀眾的眼球。不過對於這個問題我也在想,要是做個魔獸的遊戲該怎麼做呢?大的方面不說,先來看看如果來設計農民呢。
首先我們會想到,農民應該有基本的幾項功能:
Display ——顯示外觀
Excavate——採集挖掘
Move ——移動
Stop ——停止移動
這樣我們可以設計出一個抽象類別,只是Display方法需要也設定成abstract的,因為我們要設定的人族(Human)、獸族(Beast)、精靈族(Fairy)和不死族(Undeath)的農民外觀是不一樣的。需要分別實現。
圖1
突然想到一個問題,農民應該加入基本的進攻能力——Attack!很容易實現啊:我們在基類裡加入了這個方法即可。可是,有個問題,這樣Fairy也具有了進攻的能力,這樣不行,因為事實上Fairy是不能具有進攻能力的!是啊,使用類的繼承方式是有點問題了,一旦要對基類添加一些功能那麼它的衍生類別也都具有了新的功能。這樣很OK,簡單的改進影響到全域;但是很不幸,不應該具有此功能的子類也不能倖免新功能的添加。
public abstract class Farmer
{
public void Excavate(){ }
public void Move(){ }
public void Stop(){ }
/// <summary>
/// 顯示
/// </summary>
/// <remarks>顯示外觀</remarks>
abstract public void Display();
public virtual void Attack()
{
Console.WriteLine(”Fire!Attack U!”);
}
}
當然,我們可以在子類裡可以覆蓋定義此方法。
public class Fairy : Farmer
{
public override void Display()
{
Console.WriteLine(”Fariy:…….”);
}
public override void Attack() {
//啥也不寫了
}
}
說是如此,但是我們已經嗅到這裡會有問題了:
1、也許以後我們為了遊戲的發展還有其他的種族加入進來,那麼可能那個種族的農民也不能有Attack方法,我們還要進行重寫。
2、更重要的問題是,現在是我們加入了Attack這個方法,也許還會有許多其他的方法加入進來,而這個方法不能出現在所有的子類。
說穿了,使用繼承並不能很好的解決問題,因為農民的行為在子類裡不斷的變化,並且讓所有的子類都有這些行為是不恰當的。
找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。這個“封裝變化”的思想幾乎是每個設計模式背後的精神所在。
好了,我們這裡需要變化的地方就是Attack,它不是跟隨每個類都存在的。好了,獨立它!並且我們要讓它這一個攻擊行為可以動態改變。怎麼做呢,我們需要的只是一個攻擊的行為,至於到底如何攻擊我們並不在意,所以我們把Attack提取出來,乾脆定義成一個介面:
interface IAttackBehavior
{
void Attack();
}
而基類裡呢,我們肯定要用到這個介面的引用:
public abstract class Farmer
{
internal IAttackBehavior attackbehavior;
public void Attack()
{
attackbehavior.Attack();
}
……
}
至於,到底要如何進行攻擊呢,那隻需要實現這個介面即可。比如,我們先來做兩個行為實現:用武器進攻和不進攻。
圖2
/// <summary>
/// 用武器進攻
/// </summary>
class AttackWithWeapon : IAttackBehavior
{
#region IAttackBehavior 成員
public void Attack()
{
Console.WriteLine(”Hey, Fire! Attack!!!”);
}
#endregion
}
/// <summary>
/// 不進攻
/// </summary>
class NoAttack : IAttackBehavior
{
#region IAttackBehavior 成員
public void Attack()
{
Console.WriteLine(”I can NOT attack U! But i can burn your power!”);
}
#endregion
}
這樣的思想既是針對介面編程,而不是針對實現編程。
好了,我們來修改一下精靈(Fairy)的進攻行為了,它是不能進攻的,所以實現不能進攻的行為類就好了:
public class Fairy : Farmer
{
public Fairy()
{
base.attackbehavior = new NoAttack();
}
public override void Display()
{
Console.WriteLine(”Fariy:…….”);
}
}
而人族的農民呢,則是用個榔頭來進攻的:
public class Human : Farmer
{
public Human()
{
base.attackbehavior = new AttackWithWeapon();
}
public override void Display()
{
Console.WriteLine(”Human:work work.”);
}
}
同樣的道理應用於獸族:
public class Beast : Farmer
{
public Beast()
{
base.attackbehavior = new AttackWithWeapon();
}
public override void Display()
{
Console.WriteLine(”Beast:Ooo~~~Right”);
}
}
到這裡突然發現一個問題,不死族的農民是徒手進攻的啊!嗯,這裡看出重構代碼後應用了模式的好處,我們可以再來實現一個徒手進攻的行為就OK了:
class AttackWithoutWeapon : IAttackBehavior
{
#region IAttackBehavior 成員
public void Attack()
{
Console.WriteLine(”Haha, Beat!!!”);
}
#endregion
}
嗯,好了,可以讓不死族的農民進攻了!
public class Undeath : Farmer
{
public Undeath()
{
base.attackbehavior = new AttackWithoutWeapon();
}
public override void Display()
{
Console.WriteLine(”Undeath:some more.”);
}
}
調用一下看看:
public static void Main()
{
Console.WriteLine(”Hello, Design Patterns>>>>\r\n”);
Human h = new Human();
h.Display();
h.Attack();
Beast b = new Beast();
b.Display();
b.Attack();
Fairy f = new Fairy();
f.Display();
f.Attack();
Undeath u = new Undeath();
u.Display();
u.Attack();
Console.Read();
}
最終的效果呢:
Hello, Design Patterns>>>>
Human:work work.
Hey, Fire! Attack!!!
Beast:Ooo~~~Right
Hey, Fire! Attack!!!
Fariy:…….
I can NOT attack U! But i can burn your power!
Undeath:some more.
Haha, Beat!!!
哈哈,真不錯了。
圖3
如果我們要再添加一個種族呢?如果新種族的農民在進攻上有特殊的要求呢?做這些改變我們又要改動哪裡呢?很困難嗎?
哇喔,重構了代碼後,這些要求不會修改我們之前的所有代碼,只是添加新的類和行為實現就好了!太酷了!!!