Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[.net] Hoe constructie maken met import/export

Pagina: 1
Acties:
  • 529 views sinds 30-01-2008
  • Reageer

  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 11:14
Hallo,

Ik heb in c# een paar klassen gemaakt waarmee ik bepaalde bestandsformaten kan uitlezen en waarmee ik die kan aanmaken. Het gaat bijvoorbeeld om de bestandsformaten voor TomTom (.ov2) en ClieOp03 bestanden.

Nou heb ik dat zo geprogrammeerd dat de structuur van de klassen overeenkomt met de logische indeling van die bestanden. De entiteiten weerspiegelen dus goed de structuur. Maar nu is de vraag hoe je dat dan 'wegschrijft'.

Wat ik nu heb gedaan in het geval van ClieOp is een functie GenerateStream() die je kunt aanroepen op de Transactie (de hoogste entiteit in zo'n bestand). Deze heeft als output een Stream object, die je dan in een andere klasse kunt gebruiken om een bestand weg te schrijven, bijvoorbeeld.

Dat is dus één methode. Een andere methode is dat ik niet een Stream als output gebruik die ergens anders in te implementeren is, maar direct een bestand wegschrijf. Weer een andere optie is om een aparte klasse te maken die als dedicated functie heeft om zo'n Transactie-klasse te genereren op basis van een file als input (en andersom).

Wat is nou gebruikelijk? Hoe kun je hier het beste mee omgaan?

Dit is één voorbeeld, maar het is natuurlijk breed toepasbaar. Ik heb verschillende coördinaatsysteem-klassen gebouwd en hierbij heb ik een algemene klasse CoordinateConverter gebouwd die kan rekenen tussen verschillende formaten. Daar leek me die aanpak handiger.

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
ik zou iets van een interface maken.

C#:
1
2
3
4
interface IFileWriter
{
    void WriteToStream(Stream stream);
}


en dan alle klassen die je hebt deze laten implementeren. In die functie schrijf je gewoon alles naar de stream die je meekrijgt. Op deze manier kun je een willekeurige stream meegeven in je aanroepende code. Hierdoor is het ook mogenlijk om het naar andere type streams te schrijven ( Socket, ZipStream, CryptoStream )

Eventueel maak je nog een methode WriteToFile( string path ) die zelf een FileStream aanmaakt en dan WriteToStream aanroept.

Zo kan je ook een IFileReader maken met een methode ReadFromStream. Op deze manier heb je gewoon een soort Serialize/Deserialize constructie gemaakt.

[ Voor 11% gewijzigd door Woy op 18-01-2008 16:19 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 11:14
Ik begrijp je antwoord niet helemaal, maar misschien heb ik mijn vraag ook niet duidelijk gesteld ;).

De vraag is dus eigenlijk of de methode van _een_ functie GenerateStream wel de goede optie is. Je zou ook bij het aanmaken van zo'n file al kunnen kiezen voor een static functie Create met een filepath parameter en dan een knop Save. Ik vind dat persoonlijk niet handig, omdat je dan altijd een bestand aan moet laten maken en ik het handig vind als je ook '100% offline' kunt werken.

Maar wat is hier nu handig in, zijn er een soort van afspraken hoe je hiermee om moet gaan? Dat wil ik weten :).

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Bij ons is ClieopBase het hoogte object, daaronder hangen BatchBase en daaronder hangt weer TransactionBase. ClieopBase heeft een methode WriteOutput welke een stream verwacht en schrijft vervolgens de gehele clieop bestand (dus inclusief de opgegeven batches met daarin de transacties) naar de stream.

De reden dat wij deze methode aanhouden is omdat bestand voor BestandSluitRecord (Wij hebben Nederlands in de benamingen opgenomen zodat de relatie met het specifieke clieop entity duidelijk is) verschillende totalen en checksums moet kunnen wegschrijven.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ClieopIncasso clieop = new ClieopIncasso();  // bepaald direct transactie groep (betaling batches en transactie raisen dus een error)
clieop.Identity = "FLINC";
clieop.BatchIdentity = 1; //dag teller voor clieops

IncassoBatch batch = new IncassoBatch();
incassoBatch.PrincipalName = "Bla";
...

IncassoTransaction trans = new IncassoTransaction();
trans.Name = "S. Shopper";
...

batch.AddTransaction(trans);
clieop.AddBatch( incassoBatch );


using (FileStream fs = new FileStream(@"c:\tmp\clieop.dat"))
{
  clieop.WriteOutput(fs);
  fs.Flush();
  fs.Close();
}


De classes in dit voorbeeld fungeren eigenlijk als een wrapper naar verschillende Clieop record classes zoals BestandVoorloopRecord en BestandSluitRecord. Elke wrapper class roept in WriteOutput in principe zijn voorloop record, child records en sluit record aan.

Om terug te komen op je vraag welke methode het beste is. Ik geef zelf de voorkeur aan een WriteOutput methode omdat behalve een FileStream, ik ook HttpResponseStream kan meegeven en dan wordt de clieop naar de http stream geschreven.

Eventueel zou je in WriteOutput een StreamWriter instance kunnen aanmaken zodat je eenvoudiger strings kunt wegschrijven naar de stream, maar ik heb ervoor gekozen om de clieop records gewoon een byte array te laten terug gegeven. (50 bytes voor clieop record + 2 bytes voor cr+lf).

If it isn't broken, fix it until it is..


  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 11:14
Niemand_Anders schreef op vrijdag 18 januari 2008 @ 16:53:
Om terug te komen op je vraag welke methode het beste is. Ik geef zelf de voorkeur aan een WriteOutput methode omdat behalve een FileStream, ik ook HttpResponseStream kan meegeven en dan wordt de clieop naar de http stream geschreven.

Eventueel zou je in WriteOutput een StreamWriter instance kunnen aanmaken zodat je eenvoudiger strings kunt wegschrijven naar de stream, maar ik heb ervoor gekozen om de clieop records gewoon een byte array te laten terug gegeven. (50 bytes voor clieop record + 2 bytes voor cr+lf).
Dank je voor je toelichting. In deze laatste alinea zeg je inderdaad waar ik naar op zoek was. Overigens, ik heb zelf alleen maar Transactie, Batch en Post gedefineerd, omdat ik vind dat je abstract gezien in een library niet moet kijken naar hoe het uiteindelijk outputformaat is.

Je hebt in je voorbeeld een functie WriteOutput waar je een stream als parameter aan meegeeft. Ik heb geen idee, maar wat heeft dat als voordeel boven een functie zonder parameters die als output een Stream-object teruggeeft (in mijn geval een MemoryStream trouwens)? Is dat puur en alleen dat jij zélf nog kunt kiezen wat voor Stream je gebruikt? :).

Verwijderd

Klinkt mij een geval voor een Serializer. Maar ik kan het natuurlijk mis hebben.

  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 11:14
Verwijderd schreef op maandag 21 januari 2008 @ 11:15:
Klinkt mij een geval voor een Serializer. Maar ik kan het natuurlijk mis hebben.
Ik begrijp je niet :?.

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Dennis schreef op maandag 21 januari 2008 @ 11:05:
[...]

Dank je voor je toelichting. In deze laatste alinea zeg je inderdaad waar ik naar op zoek was. Overigens, ik heb zelf alleen maar Transactie, Batch en Post gedefineerd, omdat ik vind dat je abstract gezien in een library niet moet kijken naar hoe het uiteindelijk outputformaat is.

Je hebt in je voorbeeld een functie WriteOutput waar je een stream als parameter aan meegeeft. Ik heb geen idee, maar wat heeft dat als voordeel boven een functie zonder parameters die als output een Stream-object teruggeeft (in mijn geval een MemoryStream trouwens)? Is dat puur en alleen dat jij zélf nog kunt kiezen wat voor Stream je gebruikt? :).
Idd zoals ik in mijn post hierboven ook aangeeft. Je kunt beter een stream als parameter accepteren. Het wordt dan direct naar de stream geschreven waar de aanroepende code het wilt hebben.

Als je een stream terug geeft, dan heeft de aanroepende code geen controle meer waar het heengeschreven word.

Als je een stream accepteerd dan kan de Client elke willekeurige stream meegeven. Eventueel kan je deze stream dan nog wrappen in een willekeurige stream die voor jou handig is ( Mischien heb je wel een handige ClieOp stream gemaakt die je gebruikt )

Wat moet de aanroepende code tenslotte met de stream die je terug geeft?

Ik haalde in de eerste post ook express een Serializer aan. Je wilt je in memmory object tenslotte serializeren naar iets anders ( Een stream ).

Je kan dit of in je ClieOp object implementeren of eventueel een losse ClieOp Serializer maken.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Dennis schreef op maandag 21 januari 2008 @ 11:05:
[...]

Dank je voor je toelichting. In deze laatste alinea zeg je inderdaad waar ik naar op zoek was. Overigens, ik heb zelf alleen maar Transactie, Batch en Post gedefineerd, omdat ik vind dat je abstract gezien in een library niet moet kijken naar hoe het uiteindelijk outputformaat is.

Je hebt in je voorbeeld een functie WriteOutput waar je een stream als parameter aan meegeeft. Ik heb geen idee, maar wat heeft dat als voordeel boven een functie zonder parameters die als output een Stream-object teruggeeft (in mijn geval een MemoryStream trouwens)? Is dat puur en alleen dat jij zélf nog kunt kiezen wat voor Stream je gebruikt? :).
Het voordeel van een stream meegeven ipv terug krijgen ligt in het simpele feit dat jij niet altijd de stream kunt starten. Stel jij wilt de export als download aanbieden. Nu kun je uiteraard het clieop bestand als bestand op de schrijf zetten waarna je het bestand bijv via Response.WriteFile naar de browser stuurt, maar je kunt ook direct Response.OutputStream meegeven. Maar ook als je aan socket programming bezig bent, kun je niet zomaar een stream openen. In dergelijke gevallen krijgt je een NetworkStream waarover je het bestand moet versturen. Maar wat als je de clieop bestanden in een database wilt opslaan? Dan kun je eerst de clieop naar een MemoryStream schrijven waarna je de byte array in de database opslaat.

Dus door juist een stream mee te geven zijn de mogelijkheden groter, dan wanneer jij een stream terug geeft. Je zou inderdaad altijd een MemoryStream kunnen terug geven waarvan de data vervolgens naar een andere steam wordt geschreven (zoals een FileStream), maar dat lijkt mij dubbel en onnodig werk. Stream is een abstracte definitie en jij kunt dus alleen een implementatie van een stream terug geven. Doordat jij de keuze van die implementatie maakt, is de teruggave ook beperkt tot die stream.

Echter kun je wel erg eenvoudig de defintie van Stream gebruiken als argument, omdat het framework een FileStream, NetworkStream, MemoryStream of OutputStream netjes omzet tot stream.

Wat betreft je defintie dat een export niet bekend moet zijn met het formaat spreek je eigenlijk zelf al tegen door abstract over Transactie, Batch en Post toe spreken. Immers zal een TomTom export geen Batch object kennen. Je kunt eigenlijk dan niet veel meer defineren dan:
C#:
1
2
3
4
public interface IExport
{
    void WriteOutput(Stream outputStream);
}


Ik zelf vind het prettiger om het formaat te implemeren. Dus een BatchBase welke wordt gebruikt door IncassoBatch en BetalingBatch. Deze classes vormen bij mij altijd alleen de interface en niet de daadwerkelijk export implementatie. Daarvoor gebruik ik altijd aparte export classes welke IExport implementeren.

WriteOutput van ClieopBase (als parent van ClieopIncasso of ClieopBetaling) roept de export classes BestandVoorloopInfoRecord en BestandSluitRecord aan. Daartussen wordt via een loop de List<BatchBase> doorlopen welke zijn records en childs wegschrijft naar de stream.

Mijn clieop library is daarnaast ook stanalone library. Als voorbeeld ben ik nu bezig de laatste hand te leggen aan de laatste GMU (Geïntegreerde Mutatie Uitvoer) welke vanaf 28 januari gewijzigd zal zijn. GMU is de overtreffende trap van clieops en heeft ook support voor bijvoorbeeld acceptgiro's. Ook die heeft een 'interface' implementatie en een export record implementatie.

Na ruim 6 jaar verschillende soorten exports te hebben geschreven kan ik je vertellen dat er niet zoiets bestaat als een generieke export. Omdat de structuur van elke formaat anders is, zullen ook altijd je entiteiten anders zijn. Het enigste wat alle exports wel allemaal gemeen is dat ze een output hebben.

Dus je hebt een aantal classes voor de clieop implementatie, een aantal classes voor het TomTom formaat welke je eventueel een interface als IExport kunt geven.

If it isn't broken, fix it until it is..


  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 11:14
Niemand_Anders schreef op maandag 21 januari 2008 @ 12:49:
Wat betreft je defintie dat een export niet bekend moet zijn met het formaat spreek je eigenlijk zelf al tegen door abstract over Transactie, Batch en Post toe spreken. Immers zal een TomTom export geen Batch object kennen. Je kunt eigenlijk dan niet veel meer defineren dan:
C#:
1
2
3
4
public interface IExport
{
    void WriteOutput(Stream outputStream);
}


Ik zelf vind het prettiger om het formaat te implemeren. Dus een BatchBase welke wordt gebruikt door IncassoBatch en BetalingBatch. Deze classes vormen bij mij altijd alleen de interface en niet de daadwerkelijk export implementatie. Daarvoor gebruik ik altijd aparte export classes welke IExport implementeren.

WriteOutput van ClieopBase (als parent van ClieopIncasso of ClieopBetaling) roept de export classes BestandVoorloopInfoRecord en BestandSluitRecord aan. Daartussen wordt via een loop de List<BatchBase> doorlopen welke zijn records en childs wegschrijft naar de stream.

Mijn clieop library is daarnaast ook stanalone library. Als voorbeeld ben ik nu bezig de laatste hand te leggen aan de laatste GMU (Geïntegreerde Mutatie Uitvoer) welke vanaf 28 januari gewijzigd zal zijn. GMU is de overtreffende trap van clieops en heeft ook support voor bijvoorbeeld acceptgiro's. Ook die heeft een 'interface' implementatie en een export record implementatie.

Na ruim 6 jaar verschillende soorten exports te hebben geschreven kan ik je vertellen dat er niet zoiets bestaat als een generieke export. Omdat de structuur van elke formaat anders is, zullen ook altijd je entiteiten anders zijn. Het enigste wat alle exports wel allemaal gemeen is dat ze een output hebben.

Dus je hebt een aantal classes voor de clieop implementatie, een aantal classes voor het TomTom formaat welke je eventueel een interface als IExport kunt geven.
Ik heb niet duidelijk gezegd denk ik dat mijn TomTom implementatie weer geheel anders is :P. Maar over die ClieOp implementatie: ik heb zelf Transaction, Batch en Post. Deze laatste is abstract en wordt door twee klassen geïmplementeerd, te weten ReceivablePost en BusinessPaymentPost.

De WriteOutput heb ik gedefineerd in Transaction en loopt daarna door de batches en posts heen (niet zo'n recursieve WriteOutput zoals jij). Ik gebruik dus geen Output methoden in de Batch en Post klassen zelf. Is dit netter en/of heeft dit nog voordelen eigenlijk? Want juist naar dat soort 'tips' ben ik op zoek in dit topic :).

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Dennis schreef op maandag 21 januari 2008 @ 13:45:
Ik heb niet duidelijk gezegd denk ik dat mijn TomTom implementatie weer geheel anders is :P. Maar over die ClieOp implementatie: ik heb zelf Transaction, Batch en Post. Deze laatste is abstract en wordt door twee klassen geïmplementeerd, te weten ReceivablePost en BusinessPaymentPost.

De WriteOutput heb ik gedefineerd in Transaction en loopt daarna door de batches en posts heen (niet zo'n recursieve WriteOutput zoals jij). Ik gebruik dus geen Output methoden in de Batch en Post klassen zelf. Is dit netter en/of heeft dit nog voordelen eigenlijk? Want juist naar dat soort 'tips' ben ik op zoek in dit topic :).
Ik denk dat TomTom weinig kan met een bestand voor betalings verkeer.

Zoals aangeven heb ik 'interface'- en export classes. Misschien wordt het iets duidelijker als ik de code naar IncassoClieop.WriteOutput toon.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public override void WriteOutput(Stream outputStream)
{
    ClieopRecord record;

    //0001
    record = new BestandVoorloopRecord(this.identity, this.batchCount);
    record.WriteOutput(outputStream);

    //render batches
    foreach(BatchBase batch in this.batches)
        batch.WriteOutput(outputStream);

    //9999
    record = new BestandSluitRecord();
    record.WriteOutput(outputStream);
}


De implementaties van BatchBase en TransactionBase schrijven op een soort gelijke manier hun regels naar het export bestand. De 'interfaces' classes implementeren dus de hiërarchische structuur en doen de aansturing van de export. De classes welke in mijn geval ClieopRecord implementeren schrijven het eigenlijke clieop bestand naar de stream. ClieopRecord in een abstracte class welke in zijn constructor de infocode, variantcode en het aantal opvulspaties wil meekrijgen.

Daarbij vind ik het zelf erg prettig werken als structuur en formaat gescheiden blijven. Eigenlijk om dezelfde redenen waarom je bij een website data, logica en presentatie gescheiden wilt houden. De 'interface' classes handelen de logica af en de format implementatie (record) classes doen de presentatie.

Bijkomend voordeel in mijn geval was dat ik voor het GMU export bestand heb ik vrijwel alle logica en presentatie classen hebt kunnen hergebruiken. Omdat vrijwel alle methodes van base classes met abstract of virtual zijn gemarkeerd kon je vrij eenvoudig grote delen hergebruiken. Daarbij omdat mijn record classes los staan van de logica kan ik ook vrij eenvoudig unit-tests voor de record classes schrijven.

If it isn't broken, fix it until it is..


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

kijk eens naar de System.Runtime.Serialization namespace.

ASSUME makes an ASS out of U and ME


  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 11:14
rwb schreef op vrijdag 18 januari 2008 @ 16:17:
ik zou iets van een interface maken.
Zo kan je ook een IFileReader maken met een methode ReadFromStream. Op deze manier heb je gewoon een soort Serialize/Deserialize constructie gemaakt.
Verwijderd schreef op maandag 21 januari 2008 @ 11:15:
Klinkt mij een geval voor een Serializer. Maar ik kan het natuurlijk mis hebben.
H!GHGuY schreef op maandag 21 januari 2008 @ 19:48:
kijk eens naar de System.Runtime.Serialization namespace.
Ok, ik let natuurlijk ook wel op, maar nu wil ik graag weten: wat bedoelen jullie :?.

Ik heb nu even gekeken, maar wordt hier gesuggereerd dat ik een eigen Formatter moet maken (die IFormatter implementeert) en een eigen 'Serializer'? Want dat is me dus niet helemaal duidelijk, maar het klinkt architectuur-technisch gezien erg goed!

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Je kunt inderdaad je eigen formatter schrijven. Unit tests voor formatters zijn complexer omdat je dan clieop implementaties gaat testen. mijn clieop regels elk door een eigen classe worden geschreven, kan ik zeer eenvoudig elke record expliciet testen.

Omdat ik dan 100% zeker ben dat de formaat regels correct zijn, is het eenvoudig om unit tests voor de logica classes te schrijven. Ik ben werkzaam in de financiele sector en de bedrijven die onze componenten/classes gebruiken eisen dat software 100% correct werkt onder alle omstandigheden. Wij hebben gecontracten met applicatie software getekend waarbij claims bij fouten beginnen bij een miljoen euro.

Wat als je een serializer gaat schrijven, dat je dat voor de hoogste container class (Transactie in jouw geval). Dat houd dat je binnen de serializer zelf andere serializers (IFormatter) aanroept of dat je de complete clieop logica in de formatter van Transaction moet opnemen.

Dus als je voor de serializer oplossing gaat dan raad ik je aan voor elke class een eigen serializer te maken en dat je de output van elke serializer naar de hoofd stream schrijft. Omdat serializers een 1-op-1 relatie met hun data class (Transaction, Batch, Post) hebben zul je dus ook altijd die combinatie moeten testen. Omdat het formaat dan niet zonder logica afzonderlijk getest kan worden, hebben wij ervoor gekozen om niet gebruik te maken van de serializers.

Ik weet dat een concurrent van ons welke gebruik maakt van Java (niet heel erg verschillend van C#), maar wel gebruik maakt van de serializers, waarschijnlijk niet de Postbank GMU deadline van 28 januari gaan halen omdat de formatter een probleem geeft bij een acceptgiro transactie. Volgens hun nieuws brief komt hun component pas 4 februari op de markt.

Maar ik moet daarbij vermelden dat de serializer oplossing technisch wel mooier is, maar dat onze beslissing is gemaakt op basis van een risico analyse.

If it isn't broken, fix it until it is..


  • Dennis
  • Registratie: Februari 2001
  • Laatst online: 11:14
Niemand_Anders schreef op dinsdag 22 januari 2008 @ 14:06:
Je kunt inderdaad je eigen formatter schrijven. Unit tests voor formatters zijn complexer omdat je dan clieop implementaties gaat testen. mijn clieop regels elk door een eigen classe worden geschreven, kan ik zeer eenvoudig elke record expliciet testen.

[...]

Maar ik moet daarbij vermelden dat de serializer oplossing technisch wel mooier is, maar dat onze beslissing is gemaakt op basis van een risico analyse.
Leuk stukje achtergrond :). Ik begrijp je motivatie nu ook goed! Ik heb vanmiddag al zitten spelen met een BinaryFormatter en heb inmiddels een eigen formatter geschreven, ClieOpFormatter, die IFormatter implementeert.

Ik begrijp je tip over het maken van Formatters voor elke klasse afzonderlijk maar ik vraag me af wat het in mijn situatie toevoegt. Post en Batch zijn (op dit moment) onlosmakelijk verbonden met Transaction. Misschien ga ik na mijn ClieOp avontuur ook spelen met andere formaten (MT940 en die GMU klinkt ook leuk) en wijzigt die situatie nog, maar voor nu kan ik dus denk ik net zo goed bij één Formatter blijven.

Gewoon uit interesse eigenlijk, maar werk je dan bij een bank of zijn jullie een softwarehouse die gewoon veel financiële instellingen als klant hebben?

  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Dennis schreef op dinsdag 22 januari 2008 @ 14:33:
[...]
Leuk stukje achtergrond :). Ik begrijp je motivatie nu ook goed! Ik heb vanmiddag al zitten spelen met een BinaryFormatter en heb inmiddels een eigen formatter geschreven, ClieOpFormatter, die IFormatter implementeert.

Ik begrijp je tip over het maken van Formatters voor elke klasse afzonderlijk maar ik vraag me af wat het in mijn situatie toevoegt. Post en Batch zijn (op dit moment) onlosmakelijk verbonden met Transaction.
Het gebruik van losse formatters voegt toe dat de formatter alleen logica heeft van de class welke wordt geserialiseerd. Child classes worden door hun eigen formatter geserialiseerd. Daarbij, als je ooit besluit om de incasso en betaling classes te scheiden, is het erg eenvoudig om voor beide hun eigen formatter aan te roepen. De formatter kun je dan eigenlijk opnemen in WriteOutput() welke zijn serialisatie toevoegt aan de stream. Op die manier hoeft Transactie dus niet te weten welke formatter Batch of later IncassoBatch nodig heeft. Dat heb je dan verwerk in de betreffende classe zelf.

In principe mag een object geen logica van een andere class bevatten. Hij mag wel natuurlijk wel child classes gebruiken en aansturen. Eigenlijk het basis principe van OO-based programmeren.
Misschien ga ik na mijn ClieOp avontuur ook spelen met andere formaten (MT940 en die GMU klinkt ook leuk) en wijzigt die situatie nog, maar voor nu kan ik dus denk ik net zo goed bij één Formatter blijven.
Die keuze is volledig aan jouw. Het ligt er natuurlijk ook aan hoeveel je in de toekomst nog verwaht te wijzigen aan de class. Want net als bij HTML bij browsers, wijkt ook een aantal banken licht af van de clieop standaard. Fortis ondersteund bijvoorbeeld niet de processing datum '000000'.
Gewoon uit interesse eigenlijk, maar werk je dan bij een bank of zijn jullie een softwarehouse die gewoon veel financiële instellingen als klant hebben?
Wij zijn een software huis(je) welke software ontwikkelen voor banken zoals DSB Bank en BinckBank, maar ook aan bedrijven welke incasso's en/of betaling automatisch via interpay willen laten verwerken. Maar ook een aantal websites welke eenmalige machtigingen vanaf hun website in hun bankier software willen importeren.

Alleen worden fouten (bugs) in de financiele sector zwaar gestraft. De meeste banken eisen ook dat InterPay (toezichthouder elektronisch bank verkeer in Nederland) de software certificeerd. En die certificering dient voor elke versie uitgevoerd te worden. Vandaar dat de unit- en regressions test zo belangrijk voor ons zijn.

If it isn't broken, fix it until it is..


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Ik geef in dit soort gevallen meestal ook het object zelf de verantwoordelijkheid om zichzelf naar een stream te serializen ( MBV een soort interface als ik in mijn eerste post gaf ), het is dan inderdaad makkelijker om met verschillende soorten child classes te werken.

Je zou inderdaad ook per class een aparte formatter/serializer kunnen maken als je classes al erg complex zijn. Je zou dan doormiddel van een interface of een attribute aan kunnen geven welke class verantwoordelijk is voor het serializen van een class.

Een andere oplossing die ik wel eens heb gezien is per Member van een class aangeven hoe deze geserializeerd moet worden. Je kan dan een generieke serializer maken die door middel van de Attributes de hele class en zijn child classes serializeerd ( Een beetje zoals het ook in WCF werkt met datacontracts )

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

rwb schreef op dinsdag 22 januari 2008 @ 20:06:
Ik geef in dit soort gevallen meestal ook het object zelf de verantwoordelijkheid om zichzelf naar een stream te serializen ( MBV een soort interface als ik in mijn eerste post gaf ), het is dan inderdaad makkelijker om met verschillende soorten child classes te werken.

Je zou inderdaad ook per class een aparte formatter/serializer kunnen maken als je classes al erg complex zijn. Je zou dan doormiddel van een interface of een attribute aan kunnen geven welke class verantwoordelijk is voor het serializen van een class.
De serialisatie per class heeft wat mij betreft weinig te maken met de complexiteit daarvan. De logica van class B hoort simpelweg niet in class A. En dat is wel wat er gebeurt als je 1 formatter schrijft voor al je clieop classes. Het is alsof je op een bij een website een winkelmandje presentatie (formatering van data) de verantwoordelijk geeft voor het opslaan van wijzigingen. Daarvoor gebruik je verschillende lagen (DAL, business en presentatie igv 3-tier), dus waarom niet gewoon altijd logica en presentatie scheiden. Ik zelf vind het een goede gewoonte volg dit principe al sinds 2000, ook voor component en windows programming. Daarom is WPF voor mij een zegen, want eindelijk is dan nu echt de formulier code gescheiden van de presentatie.
Een andere oplossing die ik wel eens heb gezien is per Member van een class aangeven hoe deze geserializeerd moet worden. Je kan dan een generieke serializer maken die door middel van de Attributes de hele class en zijn child classes serializeerd ( Een beetje zoals het ook in WCF werkt met datacontracts )
Als je per class gaat serialiseren, hoef je op zich geen markeringen (attributen) te gebruiken. Immers het serialiseren kun je gewoon achter de schermen doen. Batch.WriteOutput doet dan gewoon zelf de serialisatie
C#:
1
2
3
4
5
6
7
8
9
public class Batch
{
   ...
   public void WriteOutput(Stream outputStream)
   {
       ClieopBatchFormatter fmt = new ClieopBatchFormatter();
       fmt.Serialize(this, outputStream);
    }
}

De batch serializer herkent herkent natuurlijk wel de post objecten en via een enumaratie roept deze voor elke Post de betreffende WriteOutput methode aan. Je hebt dus straks 3 fomatters (ClieopTransactionFormatter, ClieopBatchFormatter en ClieopPostFormatter) welke door de classes zelf worden aangeroepen. De formatters behoren namelijk verborgen te zijn voor je gebruikers. Als je GMU wilt implementeren, is het niet verstandig om de formatters als internal te declareren. Je kunt ze dan niet herbruiken in een andere assembly.

Dus je start de export met transaction.WriteOutput(fs) en deze weet zelf welke serializer nodig is, zichzelf doorgeeft aan de serializer en op het juiste punt worden de child objecten geserialiseerd.

If it isn't broken, fix it until it is..


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Niemand_Anders schreef op woensdag 23 januari 2008 @ 09:36:
[...]
De serialisatie per class heeft wat mij betreft weinig te maken met de complexiteit daarvan. De logica van class B hoort simpelweg niet in class A. En dat is wel wat er gebeurt als je 1 formatter schrijft voor al je clieop classes.
Je begrijpt niet helemaal wat ik bedoel. Ik bedoel ook per class een serializer, maar ik bedoel dat je er voor kan kiezen om de class zichzelf te laten serializen of om een exteren serializer voor die class te maken. Je kan de class dan door middel van een interface of een attribute aan laten geven welke class hem kan serializen.
Als je per class gaat serialiseren, hoef je op zich geen markeringen (attributen) te gebruiken. Immers het serialiseren kun je gewoon achter de schermen doen. Batch.WriteOutput doet dan gewoon zelf de serialisatie
Met de attributen manier bedoel ik weer wat anders. Daarbij heb je wel gewoon een Serializer die aan de hand van reflection (en toegevoegde attributes) de classes serializeert. Als alles op een generieke manier geserializeerd word scheelt dat je per class serialisatie code schrijven.

bijvoorbeeld zo
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[MySerializable]
class ObjA
{
    [MySerializableField( typeof(string), MyStringFormat.FixedLength, 30 )]
    public string Name{ get{..} set{..} }

    [MySerializableField]
    public ObjB MyBObj { get{..} set{..} }

    public int SomethingNotInFile{ get{...} set{...} }
}

[MySerializable]
class ObjB
{
    [MySerializableField( typeof(string), MyStringFormat.FixedLength, 30 )]
    public string Name{ get{..} set{..} }

    public int SomethingNotInFile{ get{...} set{...} }
}


Door middel van Reflectie kun je dan een generiek serializer maken. Eventueel zou je nog een mechanisme in kunnen bouwen waardoor je ook nog een eigen Custom Serializer aan kan geven bij een class. Het voordeel is dat je geen serializatie code hoeft aan te passen op het moment dat er iets in het formaat wijzigt. Maar het is natuulijk alleen toepasbaar als alle data redelijk generiek opgeslagen word.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


  • Niemand_Anders
  • Registratie: Juli 2006
  • Laatst online: 09-07-2024

Niemand_Anders

Dat was ik niet..

Dat betekend dus ook dat je de logica classes aanpast aan de presentatie, omdat bijvoorbeeld de BatchSluitInfo (9990) regel bestaat uit meerdere velden (aantal posten, checksum rekeningnummers, totaal bedrag van posten in batch). Daarvoor zul je in jouw opzet dus een aparte readonly property voor moeten maken waarop je dan het attribute kunt zetten. Op de losse properties zelf komt geen attribute.

Daarmee vervuil je dus in principe het design van je clieop classes. Maar InterPay is momenteel bezig met een XML variant van het Clieop formaat. Deze is pas slechts 3 jaar vertraagd (stond officieel voor 1 juli 2005 geplanned). Wij hebben dus ook al langere tijd op ClieopBase niveau een ClieopFormat property welke verwijst naar een ClieopFormat enumeratie (Edi en Xml) waarbij Edi standaard wordt gebruikt.

Omdat wij de clieop formatering volledig los hebben van de interface classes kunnen wij vrij eenvoudig switchen tussen de verschillende presentatie formaten.

Zoals ook al in de vorige post aangegeven is het gebruik van serialisatie en attributen technisch gezien een mooie oplossing. Theoretisch ook misschien zelfs de beste oplossing voor .NET, maar helaas wijkt de praktijk vaak af van de theorie.

Onze methode is simpel en direct waarbij we ook nog eens code en presentatie gescheiden houden. Ook afwijkingen voor bepaalde partijen vormen geen probleem. Daarmee hebben wij in de bancaire wereld inmiddels een zeer betrouwbare reputatie verworven. Ik zeg niet dat dat niet mogelijk is als er gebruik gemaakt wordt, maar de eenvoud van onze code heeft daar zeker wel aan bijgedrage.

If it isn't broken, fix it until it is..

Pagina: 1