奇幻RPG(人物構建 與 Abstract Factory模式)

Bacer發表於2021-09-09

奇幻RPG(人物構造 與 Abstract Factory模式)

引言

在前一節,我們介紹了Strategy模式,並使用此模式實現了一個根據角色的職業來分配技能的範例(實際也就是動態地為類分配方法)。作為一款奇幻RPG,有了職業,我們還應當可以為角色選擇種族,比如說:人類(Human)、精靈(Elf)、矮人(Dwarf)、獸人(Orc)等等。而這四個種族又有著截然不同的外形,精靈皮膚灰白、有著長長的耳朵、沒有體毛和鬍鬚;矮人的皮膚與人類近似,但是身材矮小、通常留著濃密的鬍子;獸人則有著綠色的皮膚和高大的身軀,並且面目醜陋。本文將討論如何使用GOF的Abstract Factory抽象工廠來實現這樣的角色外形設計。

面向實現的方式

簡單起見,我們假設角色身體由三部分構成,分別是:頭(Head)、身材(Stature)、皮膚(Skin)。那麼對於人類的構造,我們的第一反應自然而然地想到:它應當由 HumanHead、HumanStature、HumanSkin三個類複合而成。對於精靈也是類似的設計,於是,我們實現了下面這樣的設計(本文將僅以人類和精靈為例介紹):

圖片描述

抽象組成身體的實體類

我們發現這樣做,每個角色與他的身體部件是牢牢繫結在一起的,每建立一個角色,我們都需要為它先行建立所有其複合的類(組成身體的實體類(Concret Class),比如HumanHead)。按照物件導向的思想,我們想到應該對這一過程進行封裝,將建立角色部件這件事委派給其他的類來完成。觀察上圖,我們發現儘管角色不同,但它們都是由三個部分構成,所以,我們所能想到的實現這一過程的第一步,就是對組成身體的實體類進行抽象,我們定義三個介面:Head、Stature、Skin,代表身體的三個部分,並且讓Human和Elf的實體類去實現這個介面:

圖片描述

觀察上圖,我們發現儘管定義了介面,但是如果角色Human和Elf仍然與介面的實體類關聯,那麼效果與面向實現完全相同。現在,是時候對設計做些改動,我們讓Human和Elf與介面關聯,而不是與介面的實現關聯(OO思想:面向介面程式設計,而不是面向實現程式設計)。這一次,我們的設計變成下圖:

圖片描述

一眼望去,我們發現的第一個問題就是:Human與Elf驚人地相似,再仔細看看,我們注意到它們除了名字不同其餘的完全一樣。我們不禁思考:有必要把兩個完全一樣的類起兩個名字分別儲存麼?答案是沒有必要,我們將它們合併成一個類,起名叫Race,設計再次變成下面這樣:

圖片描述

建立工廠類

看到這裡,我們可能會想:現在結構似乎已經很完善了,我們定義了介面來解決問題,也沒有為不同的角色建立多個不同的類,而只要在Race的建構函式中為代表身體部件的變數賦不同的值,就可以建立不同種族的角色。好的,那麼我們來看看如果要建立一個Human 程式碼需要如何編寫:

Head head = new HumanHead();
Stature stature = new HumanStature();
Skin skin = new HumanSkin();
Race human = new Race(head, stature, skin);

而Race的建構函式是這樣的:

public Race(head, stature, skin){
    this.head = head;
    this.stature = stature;
    this.skin = skin;
}

我們看到,僅僅建立一個類這樣似乎太麻煩了,而且身體的部分類是角色的組成部分,為什麼它們要先於角色建立呢?

這時候,我們想到如果有一個類可以專門負責建立身體部件這件事,當我們想要建立角色的時候,將這個類傳遞給Race的建構函式就可以了。我們管建立Human身體組成部分的類稱作:HumanPartsFactory,建立Elf身體部分的類稱作ElfPartsFacotry。那麼它們應該是這樣的:

圖片描述

現在,我們再要建立一個Human,程式碼變成了這樣:

HumanPartsFactory humanFactory = new HumanPartsFactory();
Race Human = new Race(humanFactory);

相應的,我們的建構函式也需要改一改:

public Race(HumanPartsFactory humanFacotry){
    head = humanFactory.CreateHead();
    stature = humanFactory.CreateStature();
    skin = humanFactory.CreateSkin();
}

一切似乎都很好,直到我們需要建立一個Elf的時候... 我們發現Race的建構函式只能接受一個HumanPartsFactory型別的引數,為了傳遞ElfPartsFactory,我們將不得不再新增一個接受ElfPartsFactory型別的建構函式。這樣顯然不好,這一次,有了之前的經驗,我們知道我們可以透過同樣的方法來解決。

圖片描述

看到這裡,你是否能夠體會到一些“面向介面”程式設計的意味?注意到RacePartsFactory,它內部的方法返回的都是介面型別,而其實體子類的方法返回的都是介面的實體類。如果我們之前不宣告那看似無用的介面,這裡是無法實現的。

現在,我們再次修改Race的建構函式:

public Race(RacePartsFactory raceFacotry){
    head = raceFacotry.CreateHead();
    stature = raceFacotry.CreateStature();
    skin = raceFacotry.CreateSkin();
}

當我們需要一個Human的時候:

RacePartsFactory humanFactory = new HumanPartsFactory();
Race human = new Race(humanFactory);

當我們需要一個Elf的時候:

RacePartsFactory elfFactory = new ElfPartsFactory();
Race elf = new Race(elfFactory);

Abstract Factory設計模式

上面做的這些,使我們又完成了一個設計模式:Abstract Factory。它的正式定義是這樣的:提供一個介面用於建立一系列相互關聯或者相互依賴的物件,而不需要指定它們的實體類。

下面是本例中 Abstract Factory模式的最終圖:

圖片描述

程式碼實現和測試

using System;
using System.Collections.Generic;
using System.Text;

namespace AbstractFactory {

    // 定義構成身體部分的介面
    public interface IHead {  string name { get;} }
    public interface IStature {  string name { get;} }
    public interface ISkin {  string name { get;} }

    // 組成 Human 的類
    public class HumanHead : IHead { public string name { get { return "Human Head"; } } }
    public class HumanStature : IStature { public string name { get { return "Human Stature"; } } }
    public class HumanSkin : ISkin { public string name { get { return "Human Skin"; } } }

    // 組成 Elf 的類
   public class ElfHead : IHead { public string name { get { return "Elf Head"; } } }
    public class ElfStature : IStature { public string name { get { return "Elf Stature"; } } }
   public class ElfSkin : ISkin { public string name { get { return "Elf Skin"; } } }

    // 定義工廠介面
    public interface IRacePartsFactory {
       IHead CreateHead();
       ISkin CreateSkin();
       IStature CreateStature();
    }

    // 定義Human身體的工廠類
    public class HumanPartsFactory : IRacePartsFactory {
       public IHead CreateHead() {
           return new HumanHead();
       }
       public IStature CreateStature() {
           return new HumanStature();
       }
        public ISkin CreateSkin() {
           return new HumanSkin();
       }
    }

    // 定義Elf身體的工廠類
    public class ElfPartsFactory : IRacePartsFactory {
       public IHead CreateHead() {
           return new ElfHead();
       }
       public IStature CreateStature() {
           return new ElfStature();
        }
       public ISkin CreateSkin() {
           return new ElfSkin();
       }
    }
   
    // 定義 Race 類
    public class Race {
       public IHead Head;    // 做示範用,所以沒有構建屬性
       public IStature Stature;
       public ISkin Skin;

       public Race(IRacePartsFactory raceFactory) {
           Head = raceFactory.CreateHead();
           Stature = raceFactory.CreateStature();
           Skin = raceFactory.CreateSkin();
       }
    }

    class Program {
       static void Main(string[] args) {
           // 建立一個 精靈(Elf)
           Race Elf = new Race(new ElfPartsFactory());
           Console.WriteLine(Elf.Head.name);
           Console.WriteLine(Elf.Stature.name);          
           Console.WriteLine(Elf.Skin.name);
       }
    }
}

總結

本文中我們一步步學習了Abstract Factory抽象工廠模式的實現。

我首先介紹了我們奇幻RPG所面臨的一個問題:我們需要建立形態各異的角色。隨後,我們透過面向實現的方式來完成了這一過程,並討論了它的不足。隨後,我們先透過介面的使用對種族進行了抽象。接著,我們由將建立角色組成部分類的過程進行了封裝,將這一過程委派給了工廠類。最後,我們又對工廠類進行了抽象,最終實現了Abstract Factory工廠模式。

希望本文能給你帶來幫助。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2768/viewspace-2811728/,如需轉載,請註明出處,否則將追究法律責任。

相關文章