Recent

Author Topic: Ideas for class design needed  (Read 3635 times)

asdf1337

  • Jr. Member
  • **
  • Posts: 56
Ideas for class design needed
« on: January 19, 2020, 12:29:14 am »
Hello people,
I need some suggestions on a problem I've faced during extending existing Delphi&FPC code.

1. One function retrieves some data over the network and should fill the TData class.
2. This TData dataset should then be used in TDatabaseRecord where it is saved into a database.
3. The TData should also go to TAccessData where it is actually used in the program.

Code: Pascal  [Select][+][-]
  1.   // kind of contract between TDatabaseRecord and TAccessData
  2.   // so that all data is accessible/known from both classes
  3.   TData = class
  4.   private
  5.     FID: String;
  6.     FTitle: String;
  7.     FLastUpdated: TDateTime;
  8.   published
  9.     property ID: String read FID write FID;
  10.     property Title: String read FTitle write FTitle;
  11.   end;
  12.  
  13.   // saves to database
  14.   TDatabaseRecord = class
  15.   private
  16.     FData: TData;
  17.   published
  18.     property ID: String read FData.FID write FData.FID;
  19.     property Title: String read FData.FTitle write FData.FTitle;
  20.   end;
  21.  
  22.   // existing class which already inherits from a class which don't need to know about TData
  23.   TAccessData = class(TAnotherClass)
  24.   private
  25.     FData: TData;
  26.   published
  27.     property ID: String read FData.FID write FData.FID;
  28.     property Title: String read FData.FTitle write FData.FTitle;
  29.   end;
  30.  

Unfortunately it doesn't work with the property with FData.FID: Error: Record or object type expected

Would like to get some ideas on the class design and solutions how to fix it :-[
Thanks! :D

PaulRowntree

  • Full Member
  • ***
  • Posts: 132
    • Paul Rowntree
Re: Ideas for class design needed
« Reply #1 on: January 19, 2020, 02:16:12 am »
It looks like you are duplicating TData several times.
Could you1)Read the information, stuff it into a TData2) pass the TData to the a database code that constructs the record with whatever other information is needed, then inserts the record into the database. 
3) release the TData, because the database now has it. 
4) use std database access routines to pull the data as needed, without duplication.

Am I making sense?  Would this work?
Paul Rowntree
- coding for instrument control, data acquisition & analysis, CNC systems

jamie

  • Hero Member
  • *****
  • Posts: 6077
Re: Ideas for class design needed
« Reply #2 on: January 19, 2020, 02:57:28 am »
The TAccessData.TData should be TDatabaseRecord

so FDataDatabaseRecord.fDataBase….. etc...


Understand ?

 Otherwise you have two Tdata, and both are really doing nothing..

The only true wisdom is knowing you know nothing

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Ideas for class design needed
« Reply #3 on: January 19, 2020, 03:21:09 am »
As for the properties, you will need getter methods. The field forwarding only works for records (as they can not be nil, but classes/objects can be nil and the forward would fail).

As for the structure....

Why a separate TDatabaseRecord to save the data? Could TData not simply inherit for the correct base?

Or if there are reasons, such as there may be more than one TDatabaseRecord for a specific TData (in diff tables, or diff DB, or ???), then maybe have a TDataRecordWriter and TDatabaseRecordReader.
Both of which would NOT have a field for the data. The would take data as an argument to their save/load method. So you only have one Reader/Writer (or combined DbAccessor), and that one reader/writer deals with any TData.

Also depending on your needs TData could have a field for its reader/writer, and then you call Save/Load on TData, which will pass itself to the reader/writer.

All depends on what you want to do.

asdf1337

  • Jr. Member
  • **
  • Posts: 56
Re: Ideas for class design needed
« Reply #4 on: January 19, 2020, 06:52:57 pm »
The main idea for these structure was to separate the different areas of the program from each other so that they only knew what they need to know and I'm only passing/copy around the TData. And also that a programmer knows that if you add a new field to TData that you have to adapt the other two classes which do stuff with it. I also thought about using interfaces for that until I realized that this only works for functions...

@PaulRowntree
Haha yes, got that. Didn't really thought about this approach %)
Only issue is that I've to receive data from the DB again which I already have in memory...not efficient?

@jamie
Sorry but don't know what your talking about :-[

@Martin_fr
I could also use a TRecord instead a class for the dataset. That would allow me to use the property as shown, right?
The TDatabaseRecord inherits from the base class of mORMot framework for saving to a database.

« Last Edit: January 19, 2020, 06:56:50 pm by asdf1337 »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Ideas for class design needed
« Reply #5 on: January 19, 2020, 07:13:32 pm »
The main idea for these structure was to separate the different areas of the program from each other so that they only knew what they need to know and I'm only passing/copy around the TData. And also that a programmer knows that if you add a new field to TData that you have to adapt the other two classes which do stuff with it. I also thought about using interfaces for that until I realized that this only works for functions...

@Martin_fr
I could also use a TRecord instead a class for the dataset. That would allow me to use the property as shown, right?
The TDatabaseRecord inherits from the base class of mORMot framework for saving to a database.

Yes... But...

If TData is a record, and you pass it around, then each (each field "FData" and each param "AData") is a separate copy.
Changes to one record, do not affect the other records.
- For params (function params) you could use "var" param, and that will pass a pointer to the original.
- But for fields, you can not do that. Of course you can make the field a pointer to a record, but then you again cannot use the forwarding property.....

So using records you may end up with a lot of work keeping your data in sync.

If Everything only works with TData, then why do the other classes need forward properties?
No external code should need to access TDataRecord.Id or TDataAccess.Id. 
Any such external code should work with a TData?


Side note: Do all those classes need "published" fields? Do they use RTTI to access those fields?


While I still have no clue about TDataAccess, let me take a guess on TDataRecord.

Do you use some sort of ORM, and it has a baseclass for objects that can be stored in a table?
So TDataRecord is a wrapper around TData, in order to get a class that the ORM can store?

Then TDataRecord would need the published fields for the Orm, but TData would not.

The best way forward would then depend on what other means the ORM offers to deal with data.....




asdf1337

  • Jr. Member
  • **
  • Posts: 56
Re: Ideas for class design needed
« Reply #6 on: January 19, 2020, 07:35:38 pm »
The main idea for these structure was to separate the different areas of the program from each other so that they only knew what they need to know and I'm only passing/copy around the TData. And also that a programmer knows that if you add a new field to TData that you have to adapt the other two classes which do stuff with it. I also thought about using interfaces for that until I realized that this only works for functions...

@Martin_fr
I could also use a TRecord instead a class for the dataset. That would allow me to use the property as shown, right?
The TDatabaseRecord inherits from the base class of mORMot framework for saving to a database.

If TData is a record, and you pass it around, then each (each field "FData" and each param "AData") is a separate copy.
Changes to one record, do not affect the other records.
- For params (function params) you could use "var" param, and that will pass a pointer to the original.
- But for fields, you can not do that. Of course you can make the field a pointer to a record, but then you again cannot use the forwarding property.....

So using records you may end up with a lot of work keeping your data in sync.

It doesn't need to be sync as it gets/searches something over the network -> save to db -> show it in app and then the TData can be freed as it is part of the TAccessData as class variables (maybe naming it TViewData would've been better)
Could probably be like that as well:
Code: Pascal  [Select][+][-]
  1.   TViewData = class(TAnotherClass)
  2.   private
  3.     FID: String;
  4.     FTitle: String;
  5.   public
  6.     constructor Create(AData: TData); // create assign all members of TData to the class fields
  7.     property ID: String read FID write FID;
  8.     property Title: String read FTitle write FTitle;
  9.   end;
  10.  

Side note: Do all those classes need "published" fields? Do they use RTTI to access those fields?
The class TDatabaseRecord which stores it to DB, yes. That's how the ORM knows what to save. For the others public declaration should be fine.


While I still have no clue about TDataAccess, let me take a guess on TDataRecord.

Do you use some sort of ORM, and it has a baseclass for objects that can be stored in a table?
So TDataRecord is a wrapper around TData, in order to get a class that the ORM can store?

Then TDataRecord would need the published fields for the Orm, but TData would not.

The best way forward would then depend on what other means the ORM offers to deal with data.....

The ORM is Synopse mORMot Framework where the base class is TSQLRecord so TDatabaseRecord = class(TSQLRecord) and everything which should be saved to DB needs to be declared as published.

PaulRowntree

  • Full Member
  • ***
  • Posts: 132
    • Paul Rowntree
Re: Ideas for class design needed
« Reply #7 on: January 19, 2020, 09:01:38 pm »
OK, you have several more capable people than me involved ... which is a good thing ...
"Only issue is that I've to receive data from the DB again which I already have in memory...not efficient?"
Any use of a DB is an abstraction, and is usually done to insulate you from these questions.  Unless you are short of memory, I would say that the decision to use a database means you never want to have other copies of the information floating about.  When you need something, ask for it from the DB.  If you need to change it, grab it, change it, re-install it.  The DB is the primary datastore and everything else is like a client that uses it on demand. 


Paul Rowntree
- coding for instrument control, data acquisition & analysis, CNC systems

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Ideas for class design needed
« Reply #8 on: January 19, 2020, 09:39:20 pm »
I don't use mormot myself, so I do not know if it has other ways of "reading on object".

E.g., TWriter for streaming of forms to lfm, has such other ways: https://www.freepascal.org/docs-html/rtl/classes/twriter.defineproperty.html

Maybe something in Mormot can be subclassed overwritten, so the RTTI can be redirected to TData. Then TRecordData does not need the published methods?



Anyway lets recap.
- You read the data from the db (and/or write it)
- Then data gets handed around. Potentially over the network, or in other ways that have some form of serialization/de-serialization.
In that process, the data does not have to be an Orm descendant, so you use TData.

It seems TData is meant to be a container for raw data, but without any functionality of its own.


Still unclear where TAccessData comes into play, maybe some sort of controller class. Or since you suggested TViewData, maybe a class to "copy" data content to a visual control?

Does TAccessData need to own FData?
Could it be replaced by a worker class?
    TWorkWithData.DoTheJob(AData: TData);
and if it displays it, store the TData along with the visual component, so other Workers can read it, and know what is currently displayed. TData could even have "TData.WorkerInfo: TObject" of which it needs to know nothing, but workers can use it.

Just throwing in wild ideas.


dpremus

  • New Member
  • *
  • Posts: 32
Re: Ideas for class design needed
« Reply #9 on: January 19, 2020, 11:30:12 pm »
After a few years of experimenting with various techniques like ORM, MVC, etc. I conclude that hiding data behind TDataset is the most productive and easy to maintain.

In my applications, I have something that I call database layer (units with a bunch of functions that access physical data).

Any other part of the program doesn't know if data comes from the database, some REST server or something else for example microcontroller.

In that way, I still can use data-bound controls, but I also use the plain record type when I need to transfer some data between this "database layer" functions and some other business logic functions.

Recently I changed my old Delphi application from Microsoft Access to NexusDB I was only changed functions inside this database layer units. I'm also using some kind of unit testing so I can test this database layer functions independently of the rest of the application.

I'm using class hierarchy in business logic too, but only when I have something complicated to solve.

asdf1337

  • Jr. Member
  • **
  • Posts: 56
Re: Ideas for class design needed
« Reply #10 on: January 21, 2020, 01:29:02 pm »
I think the easiest way is the one from PaulRowntree and just save it to db and then do a select to get the previous saved data. Thanks to all of you! :)

My plan was to avoid several code duplications for assigning the values to each class
Code: Pascal  [Select][+][-]
  1.         FID := aID;
  2.         FTitle := aTitle;
  3.         // ... will be quite a lot more of these in constructor
  4.         +
  5.         // defining of all properties
  6.         property ID: String read FID write FID;
  7.         property Title: String read FTitle write FTitle;
  8.         // ...
  9.  

That's where
Code: Pascal  [Select][+][-]
  1.     property ID: String read FData.FID write FData.FID;
  2.     property Title: String read FData.FTitle write FData.FTitle;
  3.     // ...
  4.  
would be very useful as I'm working with the properties anyway and don't need to assign the values in several parts of the program (e.g. in the constructor). Just need to assign them once when I'm creating and filling the TData.
« Last Edit: January 21, 2020, 01:39:22 pm by asdf1337 »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Ideas for class design needed
« Reply #11 on: January 21, 2020, 03:30:44 pm »
My plan was to avoid several code duplications for assigning the values to each class
Maybe generics?

A generic procedure, that takes 2 arguments of each combination of Source/Target class?

Thaddy

  • Hero Member
  • *****
  • Posts: 14161
  • Probably until I exterminate Putin.
Re: Ideas for class design needed
« Reply #12 on: January 21, 2020, 04:10:13 pm »
No, just add an interface.
Specialize a type, not a var.

asdf1337

  • Jr. Member
  • **
  • Posts: 56
Re: Ideas for class design needed
« Reply #13 on: January 21, 2020, 08:08:50 pm »
@Martin_fr
Don't see how generics should help there.

@Thaddy
What do you mean? Interfaces are only for functions, not fields.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 9754
  • Debugger - SynEdit - and more
    • wiki
Re: Ideas for class design needed
« Reply #14 on: January 21, 2020, 08:42:17 pm »
Afaik at least fpc trunk has
Code: Pascal  [Select][+][-]
  1. generic procedure GenFunc<T1, T2>(a: T1;b: T2);
Where T1 and T2 would be specialized to any combination of TData, TDataRecord, TAccessData.

The body of the procedure would then be, for each field: "a.Field1 := b.Field1;"

To assign all fields from one object to another, you would call the correct specialized procedure.

But... As I said: afaik... I could be wrong.



Of course in that case you could go via an embedded record

TDataStore = record id: xxx; field: xxx ..... end;

And then each of TData, TDataRecord, TAccessData have a field "FData: TDataStore".
Then you can add all the property forwarders, as you wanted to do originally.

You would still pass TData around (and not TDataStore). So TData is your internal pointer abstraction.

However, if you need to syncronize between TData, TDataRecord, TAccessData, then you need to copy the record (but now you can just assign the entire record)
« Last Edit: January 21, 2020, 08:45:55 pm by Martin_fr »

 

TinyPortal © 2005-2018