22
Tags:
delphi
Skrevet af
Bruger #2730
@ 02.05.2004
De fleste rynker nok lidt på næsen når man nævner ordet object factory, for hvad er det og hvorfor skulle man bruge det? I denne artikel vil jeg svare på disse spørgsmål samt vise en konkret implementation af en object factory.
Hvad er en object Factory, og hvad bruges den til?
En object factory er, lidt som navnet antyder, en fabrik der producerer en masse objekter. Disse objektet kan så efterfølgende bruges som ganske almindelige objekter i den applikation udvikleren er ved at bygge. Man støder ofte på udtrykket object factory i forbindelse med spiludvikling hvor der er et stort behov for at loade alle de objekter der skal indgå i et spil. Hvis vi tager udgangspunkt i et kendt strategispil som 'Age of Mythology' fra Ensemble Studios så er stort set alle de genstande der eksisterer i spillet noget der er loadet med en eller anden form for objekt factory. Det er typisk en rigtig god ide at bygge en objekt factory til ethvert spil hvad enten det er stort eller ej, dette bevirker at arkitekturen i spillet samt udviddelsesmulighederne er meget lette at gå til. En object factory kan komme i mange forskellige afskygninger, et eksempel på object factory der ikke er så langt væk findes i planerne for det spil der udvikles i en gruppe her på udvikleren. Planen var at lave et strategispil! I ethvert strategispil skal der loades et antal spillere, mange forskellige bygninger, teknologier og terrænobjekter. Alt dette blev designet til at skulle ske i en object factory, således spillet var nemt at udvidde samtidig med at man havde muligheden for i designfasen af spillet at kunne modificere eksempelvis de forskellige bygninger ved blot at ændre i den fil der blev indlæst. En objekt factory er stort set ikke andet end en mængde funktionalitet der tilsammen kan producere en masse objekter, hurtigt. Der er ingen faste design patterns for en Object factory eller hovedregler for opbygningen, de varierer fra projekt til projekt.
En object factory i kode
For at bygge en object factory er det vigtigste mantra overhovedet: "design". Det kan ikke anbefales nok at man gør sig en masse tanker om hvordan ens objekter kommer til at se ud således man undgår for mange frustrationer med hensyn til at modificere og udvidde sin object factory til at favne nye attributter. Typisk når jeg laver object factories benytter jeg XML som mit underliggende filformat, dette gøres fordi det simpelthen er så legende let at tilgå og utroligt nemt at redigere i, samtidig med at selv enorme XML dokumenter er hurtige at loade. For nu at komme i gang med at kode en object factory skal vi have lavet et projekt i Delphi. Vi vil bygge en almindelig Windows Applikation for at forenkle metodikken, selvom man nok ville have valgt at bygge en .dll til at holde en object factory. Start med at lave en Windows Applikation i Delphi, og kald den noget så opfindsomt som 'ObjectFactory_proj' til projekt filen og 'ObjectFactory_form' til formen. For at vise de objekter i applikationen vi vil bygge med vores object factory tilføjer vi et TListView til vores applikation, sætter 'align' til 'alClient' og sæt 'View' til 'report' Ud over dette kan der også aktiveres gridlines og der laves to kolonner, en der hedder 'Object' og en der hedder 'Name'. Disse to skal vise henholdsvist Objekt typen og navnet på objektet samt dets attributter.
Den Object factory vi vil bygge skal kunne loade bygninger og enheder til et fiktivt strategispil. Der tages udgangspunkt i en XMl fil der indeholder de forskellige informationer omkring hvilke status bygninger og enheder har. Der tages udgangspunkt i følgende XML dokument.
<?xml version="1.0" encoding="utf-8" ?>
<objects>
<buildings>
<building>
<name>Blacksmith</name>
<hitpoints>150</hitpoints>
</building>
<building>
<name>Town hall</name>
<hitpoints>100</hitpoints>
</building>
<building>
<name>Lumber mill</name>
<hitpoints>175</hitpoints>
</building>
<building>
<name>Temple</name>
<hitpoints>200</hitpoints>
</building>
<building>
<name>House</name>
<hitpoints>50</hitpoints>
</building>
</buildings>
<units>
<unit>
<name>Swordsman</name>
<hitpoints>100</hitpoints>
<armor>6</armor>
<strength>25</strength>
</unit>
<unit>
<name>Archer</name>
<hitpoints>120</hitpoints>
<armor>5</armor>
<strength>23</strength>
</unit>
<unit>
<name>Mage</name>
<hitpoints>50</hitpoints>
<armor>3</armor>
<strength>14</strength>
</unit>
<unit>
<name>Paladin</name>
<hitpoints>150</hitpoints>
<armor>10</armor>
<strength>32</strength>
</unit>
</units>
</objects>
Et meget simpelt XML dokument der indeholder nogle få, meget simple enheder og bygniner. For at kunne lave de korrekte objekter skal der bygges nogle klasser i vores applikation der afspejler den objekt struktur der findes i denne XML fil. Det er her vi kommer til det punkt jeg talte om indledningsvist i denne artikel, nu gælder det om for så vidt muligt at have defineret alle sine attributter på hvert objekt i XML filen ellers bliver det en evig kamp mellem at først tilpasse XML filen, så tilpasse klasserne og herefter object factoryen. Vi vil nu lave to klasser der passer til hver type objekt, det vil sige en 'unit' klasse og en 'building' klasse. Vi tilføjer en ny unit til løsningen og laver i denne to nye klasser:
Unit og building klasserne
unit ObjectClasses;
interface
type
TUnit = class
private
theName : WideString;
theHitpoints : Integer;
theArmor : Integer;
theStrength : Integer;
public
Constructor Create(newName: WideString; newHitpoints, newArmor, newStrength : Integer);
property Name : WideString read theName write theName;
property Hitpoints : Integer read theHitpoints write theHitpoints;
property Armor : Integer read theArmor write theArmor;
property Strength : Integer read theStrength write theStrength;
end;
type
TBuilding = class
private
theName : WideString;
theHitpoints : Integer;
public
Constructor Create(newName : WideString; newHitpoints : Integer);
property Name : WideString read theName write theName;
property HitPoints : Integer read theHitpoints write theHitpoints;
end;
implementation
{ TBuilding }
constructor TBuilding.Create(newName: WideString; newHitpoints: Integer);
begin
theName:=newName;
theHitpoints:=newHitpoints;
end;
{ TUnit }
constructor TUnit.Create(newName: WideString; newHitpoints, newArmor,
newStrength: Integer);
begin
theName:=newName;
theHitpoints:=newHitpoints;
theArmor:=newArmor;
theStrength:=newStrength;
end;
end.
Der er ikke noget specielt ved disse to klasser, det er ganske almindelige klasser der er defineret. Det der nu skal ske er at vi laver en reel ObjectFactory klasse der indeholder funktionalitet til at loade fra XML filen og oprette objekter ud fra dette. Når et objekt er blevet oprettet bliver det lagt i en liste der så er tilgængelig for andre applikationer. Det betyder at skal man have fat i en bygning henter man en liste af bygninger og finder den man skal bruge (alternativt kan man jo bare tilbyde forskellige metoder på ObjectFactoryen der klarer denne funktionelitet). Vi vil dog i første omgang holde os til at tilbyde to lister, en der indeholder alle bygninger og en der indeholder alle enheder. For at komme i gang tilføjer vi endnu en ny klasse til vores løsning der hedder 'ObjectFactory'. Da denne klasse skal arbejde med XML skal vi have lavet en reference til Microsofts XML komponent, dette gøres ved at gå i værktøjslinien og vælge 'project' -> 'import type library' og browse efter Microsofts XML komponent, version 3 eller mere. Tilføj en ny unit og importer xml komponenten.
herefter skal vi have tilføjet den i vores 'uses' klausul i toppen af vores nye unit, det vil nu sige at toppen af vores .pas fil nu ser således ud
unit ObjectFactory;
interface
uses
MSXML2_TLB;
implementation
end.
Dette gør at vi kan tilgå og manipulere XML dokumenter. Det næste der skal ske er at vi skal have lavet et XML dokument samt loadet vores Objects.xml dokument ind i dette dokument. Vores object factory loader XML dokumentet i hver metode, det kunne naturligvis modificeres til at loade den kun enkelt gang.. Dette betyder at vores ObjectLoader fil efter et par modifikationer med load af xml dokument samt midlertidige, tomme metoder ser således ud:
unit ObjectFactory;
interface
uses
MSXML2_TLB, classes, ObjectClasses, sysutils;
procedure LoadBuildings;
procedure LoadUnits;
var
Buildings : TList;
Units : TList;
implementation
procedure LoadBuildings;
Begin
end;
procedure LoadUnits;
Begin
end;
end.
Som det ses skal der lige et par ting ekstra med i vores 'uses' klausel, dette er for at kunne lave en TList bruge vores klasser samt tilgå 'StrToInt' funktionen for at konvertere vores streng til et integer. Nu kommer alt det sjove! Nu skal vi til at loade først alle vores bygninger og så alle vores enheder. Først finder vi en liste der indeholder alle buildings i vores XML fil, det er nemt og hurtigt at gøre med en xpath. Herefter begynder vi at konstruere objekter ud fra den enkelte XmlNode i den XmlNodeList vores xpath expression giver os. Herefter bygger vi objekter ud fra hver enkelt element i vores node og tilføjer dette objekt til listen over enten bygninger eller enheder alt efter hvor det hører hjemme. Koden til den metoder der skal loade alle bygningerne ser således ud, den er lidt simplificeret for at understrege hvad der skal ske.
procedure LoadBuildings;
var
tmpDoc : IXMLDOMDocument;
tmpNodeList : IXMLDOMNodeList;
buildingNode : IXMLDOMNode;
nameNode : IXMLDOMNode;
hitpointNode : IXMLDOMNode;
myBuilding : TBuilding;
index : Integer;
begin
Buildings:=TList.Create;
tmpDoc:=CoDOMDocument.Create;
tmpDoc.load('Objects.xml');
if(tmpDoc <> nil) then
begin
tmpNodeList:=tmpDoc.selectNodes('objects/buildings/building');
for index:=0 to tmpNodeList.length-1 do
begin
buildingNode:=IXMLDOMNode(tmpNodeList[index]);
nameNode:=IXMLDOMNode(buildingNode.selectSingleNode('name'));
hitpointNode:=IXMLDOMNode(buildingNode.selectSingleNode('hitpoints'));
myBuilding:=TBuilding.Create(nameNode.Text, StrToInt(hitpointNode.text));
Buildings.Add(myBuilding);
end;
end;
end;
Efter at have implementeret vores metode der loader bygninger er det tid til at få loadet alle enheder. Dette sker på samme måde, dog er der et par ekstra attributter der skal tages højde for, men ikke noget der skulle betyde noget som helst.
procedure LoadUnits;
var
tmpDoc : IXMLDOMDocument;
tmpNodeList : IXMLDOMNodeList;
buildingNode : IXMLDOMNode;
nameNode : IXMLDOMNode;
hitpointNode : IXMLDOMNode;
strengthNode : IXMLDOMNode;
armorNode : IXMLDOMNode;
myUnit : TUnit;
index : Integer;
begin
Units:=TList.Create;
tmpDoc:=CoDOMDocument.Create;
tmpDoc.load('Objects.xml');
if(tmpDoc <> nil) then
begin
tmpNodeList:=tmpDoc.selectNodes('objects/buildings/building');
for index:=0 to tmpNodeList.length-1 do
begin
buildingNode:=IXMLDOMNode(tmpNodeList[index]);
nameNode:=IXMLDOMNode(buildingNode.selectSingleNode('name'));
hitpointNode:=IXMLDOMNode(buildingNode.selectSingleNode('hitpoints'));
armorNode:=IXMLDOMNode(buildingNode.selectSingleNode('armor'));
strengthNode:=IXMLDOMNode(buildingNode.selectSingleNode('strength'));
myUnit:=TUnit.Create(nameNode.Text, StrToInt(hitpointNode.Text), StrToInt(armorNode.Text), StrToInt(strengthNode.Text));
Units.Add(myUnit);
end;
end;
end;
Det sidste der skal laves er at implementere onShow eventen på vores form således at vi så snart applikationen loader får indlæst alle vores objekter. Den modificerede load event på vores form ser nu således ud:
procedure TForm1.FormShow(Sender: TObject);
var
buildingindex : Integer;
unitIndex : Integer;
myBuilding : TBuilding;
myUnit : TUnit;
myItem : TListItem;
index : Integer;
begin
ObjectFactory.LoadBuildings;
ObjectFactory.LoadUnits;
//show buildings
for index:=0 to ObjectFactory.Buildings.count-1 do
begin
myBuilding:=TBuilding(ObjectFactory.Buildings[index]);
myItem:=ListView1.Items.Add;
myItem.Caption:='TBuilding';
myItem.SubItems.Add(myBuilding.Name+'. - Hitpoints: '+IntToStr(myBuilding.HitPoints))
end;
for index:=0 to ObjectFactory.Units.count-1 do
begin
myUnit:=TUnit(ObjectFactory.Units[index]);
myItem:=ListView1.Items.Add;
myItem.Caption:='TUnit';
myItem.SubItems.Add(myUnit.Name+'. -'+
'Hitpoints: '+IntToStr(myUnit.HitPoints)+
'Armor: '+IntToStr(myUnit.Armor)+
'Strength: '+IntToStr(myUnit.Strength));
end;
end;
Hvis man kører applikationen nu og den klager over at den ikke kender til Objects.xml filen så kopier den ned i samme mappe som .exe filen. Så skulle det være klaret.
Vi har nu en fuldt integreret object factory der er i stand til at loade objekter direkte fra en xml fil. Dette begrænsede eksempel har kun indeholdt 9 forskellige objekter, det er overkommeligt at vedligeholde den statisk i en applikation og blot ændre dem direkte i koden hver gang de bliver ændret. Kommer man derimod op i eksempelvis store spil hvor der skal loades 1000 objekter er en object factory et must!
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (22)
Enlig en artikel. Det har jeg ventet mange måneder på...
Du er så fusket, at skrive det 2 steder:
Under C# og delphi.
Det er næste snyd...
nope, det er ikke snyd! Hvis du synes det har du vel også læst min post til co-admins hvor jeg stiller spørgsmålet og fik beskeden om at dette var måden at gøre det på
Lav lidt research før du slår ned på folk!
Jonas (Decko) Undskyld hvis du tar det som en fornærmelse. Jeg forstår dig godt, når du bliver en smugle sur eller irriteret. Jeg kan godt se, jeg har formuleret det lidt dårligt...
Jeg undskylder, og håber du kan tilgive mig!!!
Det jeg mente var nok at du er fusket. Der mener jeg også smart. Hmmm, måske kan man da godt sige at det er doppel op med de up, du tjener. Og det er jo også en faktion (som det hedder i professonelt sprog...), får det er sandt. Det betyder ikke at jeg syns du er ånd og har gjordt noget forkert.
Men bare at vise det, da jeg syns det er lidt funny. Og måske lidt sjovt, får folk at få at vide...
Jeg ånskylder vis du er fornærmet, og håber at jeg med disse ord, kan gøre dig bedre tilpads, og iøvrigt også rose dig får den flotte artikel.
Den har været hård at læse igennem. Men den er da fin. Og dejligt at der enlig sker noget med de folk der har ting i hoved, klar til at skrive en artikel, men bare ikke gør det...
Im sorry.
(ps. 5 i rating fra mig)
Det er næste snyd...
Her burde jeg måske lave en smiley, så du kan se det er lidt ironisk. Burde nok skrive det sådan:
Du er så fusket, Ha ha. At skrive det 2 steder:
Under C# og delphi. Smart gjordt. Dobelt op med up, får mindre arbejde. LOL.
Den skal jeg huske, til når jeg skal skrive en...
Det er næste snyd...
Just fun. Det er selvfølgelig godt at alle kan få glæde af en artikel.
Fed artikel. Lige hvad jeg manglede
... Men lig en ting: Kunne du ikke have vist hvordan man f.eks. undgår at blive afhængig af MSXML? Men ellers rigtig god artikel. 5 herfra.
Thumbs up
Hmmm... Er det bare míg, eller laver du en: "tmpNodeList:=tmpDoc.selectNodes('objects/buildings/building');" i både LoadBuildings og LoadUnits? Hvis det ikke bare er mig, så vil jeg godt sprøge hvorfor? Troede at "tmpNodeList:=tmpDoc.selectNodes('objects/buildings/units');" skulle kaldes i LoadUnits? Correct me if I am wrong... Men ellers god artikel, som jeg før har nævnt...
Nej... Mente "tmpNodeList:=tmpDoc.selectNodes('objects/units/unit');"... Sorry
hey jeg syndes det er godt at du har lavet denne guide til at lave spil men syndes ik den er ordnligt forklart fyndes selv den er svær at finde ud af hvad du mener så har ik fået lavet noget inu men ellers god guide
hmm jeg vil lige sige jeg syndes denne guide er lidt dårlig for du forklar dig slet ik du skriver bare og jeg ved ik hvordan man åbner alle de ting du gør
det er ik for at jeg vil klage men du kunne godt skrive hvordan man åbner de ting du gør
Som der også står i 'Niveauet' for denne artikel så retter denne artikel sig mod den lettere øvede udvikler, der godt kan se perspektivet i den kode der er lavet, hvis jeg skulle starte på et niveau der var 'begynder' ville der ikke være nogle artikler til de udviklere der er kommet længere en begynderstadiet. De ting jeg refererer til i denne artikel er ting der er taget for givet, hvis man vil kode lidt mere avanceret end begynderen. Beklager hvis du ikke har kunnet finde ud af det, men der er måske nogle ting du skal have styr på i forbindelse med brugen af Delphi.
=>The Freak: selvfølgelig skal LoadUnits bruge en ande XPath end den der bruges til at loade bygninger.... beklager denne skrivefejl. Jeg tror aldrig man kan blive helt fri fra MSXML, da den vist også bliver brugt i Delphi's implementering af XMLDokumentet. Hvis man ikke vil være afhængig, kan man jo overveje at benytte en flad tekst fil i stedet.
brian Hvarregaard: jeg kan godt kode sådan nogen ting med hvordan laver du den der ObjectFactory det er det enste har prøvet og har læst denne tråd igemmen mange gange men fårstår ik lige den der
Du kan copy-paste min kode direkte ind i din form. Tilføj en ny unit, til at indeholde klasserne. Lav to nye procedurer der er mangen til dem jeg har i min kode. Lav et brugerinterface og kald procedurerne i din onShow event...... det skulle være det. Og så naturligvis lav referencer og XML dokument.
Jeg har prøvet at køre projektet men fik en "Acces Violation". Ved at debugge koden fandt jeg ud at i proceduren LoadUnits, mens XML noder aflæses, er "armorNode" og "strengthNode" tomme pointers og det skyldes at
proceduren LoadUnits læser <building> objekt hvor disse metoder ikke er definerede.
.. (line 69)
tmpNodeList:=tmpDoc.selectNodes('objects/buildings/building');
//skal stå "objects/units/unit"
Jeg ved at det er en lille fejl men hvis man er en nybegynder, er det svært at se.
Mvh. Poul
God artikel Brian!
Kunne dog have vaeret federe at se den fuldt implementeret som dll.
Ang. MSXML, så findes der et open source library "OmniXML" som er en ren delphi/pascal implementering af XML DOM'en. Den kan hented på
http://www.omnixml.com.
Implementasjonseksempel var veldig dårlig med dårlig koding
ved ikke om jeg missede det. er pointen ikke med en factorty at den laver objekter som den ikke nedlægger .. alt med dette mønster er en factory, fordi de indikere at man skal håndtere det et andet sted?
Du skal være
logget ind for at skrive en kommentar.