Results 1 to 15 of 15

Thread: Bepaalde componenten als parameters doorgeven aan een (data)model

Hybrid View

  1. #1
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,747

    Bepaalde componenten als parameters doorgeven aan een (data)model

    Ik heb in mijn form 2 componenten staan : TEdit en TMemo.
    Nu moet ik voor het XML bericht NAW gegevens ophalen. In de exit procedure roep ik een model unit aan. Deze procedure roept een datamodel procedure aan om de gegevens uit de database te halen. Voor beide procedures geef ik een TStrings mee van de memo. Dat werkt prima. Maar hoort de TStrings wel in de datamodel. In principe is het datamodel alleen ervoor om data op te halen en e.v.t. door te geven aan een result van een function.

    Is er ergens een lijst van functies / componenten, die in het grijze gebied gebruikt mag worden (model en datamodel)?
    Delphi is great. Lazarus is more powerfull

  2. #2
    Een DataModule is ook gewoon maar net wat je ermee doet. Zoals je waarschijnlijk weet heeft Delphi de class TComponent, waarmee je je applicatie in elkaar kan zetten alsof het lego is. Je form is een component, het panel erop is een component. Een component heeft een owner en die owner maakt het makkelijk om te bepalen hoe lang zo'n component leeft. Als je het form vrijgeeft, dan gaan alle objecten erop mee. Als je Application vrijgeeft, dan gaan al z'n forms mee. Het is de ownership die dat bepaalt. En je kan componenten samenvoegen tot een groter component. Dat is ongeveer wat een TFrame doet. Je definieert een stukje form dat je kan hergebruiken op meerdere plaatsen. Het form ownt het frame, en het frame ownt de componenten erop. Je zou ook zelf compound components kunnen maken die bestaan uit meerdere components. TLabeledEdit is daar een voorbeeld van.

    Een form is eigenlijk zelf ook zo'n samengesteld component. Daarbij heeft de Delphi IDE een handige manier om die forms te bewerken. Je kan componenten op een form neerzetten en verslepen, en je kan allerlei properties veranderen die in een speciale indeling (dfm) opgeslagen worden, zodat je ze niet allemaal in code hoeft uit te schrijven. Alleen is een form een (runtime) zichtbaar component, met een window handle en alle rompslomp die daarbij komt kijken, en dat is niet altijd even handig als je bijvoorbeeld alleen een stuk business logica wil implementeren, of alleen naar een database wilde schrijven, zeker als je dat in een service of in een console applicatie wilde doen...

    En daar is dan je datamodule. TDataModule (niet DataModel, maar DataModule) is een heel dun laagje om TComponent heen. TDataModule bestaat alleen om de visuele editor van de IDE te kunnen gebruiken voor het inrichten van zo'n compound component. TDataModel is zelf helemaal niets. Je moet het gewoon zien als een TComponent, puur voor het samenstellen. Kijk maar eens in de code van TDataModule. Hij is direct afgeleid van TComponent en bevat alleen code voor het streamen naar de dfm, want dat is z'n doel. En zo moet je het ook zien, niet meer niet minder. Waarom heet ie dan DataModule? Geen idee, ik vermoed dat het ding gemaakt is met datakoppeling in het achterhoofd, en daar is hij door de jaren heen natuurlijk ook veelvuldig voor gebruikt, gezien Delphi's historisch sterke achtergrond in database-applicaties.

    Goed. Je bent object georienteerd aan het programmeren. Je hebt een form class die data toont en bewerkt, en je hebt een andere classes die business logica bewaken, of die opslaan naar een database, of die .... noem maar op. Om je applicatie zo schoon en netjes mogelijk te houden wil je graag classes hebben die één specifieke taak hebben, en die bij voorkeur niet zo erg afhankelijk zijn van andere classes. Je wilt dus zeker niet dat de class (toevallig van TDataModule afgeleid) die naar je database schrijft afhankelijk is van je form. Dat maakt het namelijk erg lastig om die database-code te hergebruiken in een ander deel van je applicatie (of in een andere applicatie). En eigenlijk wil je liever ook niet dat je form al te veel afhankelijk is van je datamodule, maar goed, soms is het ook wel lekker makkelijk, en zonder die datamodule heb je toch niets aan dat form..

    Het is dus geen slecht idee om vanuit je form data in abstracte vorm (zoals een string of een TStrings) naar je datamodule te sturen. Op die manier is er geen directe afhankelijkheid naar het form.

    Mag TStrings in een datamodule? Ja, in principe zou je zelfs TEdits naar een datamodule kunnen sturen. Een TDataModule is geen andere thread. Zoals gezegd is TDataModule alleen maar een TComponent waar jij zelf functionaliteit aan hangt, en misschien is die specieke datamodule wel een TEditManager ofzo. Maar goed, zelfs als je je daar ongemakkelijk bij voelt: TStrings is überhaupt geen component, laat staan een visueel component. Het is een abstracte base class voor een lijstje strings, direct afgeleid van TObject, en mag prima gebruikt worden in een datamodule.

    Maarrrrrr er zijn TStrings die bij je form horen en er zijn TStrings die bij je datamodule horen, dus dat de class daar gebruikt mag worden wil niet zeggen dat het ook logisch of verstandig is om Memo1.Lines direct door te geven aan je datamodule. Per slot van rekening is de specifieke implementatie van TMemoStrings niet super efficient, en wrapt deze allerlei API calls die de inhoud van je memo manipuleren. Je datamodule is er dus misschien wel bij gebaat om een efficientere afgeleide te krijgen om lekker op los te gaan.

    Daar staat dan weer tegenover dat TDataModule, juist omdat hij niet weet wat voor strings het is, misschien beter zijn eigen TStringList instantie kan maken om alle zware acties op los te laten, en deze pas naderhand te assignen aan de meegegeven TStrings.

    En tot slot kun je je dan nog afvragen wat een TStrings nou eigenlijk is. Is het logisch dat het een TStrings is, of wil je misschien gewoon een String doorgeven? Of misschien wel een specifieker type (een record, een class?) Die keuze is op zich helemaal aan jou zelf. De vraag is in dit geval niet zozeer wat er technisch mag, maar hoe je de onderdelen van je applicatie logisch gescheiden kan houden. Detailvragen daarbij zijn: Moet je form wel XML berichten opbouwen? Moet het component (de datamodule) dat verantwoordelijk is voor lezen uit de database ook wel verantwoordelijk zijn voor het opbouwen van een XML-bericht? Of heb je misschien een derde class nodig, en misschien nog wel eentje extra?

    Dus...

    Wat was je vraag ook alweer?
    Last edited by GolezTrol; 01-May-18 at 00:49.
    1+1=b

  3. #3
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,747
    Oke, Oke. Het blijft dus een grijs gebied. Zoals de modellen, zoals MVVM en MVC zeggen, moet elk model los geprogrammeerd worden. Het resultaat vanuit het datamodel (hoeft niet specifiek een datamodule te zijn; non visuele componenten kunnen @runtime worden aangemaakt) zal dan doorgegeven moeten worden aan het model (aparte unit met alle procedures / functions) en die zal het weer moeten doorgeven aan het viewmodel. Dan is alles los maar heel veel werk en omslachtig naar mijn gevoel. Wat is mooier dan een pointer meegeven aan een procedure, zodat deze zonder extra aanmaken van variabelen en dergelijke en dan te vullen voor het resultaat. Maar dan link je wel weer het ene naar het andere, waardoor een losstaand eenheid is, die onafhankelijk kan werken. Eigenlijk zijn die units net aparte threads omdat ze het bestaan van elkaar niet 'mogen' weten maar alleen het resultaat mogen doorgeven.

    Wat was mijn vraag ook alweer?

    Weet ik zelf ook niet meer

    Ik ben een klein programma aan het maken voor mijn zoon, zodat hij met zijn andere collega's een competitie kan spelen. Maar er zit zoveel werk in bij zo'n business model
    Delphi is great. Lazarus is more powerfull

  4. #4
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,708
    Quote Originally Posted by jkuiper View Post
    Maar er zit zoveel werk in bij zo'n business model
    Dan doe je iets niet goed.

    Een model is er juist voor dat er afspraken zijn over wie, wat, waar en wanneer doet; het hoe is aan die entiteit.
    Het model is er voor jou, jij bent er niet om het model te plezieren en is bedacht om het de ontwikkelaar makkelijk
    te maken en veel werk (vooral achteraf) uit handen te nemen.

    Je kunt beter vooraf een uur langer nadenken over je opzet/ontwerp/model, dan een week langer alles achteraf weer
    rechttrekken.

    Waar ging de vraag ook alweer over?
    TMemoryLeak.Create(Nil);

  5. #5
    Als het goed is zou (uiteindelijk) het opzetten van een nieuwe class een eitje moeten zijn. De complexiteit is wat er in die class gebeurt, maar die complexiteit heb je ook als je alles in één unit stopt. Alleen in het laatste geval heb je heel veel denkwerk, terwijl je bij een goede modulaire opbouw een deel van het denkwerk kan omzetten in typwerk, en zelfs dat typwerk is met de tools die de IDE biedt (class completion e.d.) maar minimaal.
    1+1=b

  6. #6
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,747
    Als ik dan gegevens van een leverancier wilt ophalen en tonen in een memo, zal het er zo uitzien als je gebruik maakt van de business logica:

    datamodel (datasource):
    Delphi Code:
    1. function TDMImporteren.DBHaalLeverancier(const aValue : string): TStrings;
    2. var MyQuery : TMyQuery;
    3. begin
    4.   MyQuery := TMyQuery.Create(nil);
    5.   Result  := TStringlist.Create;
    6.   try
    7.     MyQuery.Connection := DBConnection;
    8.     with MyQuery.SQL do
    9.     begin
    10.       add('SELECT l.naam, l.straat, l.postcode, l.plaats, la.naam AS landnaam');
    11.       add('FROM leveranciers l');
    12.       add('LEFT OUTER JOIN landen la ON l.land = la.land');
    13.       add('WHERE l.leverancier = :id');
    14.     end;
    15.     Myquery.params[0].AsString := aValue;
    16.     MyQuery.Active := true;
    17.     if MyQuery.RecordCount > 0 then
    18.     begin
    19.       Result.Add(MyQuery.FieldByName('naam').AsString);
    20.       Result.Add(MyQuery.FieldByName('straat').AsString);
    21.       Result.Add(MyQuery.FieldByName('postcode').AsString);
    22.       Result.Add(MyQuery.FieldByName('plaats').AsString);
    23.       Result.Add(MyQuery.FieldByName('landnaam').AsString);
    24.     end;
    25.     MyQuery.Active := false;
    26.   finally
    27.     MyQuery.free;
    28.   end;
    29. end;

    model (unit):
    Delphi Code:
    1. function TImporteren.HaalLeverancier(const aValue : string) : boolean;
    2. var sl : TStrings;
    3. begin
    4.   try
    5.     sl := datamodule.DBHaalLeverancier(aValue);
    6.     if sl.Count > 0 then
    7.     begin
    8.       fLevnaam := sl[0];
    9.       fLevstraat := sl[1];
    10.       fLevpostcode := sl[2];
    11.       fLevplaats   := sl[3];
    12.       fLevlandnaam := sl[4];
    13.       result := true
    14.     end else
    15.       result := false;
    16.   finally
    17.     sl.Free;
    18.   end;
    19. end;

    viewmodel (form):
    Delphi Code:
    1. procedure TFrmImporteren.ELeverancierExit(Sender: TObject);
    2. begin
    3.   if link.HaalLeverancier(ELeverancier.text) then
    4.   begin
    5.     MMOLeverancier.lines.Add(link.Levnaam);
    6.     MMOLeverancier.lines.Add(link.Levstraat);
    7.     MMOLeverancier.lines.Add(link.Levpostcode);
    8.     MMOLeverancier.lines.Add(link.Levstraat);
    9.     MMOLeverancier.lines.Add(link.LevLandnaam);
    10.   end;
    11. end;

    Kan de logica er wel in zien. Ik kan hier in plaats van een memo een listview plaatsen en de properties uitlezen. Ik kan zelfs kiezen om Tlabels te gebruiken.
    Delphi is great. Lazarus is more powerfull

  7. #7
    Misschien heb ik me dan te weinig verdiept in de recente ontwikkelingen. Wat is dat model? Beheert dat leveranciers? Of stelt dat één leverancier voor? Je slaat de gegevens van de leverancier op in velden van het object zelf, dus je TImporteren kan maximaal 1 leverancier voorstellen, maar kan ook nog 0 leveranciers voorstellen op het moment dat je er nog geeneen hebt geladen. Maar als het een leverancier voorstelt, waarom heet het dan TImporteren?
    En waarom geeft je datamodule een TStringList terug? Waarom geen Leverancier class of record?

    Ik ga eens kijken of ik een alternatief voorstel kan doen. Moet alleen even kijken wanneer..
    Last edited by GolezTrol; 01-May-18 at 13:48.
    1+1=b

  8. #8
    *+E13818MU01F0F* Norrit's Avatar
    Join Date
    Aug 2001
    Location
    Landgraaf
    Posts
    967
    Blij dat jij de logica hier van inziet, want ik ben het kwijt.

    Ik mis hier volledig je business laag. Je stopt nu logica in je model wat eigenlijk dom is.
    TImporteren is dan ook weer gek, want je implementatie is TLeverancier (1 enkele)

    Je business laag zou HaalLeverancier moeten implementeren, dus die doet de call naar de datamodule, krijgt dat TStrings object en vertaalt het naar een TLeverancier (TImporteren ga ik niet eens meer gebruiken hier, slechte naam!).
    Overigens is de implementatie van TLeverancier in jouw voorbeeld niets anders dan een afgeleide van TStrings waarbij de privates die je nu gebruikt overbodig zijn. Die kun je dan implementeren op zichzelf. Elke property krijgt dan een get/set met een aanroep naar de funcies GetAtIndex en SetAtIndex (nog zelf implementeren, misschien zelfs superclass erboven hangen)

    Maar ik heb het gevoel dat je in je eigen implementatie aardig wat shortcuts genomen hebt en/of bepaalde implementatie lagen niet begrepen
    Objective reality is a delirium caused by lack of alcohol in blood

  9. #9
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,708
    ...ook wil je geen geïnstantieerde klasse als resultaat van een functie hebben.

    Wil je een gevuld object als resultaat, dan maak je deze eerst aan voordat je de functie aanroept
    en geeft hem mee als (const) parameter.

    Delphi Code:
    1. function TDMImporteren.HaalLeverancier(const AID: Integer; const ALeverancier: TLeverancier): Boolean;
    2. begin
    3.   if Assigned(ALeverancier) then
    4.   begin
    5.     // Haal de data op, vul ALeverancier en geef TRUE terug indien gelukt
    6.   end
    7.   else
    8.     Result := False;
    9. end;
    10.  
    11. procedure TfrmMain.LeverancierKnop(Sender: TObject);
    12. var
    13.   L: TLeverancier;
    14. begin
    15.   L := TLeverancier.Create;
    16.   try
    17.     if DMImporteren.HaalLeverancier(123, L) then
    18.       // doe wat met L
    19.   finally
    20.     L.Free;
    21.   end;
    22. end;

    Of je moet een constructor maken, maar persoonlijk vind ik dat die erg kort en zo fout-ongevoelig
    mogelijk moet zijn en dan valt die optie dus eigenlijk meteen af.

    Vergeet ook niet dat het @runtime aanmaken van een component (vooral een dataset) redelijk
    duur is qua tijd.
    Moet je het een keertje doen, dan is het geen probleem, wil je daarentegen honderden keren
    achter elkaar zoiets doen, dan kun je beter een andere aanvliegroute pakken.

    Ik heb niet zo heel lang geleden het project van een ander "Geoptimaliseerd" door een TADOQuery
    niet steeds opnieuw aan te maken, maar gewoon te herbruiken en het scheelde echt verschrikkelijk
    veel in verwerkingstijd.
    Last edited by VideoRipper; 01-May-18 at 14:31.
    TMemoryLeak.Create(Nil);

  10. #10
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,747
    Dit is maar een heel klein stukje van het geheel. Het geheel maakt een XML met een reeks artikelen, die vanuit het buitenland komen, en stuurt deze naar een webservice. En omdat het produkten betreft uit het buitenland, wordt deze 'geimporteerd' naar de webservice. De naam is dus niet slecht.
    Quote Originally Posted by Goleztrol
    En waarom geeft je datamodule een TStringList terug? Waarom geen Leverancier class of record?
    Had ik ook kunnen doen. Maar ik heb even deze properties in de class TImporteren aangemaakt, om in mijn form er bij te kunnen komen.
    Delphi Code:
    1. property Levnaam      : string read fLevnaam write fLevnaam;
    2.     property Levstraat    : string read fLevstraat write fLevstraat;
    3.     property Levpostcode  : string read fLevpostcode write fLevpostcode;
    4.     property Levplaats    : string read fLevplaats write fLevplaats;
    5.     property LevLandnaam  : string read fLevlandnaam write fLevlandnaam;
    Quote Originally Posted by Norrit
    Je business laag zou HaalLeverancier moeten implementeren, dus die doet de call naar de datamodule, krijgt dat TStrings object en vertaalt het naar een TLeverancier
    .
    Hoe vertel je dan aan je form dat deze bij bijvoorbeeld een exit of onclick deze gegevens moet ophalen? Heel veel frameworks hebben binding naar een component, die op je form staat. Ik wil niet gebruik maken van die tool. Dan ben ik genoodzaakt om een eigen class te maken, die de bindngs uitvoert, zodat als een property gebruikt wordt, deze koppeling uitvoert (zie bijlage).

    Ben ik dan niet GUI componenten aan het koppelen aan de tussenlaag (model), welke eigenlijk los moet staan (naar mijn principe).

    Vergeet niet dat ik alleen maar wat gegevens wilt ophalen van de database (in dit geval NAW gegevens van een leverancier) en dat in TImporteren nog veeeeeeeel meer gebeurt
    Attached Files Attached Files
    Delphi is great. Lazarus is more powerfull

  11. #11
    *+E13818MU01F0F* Norrit's Avatar
    Join Date
    Aug 2001
    Location
    Landgraaf
    Posts
    967
    Hoe Bindings in elkaar steekt weet ik niet, nooit mee gewerkt en ga ik ook niet meer doen.

    Maar je zegt eigenlijk dat TImporteren heel veel meer doet, maar geeft het wel properties van een klasse, wat weer suggereert dat het geen business object is maar een model. Iets klopt er niet in je structuur...
    En je hebt gelijk, UI en tussenlaag (business objects, niet model) moet je scheiden. Even Bindings los gelaten, want nogmaals, daar weet ik niets van, in je UI mag je echt wel een call doen op een business object (HaalLeverancier). Maar HaalLeverancier vult geen random properties in je business object, maar retourneert daadwerkelijk een TLeverancier.
    Hoe wil je eigenlijk met hetzelfde business object (TImporteren) anders een lijst van leveranciers ophalen? Private anonieme lijst van objecten vullen? Of als TImporteren ook een HaalMedewerker functie krijgt (wat eigenlijk weer een ander business object zou moeten zijn, maar dat terzijde), krijg je dan medadres properties?
    Hele truc is net dat je middels objecten gaat werken tussen de lagen in, en UI werk je eigenlijk alleen nog maar met objecten.

    En vergeet niet dat "alleen maar wat gegevens ophalen van de database" nietszeggend is. Er zijn hele applicaties die voor 80% (als het niet meer is) niets anders doen. En de andere 20% is dan data wegschrijven. Zoiets heet een database applicatie, maar die heb je vast en zeker ook al vaker gebouwd. Dat code technisch data ophalen minder logica bevat als wegschrijven en daarom ook korter/overzichtelijker is doet daar weinig afbreuk aan.
    Objective reality is a delirium caused by lack of alcohol in blood

  12. #12
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,747
    Peter, hoe vertel ik aan de datamodule dat tLeverancier bestaat in een andere unit. Dat kan alleen als deze in de uses staat van de datamodule. Maar TLeverancier krijg ook de datamodule mee in de uses. Krijg je dan geen 'circular reference'?
    Delphi is great. Lazarus is more powerfull

  13. #13
    TLeverancier moet (m.i.) geen datamodule meekrijgen. De datamodule kan een TLeverancier maken. TLeverancier heeft maar één doel: Een leverancier zijn. Je kan andere classes hebben die daar iets mee doen, zoals bewerken (je form), opslaan in je database, contrueren op basis van data uit de database, serialiseren naar XML... Al die classes die iets doen met een leverancier mogen de unit uLeverancier met daarin de class (of record?) TLeverancier usen. Maar niet andersom.
    Last edited by GolezTrol; 01-May-18 at 18:38.
    1+1=b

  14. #14
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,747
    Ik ben u even MVVM in delphi aan het lezen. Ben daar niet helemaal mee eens, maar kom er op terug.
    Delphi is great. Lazarus is more powerfull

  15. #15
    Tja, er zijn natuurlijk verschillende patterns om dit op te lossen, en MVVM is er maar een van. Daarbij zijn er heel veel nuances te bedenken in hoe je het precies implementeert. Zelfs be beknopte beschrijving van MVVM op Wikipedia noemt al dat het model een domain model kan zijn (object georenteerde representatie van je domeinobjecten, zoals TLeverancier), of een data access layer. Ik zou zelf voor het eerste kiezen, zodat je in je code logische objecten hebt, waar je logische (vanuit je real life processen) handelingen op kan doen. Het daadwerkelijk opslaan in de database is dan maar een detail.
    1+1=b

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •