[XML] Open/Save dmv Template Method (Design Pattern)

Pagina: 1
Acties:

  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 04-05 13:09
Ik kom hier even niet echt uit. Mijn programma bestaat uit een diepe hiërarchische structuur. Om deze op te slaan gebruik ik XML. Eerder deed ik door van buitenaf (deze structuur) een class weg te schrijven en vervolgens (recursief) de kinderen weg te schrijven.

Nu ben ik bezig om alle verantwoordelijkheid en functionaliteit in de classes zelf op te slaan. Alle classes subclassen een abstract hoofdclass, waarin ik zoveel mogelijk standaardfunctionaliteit definieer. Hierin zitten al de lijst voor de kinderen, bepaalde default-values (met setters/getters) die alle subclasses ook moeten hebben, en functies die ook telkens terugkomen. Zo ook de functie voor Open en Save.

Het saven doe ik nu op een hele gave manier, namelijk mbv het Template Method Design Pattern. In de hoofdclass zit een functie Save en een abstract functie SaveThis. Vanuit Save roep ik SaveThis aan, en de subclass moet dus verplicht SaveThis overriden om de dingen opslaan die specifiek voor deze subclass gelden. Daarna sla ik in Save de algemene dingen op, en roep alle Save's van de kinderen uit de lijst aan. Tot zover perfect.

Maar het openen is een heel ander verhaal. Ik weet nu niet hoe en waar ik vanuit de Open functie kan bepalen wat voor type een element moet worden (kan wel daar met een switch natuurlijk, maar dan moet ik voor elke subclass (die toegevoegd wordt) een regel in de switch toevoegen. Maar als ik het vanuit de subclass doe (bijvoorbeeld op het niveau van het itereren van kinderen), krijg ik daar functionaliteit die eigenlijk voor alle subclasses geldt. Kortom, de weg terug vind ik nogal lastig.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Hoofclass
public bool Save(XmlWriter xmlWriter)
{
    xmlWriter.WriteStartElement(GetType());
    SaveThis(xmlWriter);

    xmlWriter.WriteAttributeString("Enabled", Enabled.ToString());
    xmlWriter.WriteAttributeString("UseFocusCorrector", UseFocusCorrector.ToString());

    foreach (CTestFabriekComponent t in list)
        t.Save(xmlWriter);

    xmlWriter.WriteEndElement();
    
    return true;
}

abstract private bool SaveThis(XmlWriter xmlWriter);


C#:
1
2
3
4
5
6
// Subclass
private override bool SaveThis(System.Xml.XmlWriter xmlWriter)
{
    xmlWriter.WriteAttributeString("Name", strName);
    return true;
}


edit:

GetType is ook een astract new string in de hoofdclass, waardoor alle subclasses daarin ook hun eigen naam specificeren.

[ Voor 13% gewijzigd door riezebosch op 02-05-2005 10:01 ]

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Afhankelijk van de complexiteit van je structuur zou ik oppassen met het integreren van verschillende aspecten van de functionaliteit van die structuur. Het persistent maken van objecten is niet het hoofdaspect en daarom kan je dit vaak beter in een andere structuur onderbrengen. Je krijgt anders van die drukke en volle classes.

Als je het in een andere structuur onder brengt, bv de XMLPersoonPersister (met een load en save method_, hou je overzichtelijk bij elkaar hoe het persisten werkt ipv dat het over je hierarchie versnipperd ligt. Deze oplossing is vooral sterk als je het niet toelaat dat extern de hierarchie wordt uitgebreid en alle classes bekend zijn. Wil je dat iedereen subclasses kan maken, dan krijg je met deze oplossing wel problemen.

Ik zet in ieder geval mijn xml persist aanpak altijd zo op en het heeft tot zover altijd uitstekend gewerkt.

  • whoami
  • Registratie: December 2000
  • Laatst online: 09:11
^^ met Alarmnummer.

Ik heb vorige week ook één van m'n classes ff onder handen genomen.
Dit was een class die een 'repository' moest voorstellen van bepaalde items die in een xml file te vinden waren, en die class had ook load en save methods.
Ik heb m'n class ook zo gemaakt dat ik een aparte 'persister/gateway' heb die verantwoordelijk is voor het laden van de xml file en een object van het type 'blaat' teruggeeft. 'Blaat' is dan eigenlijk m'n in memory repr. van de items in m'n file.
M'n persister heeft dan ook een save method die een object van het type 'blaat' meekrijgt, en deze naar een file wegschrijft.

https://fgheysels.github.io/


  • whoami
  • Registratie: December 2000
  • Laatst online: 09:11
Alarmnummer schreef op maandag 02 mei 2005 @ 10:07:
Wil je dat iedereen subclasses kan maken, dan krijg je met deze oplossing wel problemen.
Hmm, ik heb het zo aangepakt dat m'n classes moeten afgeleid zijn van een bepaalde interface.
Die interface bepaalt dat m'n items DirectRead en DirectWrite methods moeten hebben. Deze methods krijgen dan respectievelijk een XmlTextReader en een XmlTextWriter als argument mee.
De class is zo zelf verantwoordelijk voor de manier waarop hij weggeschreven wordt naar de xml file, maar het is wel een aparte class die ervoor zorgt dat de volledige collectie items op de juiste manier naar het bestand weggeschreven wordt, of gelezen wordt.
Dit is natuurlijk niet interessant als je die objecten ook in een andere vorm wilt opslaan, maar bij mij is dat niet het geval.

https://fgheysels.github.io/


  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 04-05 13:09
Dat is dus ook (ongeveer) mijn aanpak. Alleen zit ik nu met het punt dat ik op verschillende niveaus objecten toe wil kunnen voegen, en dat alle objecten kinderen kunnen hebben (die nu dus ook automatisch in goede volgorde weggeschreven worden).

Misschien dat het een beter idee is dit toch aan de subclass over te laten.

Ik zit dus nog steeds te knutselen aan een macro tool, en in deze hoofdclass heb ik dus ook een functie Play gespecificeerd. Nu laat ik een subclass bepalen wat hij zelf moet doen bij een Play, en roep daarna vanuit de hoofdclass alle kinderen aan om ook af te spelen.

Moet nog is goed heroverwegen op welk niveau ik alles wil hebben. Sowieso leek het me een goed idee om in de abstract class / interface aan te geven dat alle objecten een functie Play/Open/Save hebben, zodat ik me niet druk hoef te maken van welk type de kinderen zijn maar gewoon rechtstreeks die functie aan kan roepen.

[ Voor 48% gewijzigd door riezebosch op 02-05-2005 10:29 ]

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

whoami schreef op maandag 02 mei 2005 @ 10:15:
[...]
Hmm, ik heb het zo aangepakt dat m'n classes moeten afgeleid zijn van een bepaalde interface.
Die interface bepaalt dat m'n items DirectRead en DirectWrite methods moeten hebben. Deze methods krijgen dan respectievelijk een XmlTextReader en een XmlTextWriter als argument mee.
Ik snap je aanpak :) Ik heb er in het verleden ook genoeg zo opgezet. Het nadeel is dat je complexe structuren krijgt (allerlei aspecten door elkaar). Maar jouw aanpak is soms ook erg sterk (vooral als je een hele dynamische hierarchie heb waar iedereen allerlei subclasses aan mag toevoegen).
De class is zo zelf verantwoordelijk voor de manier waarop hij weggeschreven wordt naar de xml file, maar het is wel een aparte class die ervoor zorgt dat de volledige collectie items op de juiste manier naar het bestand weggeschreven wordt, of gelezen wordt.
Dit is natuurlijk niet interessant als je die objecten ook in een andere vorm wilt opslaan, maar bij mij is dat niet het geval.
De keuze is of jij het anders gaat opzetten. Beide aanpakken (en er zijn trouwens ook wel tussen variaties te vinden) hebben voor en nadelen.

[ Voor 10% gewijzigd door Alarmnummer op 02-05-2005 10:31 ]


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

riezebosch schreef op maandag 02 mei 2005 @ 10:24:
Moet nog is goed heroverwegen op welk niveau ik alles wil hebben. Sowieso leek het me een goed idee om in de abstract class / interface aan te geven dat alle objecten een functie Play/Open/Save hebben, zodat ik me niet druk hoef te maken van welk type de kinderen zijn maar gewoon rechtstreeks die functie aan kan roepen.
Je bent zeker nog niet bekend met het visitor design pattern ;) Deze is ook erg sterk op het moment dat je een vaste hierarchie hebt.

  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 04-05 13:09
Alarmnummer schreef op maandag 02 mei 2005 @ 10:32:
[...]

Je bent zeker nog niet bekend met het visitor design pattern ;) Deze is ook erg sterk op het moment dat je een vaste hierarchie hebt.
Heb gelukkig het GoF boek hier voor me liggen B)

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 04-05 13:09
Nu heb ik het (even) zo opgelost. Mijn hiërarchie is dus helemaal niet zo vast. De gebruiker kan op ieder niveau verschillende dingen (genest) toevoegen. Een TestSet kan TestScripts maar ook weer TestSets bevatten. Een Window kan Windows, Commands, Keyboards of, Checks bevatten. De objecten hebben veel overeenkomsten. Zo moeten ze allemaal (op het onderste niveau na) kinderen kunnen bevatten, allemaal opgeslagen/geopend worden en allemaal kunnen "afspelen" (Playback). Daarom leidt ik ze af van een standaardclass die ik TestFabriekComponent heb genoemd (het programma heet ook TestFabriek). In deze TestFabriekComponent zijn verschillende standaardfuncties gemaakt die van toepassing zijn op alle afgeleide classes (zoals of het element Enabled is, wat weer afhankelijk is of z'n parents het ook allemaal zijn).

Hierbij zijn ook wat functies die wel in alle classes terugkomen, maar die wel in de specifieke class invulling moeten krijgen. Hiervoor maak ik (zoals in de openingpost ook beschreven) gebruik van het Template Method Pattern. Omdat aan een object soms ook objecten van hetzelfde type toegevoegd kan worden, of zelfs een object van het ene type aan het andere toegevoegd en het andere ook aan het ene, bevat TestFabriekComponent de functie Add(TestFabriekComponent). Deze functie zou in alle subclasses hetzelfde zijn, op de check na of het nieuwe object inderdaad toegevoegd mag worden. Daarom zit het toevoegen wél in TestFabriekComponent, maar wordt vandaaruit een abstract functie AddAllowed(string Type aangeroepen waardoor de subclass bepaalt of het element inderdaad toegevoegd mag worden.

C#:
1
2
3
4
5
6
7
8
9
10
public bool Add(CTestFabriekComponent c)
{
    if (AddAllowed(c.GetType()))
    {
        list.Add(c); 
        return true; 
    }

    return false;
}


Deze constructie is ook erg handig wanneer ik straks vanuit de GUI een (context)menu laat verschijnen, en bepaalde MenuItems wil disablen (omdat ze op dat niveau niet toegevoegd mogen worden). De geselecteerde TreeNode heeft een referentie in z'n Tag naar het object wat ie weergeeft. Door deze te casten naar TestFabriekComponent (zonder me zorgen te maken wat voor type TestFabriekComponent het precies is) kan ik AddAllowed aanroepen, en de specifieke MenuItems enablen/disablen.

edit:

Oh, en waar het eigenlijk om ging...

Het openen heb ik dus zo gedaan, dat TestFabriekComponent ook verantwoordelijk is voor het inlezen van nieuwe objecten. Hierin roep ik eerst OpenThis aan om de class-specifieke members in te lezen, daarna lees ik de TestFabriekComponent algemene members in. En daarna volgt een while-loop waarin ik blijf lezen totdat het bij dit element behorende EndElement gevonden is met daarin een switch om nieuwe (kind)elementen te herkennen en toe te voegen.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public bool Open(XmlReader xmlReader)
{
    OpenThis();

    Enabled = bool.Parse(xmlReader.GetAttribute("Enabled"));
    UseFocusCorrector = bool.Parse(xmlReader.GetAttribute("UseFocusCorrector"));

    bool bRead = xmlReader.Read();
    while (xmlReader.NodeType != XmlNodeType.EndElement && bRead)
    {
        switch (xmlReader.Name)
        {
            case "TestProject":
                bRead = Add(new CTestProject().Open(xmlReader));
                break;
            //[...]
        }

        bRead = xmlReader.Read();
    }

    return bRead;
}


Het gaat me er niet om te pimpen wat ik bedacht heb ofzo. Het gaat me erom dat ik me afvraag of dit een aardig/goed idee is en wat de pro's en cons zijn. Met name omdat ik nu probeer Design Patterns te gebruiken. Er zal vast wel meer ervaring zijn met het opslaan van geneste datastructuren. Daarom whoami en alarmnummer iig alvast bedankt voor de reacties :)

[ Voor 39% gewijzigd door riezebosch op 02-05-2005 12:13 ]

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


  • D4Skunk
  • Registratie: Juni 2003
  • Laatst online: 20-10-2025

D4Skunk

Kind of Blue

Dit ziet er zeer interessant uit, maar enkele zaken zou ik op een andere manier aanpakken :

- de boomstructuur zit in je xml-file vervat. Dit zorgt dat je bij het openen van de xml-file een prachtige boomstructuur te zien krijgt, maar houd echter wel rekening met het volgende aspect : de meeste tools / interfaces zijn gemaakt/werken met tabellen ipv boomstructuren.
Wanneer je bv een import/export moet laten lopen, of je gegevens persisten in een db, ben je imho beter af met een tabelstructuur. Om 1 bepaalde leaf te loaden moet je dan ook je volledige tree doorlopen => performantie ?
Wanneer je je gegevens in een boomstructuur opslaat, vermeng je mijn persoonlijke mening het model en de view. (MVC ), want je kan bv later altijd beslissen om je tree volgens een andere property van je object op te bouwen...

- Vanaf het moment dat ik zag dat je je menustructuur wou gaan uitbreiden, moest ik denken aan het Gof Command Pattern.
Een geïmprovisserd voorbeeld voor een richtextbox / bold command

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface ICommand {
   public bool IsEnabled(object o);
   public void Execute(object o);
   public string Caption();
}

private object RichTextBoxBoldCommand : ICommand {
   public bool IsEnabled(object o) {
      RichTextBox rtb=(RichTextBox)o;
      return rtb.SelectedText.Length>0;
   }
   public void Execute(object o) {
      RichTextBox rtb=(RichTextBox)o;
      rtb.SelectedText.SetBold();
   }
   public string Caption() {
     return "&Bold"
   }
}


Dit kan je dan verder uitbreiden met undo, UndoEnabled, descriptions, help etc...

  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 04-05 13:09
En daar komen we D4Skunk weer tegen :)

1: Het is een opzichzelfstaande applicatie, dus met im-/exports het ik niet te maken.
2: Ik onderken juist een duidelijke hiërarchie in de structuur, vandaar dat het een boom is. Dat ik dit in een TreeView weergeef is juist ideaal (hoewel ik lang heb zitten zoeken hoe ik de data en de view toch gescheiden kon houden (zoals je weet ;)). Ik weet ook niet hoe je het anders zou willen doen? Uit een platte bak weer een Tree tevoorschijn toveren?
3: Bedankt voor de tip. Weet alleen niet of ik het ook ga implementeren (heb ff al genoeg aan m'n hoofd).

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


  • D4Skunk
  • Registratie: Juni 2003
  • Laatst online: 20-10-2025

D4Skunk

Kind of Blue

offtopic:
Inderdaad, 't wordt tijd om me wat met mijn werk bezig te houden ipv hier een hele dag op got rond te hangen :+

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

D4Skunk schreef op maandag 02 mei 2005 @ 14:00:
Dit ziet er zeer interessant uit, maar enkele zaken zou ik op een andere manier aanpakken :

- de boomstructuur zit in je xml-file vervat. Dit zorgt dat je bij het openen van de xml-file een prachtige boomstructuur te zien krijgt, maar houd echter wel rekening met het volgende aspect : de meeste tools / interfaces zijn gemaakt/werken met tabellen ipv boomstructuren.
Wanneer je bv een import/export moet laten lopen, of je gegevens persisten in een db, ben je imho beter af met een tabelstructuur. Om 1 bepaalde leaf te loaden moet je dan ook je volledige tree doorlopen => performantie ?
1)Ga je ooit een enkele node inladen?
2)Bomen laten zich perfect vertalen naar een tabel.
Wanneer je je gegevens in een boomstructuur opslaat, vermeng je mijn persoonlijke mening het model en de view. (MVC ), want je kan bv later altijd beslissen om je tree volgens een andere property van je object op te bouwen...
Volgens mij moet je meer koffie drinken want hier kan ik geen kant mee op. Verder is dit een view model..(mijn naam... officieel heet het de presentation model (kwamik later achter)) een model die de view ondersteund...

[ Voor 7% gewijzigd door Alarmnummer op 02-05-2005 15:33 ]


  • D4Skunk
  • Registratie: Juni 2003
  • Laatst online: 20-10-2025

D4Skunk

Kind of Blue

offtopic:
hmmz ik had nochthans beloofd het iets kalmer aan te doen :-)
Alarmnummer schreef op maandag 02 mei 2005 @ 15:18:
[...]

1)Ga je ooit een enkele node inladen?
2)Bomen laten zich perfect vertalen naar een tabel.

[...]

Volgens mij moet je meer koffie drinken want hier kan ik geen kant mee op. Verder is dit een view model..(mijn naam... officieel heet het de presentation model (kwamik later achter)) een model die de view ondersteund...
@alarmnummer
Even verduidelijken, een voorbeeldje zal waarschijnlijk iets duidelijker zijn :

Het beste voorbeeld om dit te illustreren is het rekeningstelsel van een boekhouding :
Normaalgezien is een rekening georganiseerd per rekeningnummer : 6 = kosten, 7 = opbrengsten etc.. Dus zou je kunnen zeggen dat je dit in een tree wenst op te slaan... als volgt :
code:
1
2
3
4
5
6
7
8
9
...
6
 60 
  601
  602..
 ...
7
  ..
..


Veronderstel nu dat je morgen overstapt van een standaardboekhouding naar een analytische boekhouding. Dan heb je naast de onderverdeling per rekeningnummer ook nog een onderverdeling per kostenplaats en kostendrager; je tabel bevat dus 3 dimensies of 'Tree-modellen'.
Wat ik dus bedoel met view (MVC) presentation model - thx alarmnummer ! - is dat een tree een bepaalde interpretatie is van gegevens. Bij uitbreiding van je gegevens naar andere dimensies dan diegene die je gebruikt in je oorspronkelijke boomstructuur kom je dan gegarandeerd in de problemen, of je bent verplicht iedere keer de volledige tree in het geheugen te laden, om hem daarna te overlopen.
Een bijkomend nadeel is dat je steeds van de top-node moet vertrekken...
Als je dus wenst te kiezen voor een flexibel model dat je later gegarandeerd geen kopzorgen bezorgt, kies dan nooit voor opslag in een boomstructuur.

Natuurlijk staat het iedereen vrij te kiezen wat men wil... B)

[ Voor 2% gewijzigd door D4Skunk op 02-05-2005 18:54 . Reden: mvc -> presentation model ]


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Als je dus wenst te kiezen voor een flexibel model dat je later gegarandeerd geen kopzorgen bezorgt, kies dan nooit voor opslag in een boomstructuur.
:z sorry hoor.. denk niet dat ik hierop in hoef te gaan. Boomstructuren zijn volgens mij wel de meest gebruikte structuren die je eigelijk maar kan bedenken.

En volledig inladen is ook onzin. In XML kan je gerust met XPath een node eruit lichten.

[ Voor 16% gewijzigd door Alarmnummer op 02-05-2005 19:39 ]


  • gorgi_19
  • Registratie: Mei 2002
  • Nu online

gorgi_19

Kruimeltjes zijn weer op :9

Even dicht, even bijlezen...

Digitaal onderwijsmateriaal, leermateriaal voor hbo


  • gorgi_19
  • Registratie: Mei 2002
  • Nu online

gorgi_19

Kruimeltjes zijn weer op :9

En weer open; gaarne weer ontopic en minder inhakken op elkaar. :)

Digitaal onderwijsmateriaal, leermateriaal voor hbo

Pagina: 1