 |
BorlandTalk.com Borland discussion newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Tomislav Stamac Guest
|
Posted: Sat Dec 03, 2005 3:03 pm Post subject: OPF questions |
|
|
Hi,
I've recently read papers on OPF by Mr. Philip Brown.
I said this approach could make my life much easier, and it does not seem so
hard.
Then I ended up reading posts on this group.
The philosophy of splitting things in 3 layers (presentation,problem domain
and persistence data management)
seems so right, but building it is all but simple.
Then I read Mr. Jim Coopers Building OPF.
Then Joanna Carters articles.
And finally Mr. Scott W. Amblers Object mapping and OPF papers.
I've been writing applications in Delphi for 5 years,
but I don't have a large experience in designing classes or OO programming,
I have no experience with interfaces at all!
I sometimes have problems with he terminology used,
and my English is not that good to understand all you've said. :(
My questions:
-Can I build OPF without interfaces?
-What is a MVP?
-What is a Factory?
-The concurrency issue:
2 clients can at pretty the same time check the timestamp of a record!
They can both get the same value but while one of them is getting
timestamp the other one
posts changes to record.
The other one thinks that the record did not change so he posts also.
Unless checking is done with the same SQL statement in which is the
update:
...SQL.Text:='UPDATE ... WHERE (OID= 1) AND (TimeStamp= 2);
ExecSQLM
If RowsAffected=0 then raise EConcurrencyException...
Am I right at least in this point?
-How do I implement Transactions?
(especially in storage systems that do not support that
or my case MSSQL does not allow me to have nested transactions)
-Joanna how did you make your collections to retrieve data in chunks? Very
nice thing!
-I'm not quite sure I understood the whole idea about Proxies too.
-Where do I start?
Will I go terribly wrong if I start by building base TPDObject on the
model suggested by
Mr. Philip Brown (if that model is too simplified I'm afraid I will!)?
Thanks,
Tomislav
|
|
| Back to top |
|
 |
Bob Dawson Guest
|
Posted: Sat Dec 03, 2005 7:48 pm Post subject: Re: OPF questions |
|
|
"Tomislav Stamac" wrote
| Quote: |
I've been writing applications in Delphi for 5 years,
but I don't have a large experience in designing classes or
OO programming,
|
Always time to learn :-)
| Quote: | -Can I build OPF without interfaces?
|
Yes, certainly--you can use them or not as you choose. But interfaces aren't
that hard or mysterious once you get used to writing classes and organizing
code in OO ways.
A Model-View-Presenter pattern. This is really Joanna's area, but basically
-- The Model is the set of objects that model the problem domain and
execute its logic. Things like TCustomer, TAddress, TOrder, and TLineItem
might be parts of a sales model.
-- A View is generally an interface between the object model and some
person who needs to interact with it. Forms, Frames, and the use-case flows
that they enable constitute views.
-- A Presenter is a class or set of classes responsible for getting the
actions of the users and translating these into actions on the model, and
for interpreting the rules, constraints, and state of the model objects into
information for the view.
In terms of familiar Delphi objects, you might think of a database tables as
a model object, a grid as a view component, and the linking components
(dataset, datasource) as being presenter elements. That's not really
accurate, but I think it gets across the general idea.
-What is a Factory?
A Factory is an object that creates other objects, so that the code that
uses the object doesn't also have to be involved with how to make one. If
making an object is as simple as
myObj := TSomething.Create;
then using a factory may not be worthwhile. But if myObj could really be any
of several TSomething descendents, or might have been retrieved from a cache
rather than created, or requires a lot of preparation before being ready for
use, then writing a factory to handle all these details makes a lot of
sense, and everything else in the program just calls the factory method.
-The concurrency issue:
[...] checking is done with the same SQL statement in which is the
update:
Doesn't have to be the same statement, but does have to be within a single
transaction context.
-How do I implement Transactions?
(especially in storage systems that do not support that
or my case MSSQL does not allow me to have nested transactions)
Transaction nesting can be handled in the persistence layer--the database
does not need to support it. If all calls to the database are routed through
a persistence layer, then calls to begin a transaction by the object layer
can be caught and counted--only the first one send a call to the database.
Similarly, calls to commit are trapped, and don't actually get transmitted
to the database unless the number of commits matches the number of
BeginTransactions (so we know we're at the outer-most transaction).
| Quote: | -Joanna how did you make your collections to retrieve data in chunks? Very
nice thing!
|
I don't do paging, so I'll leave that one. My prejudice is that datasets
large enough to require paging are suspicious from a design point of view,
but that might not be true for all domains.
| Quote: | -I'm not quite sure I understood the whole idea about Proxies too.
|
You have a specific reference in mind here, or just the concept of proxy
objects in general? Proxy objects in general are mediators--they serve the
same sort of purpose that interfaces do.
| Quote: | -Where do I start?
|
Ask a lot of questions here--you're doing great.
| Quote: | Will I go terribly wrong if I start by building base TPDObject on the
model suggested by Mr. Philip Brown (if that model is too simplified
I'm afraid I will!)?
|
A lot depends on what your design goals are--Brown's OPF design works well
for linking a very stable object model to multiple databases, but the
interaction between the business objects and the persistence layer is very
code intensive and I think there are better ways now for solving some of the
problems OPFs generally encounter. Additionally, Brown's OPF articles are a
bit dated in technique--the design relies a lot on class friendship, was
written (IIRC) before Delphi had interfaces, and before much was really
written about design patterns.
What sort of problem domain are you working on, and what do you want the OPF
to accomplish for you?
bobD
|
|
| Back to top |
|
 |
Joanna Carter [TeamB] Guest
|
Posted: Sat Dec 03, 2005 9:15 pm Post subject: Re: OPF questions |
|
|
"Tomislav Stamac" <tstamac (AT) vip (DOT) hr> a écrit dans le message de news:
4391b375$1 (AT) newsgroups (DOT) borland.com...
Bob has done a good job answering most of your questions, if you need to
know more about MVP, then Google this group on the subject.
| Quote: | -Joanna how did you make your collections to retrieve data in chunks? Very
nice thing!
|
The collection class has a set of events that are called when data is
required, these events are handled in the OPF to deliver objects as
required.
| Quote: | -I'm not quite sure I understood the whole idea about Proxies too.
|
I use the term Proxy object to indicate a version of an object that only has
enough data to allow you to identify the object and to disply the properties
most likely to be required for browsing. It is intended as a lightweight
version of the full object for browsing; a proxy object is really the same
object as a full object, it is completely populated when an object is
selected for editing or other full object operation.
| Quote: | -Where do I start?
|
By asking questions here ? )
| Quote: | Will I go terribly wrong if I start by building base TPDObject on the
model suggested by
Mr. Philip Brown (if that model is too simplified I'm afraid I will!)?
|
Try Googling in this group for previous discussions on ValueType Framework
or Attribute Framework to see how to "build a better mousetrap". As Bob
mentions, Philip's techniques are quite dated now; we have moved on a lot in
terms of interfaces and overall OPF design principles.
Joanna
--
Joanna Carter [TeamB]
Consultant Software Engineer
|
|
| Back to top |
|
 |
Tomislav Stamac Guest
|
Posted: Sat Dec 03, 2005 11:03 pm Post subject: Re: OPF questions |
|
|
Thank You for replay Bob,
| Quote: | A Model-View-Presenter pattern. This is really Joanna's area, but
basically
-- The Model is the set of objects that model the problem domain and
.... |
OK, I'll leave that after I finish with OPF. :)
| Quote: | -What is a Factory?
-The concurrency issue:
[...] checking is done with the same SQL statement in which is the
update:
Doesn't have to be the same statement, but does have to be within a single
transaction context.
-How do I implement Transactions?
(especially in storage systems that do not support that
or my case MSSQL does not allow me to have nested transactions)
Transaction nesting can be handled in the persistence layer--the database
does not need to support it. If all calls to the database are routed
through
a persistence layer, then calls to begin a transaction by the object layer
can be caught and counted--only the first one send a call to the database.
Similarly, calls to commit are trapped, and don't actually get transmitted
to the database unless the number of commits matches the number of
BeginTransactions (so we know we're at the outer-most transaction).
|
Well, I still don't get it.
What you're saying is at the beginning of object layer transaction I could
start an
SQL transaction, and when the last task in the Transaction.TaskList is done
sucessfully i commit the SQL transaction.
But if my TimeStamp checking & saving object should be in single transaction
then i would end up with something like this:
TSomeDM.SaveObject(AnObject:TPDObject);
begin
fAdoConnection.BeginTrans;
try
if GetRecordTimeStamp=AnObject.TimeStamp
then SaveObject
else raise EConcurrencyException('Another user has changed the
record!');
fAdoConnection.CommitTrans;
except
fAdoConnection.RollBackTrans;
raise;
end;
end;
and in application:
....
Transaction:=TDMTransaction.Create;
Transaction.AddSaveObject(Object1);
Transaction.AddSaveObject(Object2);
if Transaction.ProcessTransaction
then Transaction.Commit
else begin
Transaction.RollBack;
ShowError;
end;
Now when I start: Transaction.ProcessTransaction I'm sending
BeginTransaction to SQL server
then the 1st task on begining of execution sends another BeginTransaction
to SQL server,
and commit on ending,
and again the 2nd task is doing the same thing.
finally on sucessful TDMTransaction last commit is sent.
When I try to send BeginTransaction to MSSSQServer it plesentlly says that
nested
transactions weren't allowed.
And another thing in this cpeciffic case I wouldn't like 1st transaction
finished
or 1st record updated if the second one fails.
| Quote: |
I don't do paging, so I'll leave that one. My prejudice is that datasets
large enough to require paging are suspicious from a design point of view,
but that might not be true for all domains.
|
Well tell that to my boss.
He is the man who works in TurboPascal (7 i think)16bit DOS applications,
database is Btrieve
I suppose that is something you call a legacy database
And when You say to him "pointer" BOO! Like You said Interface to me. ))
Has a pretty good framework (for that era) made by the boys who knew how to
make it
and that framework allows him to browse the whole table (about 2 millions of
records) in
front of the user first->last in a milisecond! In fact it is not even a
table not SQL one.
And if he can do it why can't I? (his question not mine)
I tried several times to point out that something is at least a bad idea,
didn't go very well, but if You want to tell him that user does not have to
look
at 100+ records at the same time I'll give You his cell phone number.
Just don't tell him it was me. :-D
| Quote: | You have a specific reference in mind here, or just the concept of proxy
objects in general? Proxy objects in general are mediators--they serve the
same sort of purpose that interfaces do.
|
I understood that a Proxy is used when you want to give a choice to user,
search, or something, and you dont to avoid heavy network traffic
and user waiting for list to populate,
so instead of retriving complete objects of let's say a
TPerson in a list You could retrive just LastName, FirstName for instance,
The easiest way that I can think of is to do this:
TPDObject=class
OID:TOID;
...
end;
TPersonProxy=class(TPDObject)
protected
FirstName:string;
LastName:string;
get...
set...
public
load...
function save:boolean;override;
end;
TPerson=class(TPDObject)
AllOtherFields
....
end;
function TPersonProxy.Save:boolean;
begin
raise exception.create('Don''t even think about it!');
end;
But 2 things bother me here:
1. I think that I missed the whole concept.
2. What if in another instance I wanted to show the city that a person lives
in or something else
how many proxyes should i write that way?
| Quote: |
-Where do I start?
Ask a lot of questions here--you're doing great.
|
) I'm affraid you'll be bored with stupid questions.
| Quote: | Will I go terribly wrong if I start by building base TPDObject on the
model suggested by Mr. Philip Brown (if that model is too simplified
I'm afraid I will!)?
A lot depends on what your design goals are--Brown's OPF design works well
for linking a very stable object model to multiple databases, but the
interaction between the business objects and the persistence layer is very
code intensive and I think there are better ways now for solving some of
the
problems OPFs generally encounter. Additionally, Brown's OPF articles are
a
bit dated in technique--the design relies a lot on class friendship, was
written (IIRC) before Delphi had interfaces, and before much was really
written about design patterns.
|
I assume that by class friendship You mean the circular reference thing and
allowing
classes to see each others fields by placing them in same unit.
But what are those other ways interfaces?
I don't know what is their purpose.
Yes, I've heard of Patterns, Unit Testing, Refactoring terms
First time in my life here on this group. :(
The term UML I've first heard in Scott W. Amblers papers on OPF.
Sorry.
| Quote: | What sort of problem domain are you working on, and what do you want the
OPF
to accomplish for you?
|
That would really be to much writing here.
In the original post I wrote a whole page after this but I'll save You
from reding it.
I have several applications in the system and SQL strings all over the
place.
All the bad things that you people or the whitepapers (what is a whitepaper
anyway?)
said are happening to me.
Form based, event driven actions lot of SQL (similar) in code can't track
stupidities in them...
Basically I'm really having trouble to keep up with maintaining the system,
and I should be upgrading it with things that are'nt finished.
Still after all this questions if You think that I'm not hopeless, and You
like reading
I could write a bit more specific about my Tables (future Objects ) and
the problem domain.
Tomislav
|
|
| Back to top |
|
 |
Bob Dawson Guest
|
Posted: Sun Dec 04, 2005 3:29 am Post subject: Re: OPF questions |
|
|
"Tomislav Stamac" wrote
| Quote: |
Well, I still don't get it.
|
Okay, here's a typical situation--saving objects or collections of those
objects may have to be transaction wrapped, so we have:
procedure TPDObject.Save;
begin
PersistenceService.BeginTransaction;
try
PersistenceService.SavePDObject(self);
PersistenceService.Commit;
except
PersistenceService.Rollback;
end;
end;
and in a collectionClass
procedure TPDObjectCollection.SaveObjects;
var
i : integer;
begin
PersistenceService.BeginTransaction;
try
for i := 0 to Count -1 do
TPDObject(Item[i]).Save;
PersistenceService.Commit;
except
PersistenceService.Rollback;
end;
end;
Now how do we make sure that each object saves correctly without knowing if
a transaction is already in progress? Have to look at the persistence
service object for that:
procedure PersistenceService.BeginTransaction;
begin
if FPersistenceNestingLevel = 0 then
FPersistenceStore.BeginTransaction;
inc(FPersistenceNestingLevel);
end;
procedure PersistenceService.Commit;
begin
dec(FPersistenceNestingLevel);
if FPersistenceNestingLevel = 0 then
FPersistenceStore.Commit
end;
Not full production code obviously, but that's the basic technique--by
routing everything though an OPF persistence portal, you gain the ability to
intercept what would otherwise be direct calls to the data storage device
and handle them appropriately.
The above code also indicates the basic structural layers involved: The
persistent objects or collection classes can know that a persistence service
exists, but they have no knowledge about how it works.
The persistence service object is a generic class that actually performs its
functions using internal composition or plug-in implementation objects
(here, the FPersistenceStore). That gives it a consistent interface
regardless of whether it's actually hitting an RDBMS, a flat file, or other
data storage object.
| Quote: |
TSomeDM.SaveObject(AnObject:TPDObject);
begin
fAdoConnection.BeginTrans;
[...]
end;
|
As above--the persistence layer does not generate or forward a transaction
request to the database if it's already InTransaction--which is something
that the persistence layer can know.
| Quote: | ...
Transaction:=TDMTransaction.Create;
Transaction.AddSaveObject(Object1);
Transaction.AddSaveObject(Object2);
|
Actually this looks to me more like Ambler's approach. If you use an
explicit transaction object like this, then the call
| Quote: | Transaction.AddSaveObject(Object1);
simply adds the object to an internal list, it doesn't do anything about |
beginning a transaction. That happens only when
| Quote: | if Transaction.ProcessTransaction
is called. |
| Quote: | When I try to send BeginTransaction to MSSSQServer it plesentlly says that
nested transactions weren't allowed.
|
Right--but using the above technique the database doesn't get redundent
BeginTransaction calls.
| Quote: | And another thing in this cpeciffic case I wouldn't like 1st transaction
finished
or 1st record updated if the second one fails.
|
There are multiple issues here. The nesting method above works well even if
an application programmer wants to transaction-wrap objects that aren't
related in the object model. For example
[...]
PersistenceService.BeginTransaction
try
ObjectOne.Save;
// point1 (see below)
ObjectTwo.Save;
PersistenceService.Commit;
except
PersistenceService.Rollback;
end;
This works just like nesting within a collection. Since the programmer
called PersistenceService.BeginTransaction directly, the object save is just
going to incriment FPersistenceNestingLevel, and the call to Commit
decriment it to 1 again. Only the outermost call transmits anything to the
data storage device.
You also have to think about how the object knows it's dirty. At point1
above the first object has been saved successfully, but might still be
rolled back, so it can't finalize its state or become 'Clean' again.
Basically, an object being saved has to subscribe to the transaction (beome
a transaction observer). Then the transaction can notify all the involved
objects about how things went at the end.
| Quote: | and that framework allows him to browse the whole table (about 2
millions of records) in front of the user first->last in a milisecond! In
fact it is not even a table not SQL one.
|
This isn't really an OPF issue or even an OO design issue, but an issue of
adapting from desktop thinking to client-server or n-tier thinking. Calls
like 'SELECT * from MYTABLE' simply don't scale well, and if you're still
fighting that battle then you do indeed have a problem.
| Quote: | I understood that a Proxy is used when you want to give a choice to user,
search, or something, and you dont to avoid heavy network traffic
and user waiting for list to populate,
|
Okay--this would generally be handled by giving the persistent object an
internal boolean IsProxy property. The object simply loads only a subset of
its normal fields initially, and sets IsProxy to true. Any fields not loaded
have code in their getters that triggers a full load (a second SQL fetch for
the missing information), which will turn IsProxy to false.
Personal take: the actual difference between loading all fields or only some
is generally much much less than the SQL round trip time. So the difference
only matters when (1) you're dealing with very large properties (blobs, text
fields), or (2) very large collections of objects, most of which will never
actually be used. In most or all other cases proxy objects are a waste of
time.
For those instances where it does matter, then I would generally wrap the
additional property or properties into another object, so instead of having
a JPeg field in this object and using the proxy pattern
TStoredPhoto = class(TPDObject)
private
FASA : integer;
FFStop : double;
FImage : TJPeg;
IIsProxy : boolean
FPhotographerID : TPDObjectID;
FTitle : string;
[...]
end;
I'd just create a
TStoredPhotoJPeg = class(TPDObject)
and make that a load-on-demand internal composite of the TStoredPhoto:
TStoredPhoto = class(TPDObject)
private
FASA : integer;
FFStop : double;
FImage : TStoredPhotoJPeg;
[...]
protected
function GetImage : TJPeg;
property Image : TJPeg read GetImage;
function TStoredPhoto.GetImage : TJPeg;
begin
if not assigned(FImage) then
FImage := TStoredPhotoJPeg.CreateLoad(Id);
Result := FImage.Image;
end;
This accomplishes the exact same thing that the proxy pattern does, but it
doesn't require any new code or infrastructure support--it's just another
TPDObject.
| Quote: | 2. What if in another instance I wanted to show the city that a person
lives
in or something else
how many proxyes should i write that way?
|
While that's a difficult question in theory, its not really much of a
problem in practice. As I argued above, most fields are in fact trivial to
fetch in comparison to the total round trip cost--getting the object is
going to cost about the same whether you get it all or only a part. For the
cases where skipping some data really does make sense, then you can pretty
much tell what you need and what you don't from the actual problem domain.
| Quote: | ) I'm affraid you'll be bored with stupid questions.
|
Not at all. The regulars here all have our own approaches and perspectives,
and answering basic questions is a good way to start discussions/arguments
between us, which we all seem to enjoy. :-)
And remember--most Delphi programmers never make it here at all--so we
really don't see much in the way of 'stupid' questions. Try calling them
'fundamental' or 'core' questions instead--sounds much nicer, and is still
accurate.
| Quote: | I assume that by class friendship You mean the circular reference thing
and
allowing classes to see each others fields by placing them in same unit.
|
Yep. Friendship is kind of a cheat from an OO theory perspective. Worse,
it's very code intensive. What you want to accomplish is writing code that
can transfer the data of ANY persistent object back and forth between the
object layer and the persistence layer. Then you don't have to code each
domain object and persistence helper object individually.
| Quote: | But what are those other ways interfaces?
|
Using interfaces makes certain types of problems easier, but again that's
really a call you have to make. If you think about the problem in general as
one of passing data between layers, then obviously datasets have been doing
that for years.
| Quote: | I don't know what is their purpose.
|
Interfaces? For me their main purpose is being able to use a class without
knowing what specific class it is. For example, think about defining methods
that take a TStrings parameter--you can pass it a TStringList, a
ComboBox.Items, a ListBox.Lines, etc and everything just works. Inerfaces
allow you to do the same thing, but with classes that don't have to descend
from a common ancestor having the common characteristics. If yo go back to
the eariler code, for instance, think about this line:
| Quote: | Transaction.AddSaveObject(Object1);
|
probably declared as
procedure TTransaction.AddSaveObject(aObject : TPDObject);
That will work as long as you never want to save a TCollectionItem, ot a
TStringList, or any of the other objects Delphi provides for you to descend
from, but that don't decend from your TPDObject class. But if instead you
wrote the method as
procedure TTransaction.AddSaveObject(aPersistable : IPersistable);
then you'd have the option of saving/loading anything that you can implement
the IPersistable interface on, not just TPDObject descendents. That's what
interfaces bring to the table--a way to code as if Delphi supported multiple
inheritance.
| Quote: | Yes, I've heard of Patterns, Unit Testing, Refactoring terms
|
All three are big items. :-)
| Quote: | That would really be to much writing here.
|
We can take it in chunks. <g>
bobD
|
|
| Back to top |
|
 |
Tomislav Stamac Guest
|
Posted: Sun Dec 04, 2005 11:03 am Post subject: Re: OPF questions |
|
|
Hi Joanna, thank You for Your time.
| Quote: | The collection class has a set of events that are called when data is
required, these events are handled in the OPF to deliver objects as
required.
|
I think I generally understand the concepts of what has been said
in all of those papers, but the details are bothering me.
I understood that Your collection has events for handling those things,
I don't just get how do you tell to for instance MSSQLS that it returns
just 15 records out of 100 and then in OnNeedNewChunk You tell it to
give another 15 starting from the last+1.
Unless that is done by serverside cursor or a middletier server.
I was about to ask You why have You used a TCollection (which seemed more
complicated)
instead of TList like Mr. Brown (I used TList a lot so I'm familiar to it)
but
I realized that TCollection has 2 major advantages at a first look to a
source:
1. You don't have to serve it with outside created objects,
(It creates an instance of proper class object in Add)
2. It frees an instance of item in Delete, so you don't have to write a
cleanup code.
| Quote: | | -Where do I start?
By asking questions here ? )
So I guess I've already started! ) |
| Quote: | Try Googling in this group for previous discussions on ValueType Framework
or Attribute Framework to see how to "build a better mousetrap". As Bob
mentions, Philip's techniques are quite dated now; we have moved on a lot
in
terms of interfaces and overall OPF design principles.
|
I'll google for some articles about VTF when I'm done with Your articles
on interfaces that I've downloaded.
Just to be sure that I understand this:
a Value Type Framework would be a set of classes responsible for let's say
examining the properties of a PDObject properties, and mapping them to
SQL types or wathever is on that side...
So instead of a simple (outdated) modell from Mr. Brown:
TSomePDObject=class(TPDObject)
fIntegerField:integer;
fDateTimeField:TDateTime;
....
end;
We would have something like this?
TVTObject=class
fValue:variant;
fIsServerAssigned:boolean;
...
function GetSQLType:string;virtual;abstract;
function GetSQLValue:string;virtual;abstract;
public
property Value:variant read GetValue write SetValue;
...
property SQLType read GetSQLType;
property SQLValue read GetSQLValue;
end;
TVTDateTime=class(TVTObject)
...
end;
TPDObject=class(TCollectionItem)
fOID:TOID;
...
end;
TSomePDObject=class(TPDObject)
fIntegerField:TVTInteger;
fDateTimeField:TVTDateTime;
....
end;
where
function TVTDateTime.Getvalue:DateTime;
begin
Result:=TDateTime(fValue);
end;
function TVTDateTime.GetSQLType:string;
begin
Result:='DATETIME';
end;
function TVTDateTime.GetSQLValue:string;
begin
Result:=FormatDateTime('YYYY-MM-DD HH:NN:SS.ZZ',Value);
end;
And another question on your Example with a collection:
type
TStockCollection = class(TPDCollection)
private
function GetItems(Idx: Integer): TStockItem;
public
constructor Create(AOwner: TPDObject = nil); override;
function ItemClass: TPDObjectClass; override;
function AddItem(AOID: TOID) : TStockItem;
function CurrentItem: TStockItem;
property Items[Idx: Integer]: TStockItem
read GetItems; default;
end;
"TPDCollection supports the concept of an Iterator, which can be defined as
:"
IPDIterator = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
function CurrentItem: TPDObject;
procedure First;
function IsDone: Boolean;
procedure Next;
end;
Why not just:
TPDCollection = class(TOwnedCollection)
...
...
public
...
procedure First;
function IsDone:boolean;
procedure Next;
end;
What did You gain by using interface here (maybe I'll understand that when I
read Your articles on interfaces) :-)
Thanks, again
Tomislav
|
|
| Back to top |
|
 |
Joanna Carter [TeamB] Guest
|
Posted: Sun Dec 04, 2005 12:59 pm Post subject: Re: OPF questions |
|
|
"Tomislav Stamac" <tstamac (AT) vip (DOT) hr> a écrit dans le message de news:
[email]4392ccb7 (AT) newsgroups (DOT) borland.com[/email]...
| Quote: | I understood that Your collection has events for handling those things,
I don't just get how do you tell to for instance MSSQLS that it returns
just 15 records out of 100 and then in OnNeedNewChunk You tell it to
give another 15 starting from the last+1.
Unless that is done by serverside cursor or a middletier server.
|
The idea of an OPF is that the object side of things really doesn't know
about database side of things. You have to use whatever techniques that the
type of database you are using provides. I was using Firebird/Interbase and,
at the time, I had to maintain a cursor on the table in order to be able to
pick up where it left off; this meant having a connection open as long as
the collection was not completely full. Nowadays, Interbase supports a
first..next syntax, so I would use that.
| Quote: | I was about to ask You why have You used a TCollection (which seemed more
complicated)
instead of TList like Mr. Brown (I used TList a lot so I'm familiar to
it)
but
I realized that TCollection has 2 major advantages at a first look to a
source:
1. You don't have to serve it with outside created objects,
(It creates an instance of proper class object in Add)
2. It frees an instance of item in Delete, so you don't have to write a
cleanup code.
|
To be picky, I don't use TCollection, but my own special collection class;
it just happens to be similar to TCollection in that it ensures that you can
only add new items of the correct type.
Nowadays, I use either templated list classes in Delphi for Win32 or .NET
2.0 generic classes in C#; both of which allow typesafe collections without
having to write a typesafe wrapper/override for every type.
| Quote: | Just to be sure that I understand this:
a Value Type Framework would be a set of classes responsible for let's say
examining the properties of a PDObject properties, and mapping them to
SQL types or wathever is on that side...
So instead of a simple (outdated) modell from Mr. Brown:
TSomePDObject=class(TPDObject)
fIntegerField:integer;
fDateTimeField:TDateTime;
...
end;
We would have something like this?
TVTObject=class
fValue:variant;
fIsServerAssigned:boolean;
...
function GetSQLType:string;virtual;abstract;
function GetSQLValue:string;virtual;abstract;
public
property Value:variant read GetValue write SetValue;
...
property SQLType read GetSQLType;
property SQLValue read GetSQLValue;
end;
|
No, No, No !! )
1. You should *never* use variants, always use typesafe derivatives of a
base abstract class. The VTF is fairly well documented in previous threads
here. The old TPDObject way of doing things relied on published properties
having RTTI to allow us to examine properties for persistence. Now we tend
to use the the VTF instead as it is more flexible than RTTI.
e.g.
TValueType = class
private
...
public
procedure Assign(const other: IValueType);
function Clone: TValueType;
function GetAsString: string;
function GetName: string;
function GetRequired: Boolean;
function IsNull: Boolean;
procedure SetNull;
procedure SetRequired(value: Boolean);
property AsString: string
read GetAsString;
property Required: Boolean
read GetRequired
write SetRequired;
end;
TIntegerValueType = class(TValueType)
private
fValue: Integer;
function GetValue: Integer;
procedure SetValue(value: Integer);
public
property Value: Integer
read GetValue;
write SetValue;
end;
TBaseObject = class
private
fValues: TStringList;
protected
property Values: TStringList
read fValues;
function GetStringValue(const name: string): string;
function GetIntegerValue(const name: string): Integer;
function GetDateTimeValue(const name: string): TDateTime;
...
procedure SetStringValue(const name: string; const value: string);
procedure SetIntegerValue(const name: string; value: Integer);
procedure SetDateTimeValue(const name: string; value: TDateTime);
....
end;
function TBaseObject.GetIntegerValue(const name: string): Integer;
begin
result := TIntegerValueType(fValues.Objects[fValues.IndexOf(name)]).Value;
end;
procedure TBaseObject.SetIntegerValue(const name: string; value integer);
begin
TIntegerValueType(fValues.Objects[fValues.IndexOf(name)]).Value := value;
end;
TPerson = class(TBaseObject)
private
function GetName: string;
procedure SetName(const value: string);
function GetAge: Integer;
procedure SetAge(value: Integer);
public
constructor Create;
property Name: string
read GetValue;
write SetValue;
property Age: Integer
read GetAge;
write SetAge;
end;
constructor TPerson.Create;
begin
inherited Create;
Values.AddObject("Name", TStringValueType.Create);
Values.AddObject("Age", TIntegerValueType.Create);
end;
function TPerson.GetAge: Integer;
begin
result := GetIntegerValue("Age");
end;
proecdure TPerson.SetAge(const name string; value: Integer);
begin
SetIntegerValue("Age", value);
end;
2. You should not have anything like SQL types in the object side of things,
they should be mapped inside the OPF layer.
Joanna
--
Joanna Carter [TeamB]
Consultant Software Engineer
|
|
| Back to top |
|
 |
Tomislav Stamac Guest
|
Posted: Sun Dec 04, 2005 1:57 pm Post subject: Re: OPF questions |
|
|
Thanks, Bob
That elaboration on transactions throws a lot of light
on the dark area surrounding me. :-)
| Quote: | The above code also indicates the basic structural layers involved: The
persistent objects or collection classes can know that a persistence
service
exists, but they have no knowledge about how it works.
|
Yes, I thought that is the general idea, but maybe I'm still confusing some
things
that should be in PD layer to Persistence layer and vice versa.
| Quote: |
The persistence service object is a generic class that actually performs
its
functions using internal composition or plug-in implementation objects
(here, the FPersistenceStore). That gives it a consistent interface
regardless of whether it's actually hitting an RDBMS, a flat file, or
other
data storage object.
|
I lost You there.
What do You mean by internal composition or plug-in implementation objects?
| Quote: | You also have to think about how the object knows it's dirty.
|
I haven't thought of that. (Dirty meaning has to be saved?)
That means, since no object was saved without a transaction,
(because Objects save method is calling a transaction begin)
that an object can't ever simply update his dirty status to clean,
but it has to be notified about that by the transaction itself.
Or You said that it has to become a TransactionObserver, is
appropriate place to put an interface something that observes
or is listening the current transaction status and all persistable objects
are implementing that interface?
| Quote: | fighting that battle then you do indeed have a problem.
|
You couldn't possibly get the idea about its magnitude :-)
| Quote: |
I understood that a Proxy is used when you want to give a choice to user,
search, or something, and you dont to avoid heavy network traffic
and user waiting for list to populate,
|
OK. I guess I can start without a proxy functionality of an Object
and implement that later if the need for it arises.
| Quote: | For those instances where it does matter, then I would generally wrap the
additional property or properties into another object, so instead of
having
a JPeg field in this object and using the proxy pattern
TStoredPhoto = class(TPDObject)
private
FASA : integer;
FFStop : double;
FImage : TJPeg;
IIsProxy : boolean
FPhotographerID : TPDObjectID;
FTitle : string;
[...]
end;
I'd just create a
TStoredPhotoJPeg = class(TPDObject)
and make that a load-on-demand internal composite of the TStoredPhoto:
TStoredPhoto = class(TPDObject)
private
FASA : integer;
FFStop : double;
FImage : TStoredPhotoJPeg;
[...]
protected
function GetImage : TJPeg;
property Image : TJPeg read GetImage;
function TStoredPhoto.GetImage : TJPeg;
begin
if not assigned(FImage) then
FImage := TStoredPhotoJPeg.CreateLoad(Id);
Result := FImage.Image;
end;
This accomplishes the exact same thing that the proxy pattern does, but it
doesn't require any new code or infrastructure support--it's just another
TPDObject.
|
This is beautiful. To break an object into two.
And I read about lazy constructs/loads in the very first
article (Philip Browns). But that thought didn't came to me.
Maybe too much info at once.
Proxies distracted me. ;)
And BTW I have a similar situation there:
several JPGs cca 50kb each are stored at the server sent via GPRS or EDGE.
Logically they belong to one object or a record in a table if you wish.
Now clients that have to deal with these photos are behind a 128kbps
frame-relay
link to the server.
Luckily they don't ever have more than a few rows on the screen and they do
load JPG on demand.
| Quote: | Not at all. The regulars here all have our own approaches and
perspectives,
and answering basic questions is a good way to start discussions/arguments
between us, which we all seem to enjoy. :-)
|
I'm glad that I could help.
Are there any fights sometimes. :-)
| Quote: | Yep. Friendship is kind of a cheat from an OO theory perspective. Worse,
it's very code intensive.
|
Why is that so (code intensive), I can't see that far.
| Quote: | What you want to accomplish is writing code that
can transfer the data of ANY persistent object back and forth between the
object layer and the persistence layer. Then you don't have to code each
domain object and persistence helper object individually.
Or I can. |
You mean not to override and code different code in a speciffic DMObject
for save method or load...
....but to use value type framework that Joanna metioned?
I'll deal with interfaces later, after I read Joannas articles on the
subject.
| Quote: | We can take it in chunks. <g
|
) What do You mean? Me learning in chunks, or Me explaining my problem
domain in chunks?
Oh, and this is really embarrassing and off topic, but i have to ask.
I've seen a
I have no idea what does it mean, and since this one was addressed to me...
what is it? )
Tomislav
|
|
| Back to top |
|
 |
Tomislav Stamac Guest
|
Posted: Sun Dec 04, 2005 2:49 pm Post subject: Re: OPF questions |
|
|
Thank You all for answers.
I'll do some more googling and reading,
and then I'll probably bother You some more.
I forgot to mention, but didn't think it was relevant:
I'm still using Delphi 6 pro.
It's a bit complicated to upgrade because a good part
of my system uses Indy 8.xx components,
which are not compatible to later versions introduced in D7...
And as You can guess TCP communication wasn't done in
OO practice in those days, but it was also driven by form
and formevent logic.
So I would have to rewrite pretty big parts of all applications
for TCP communication.
If I was clever back then (and if the task was not supposed to be done
yesterday)
I would have written Clients as classes so now I would just
rewrite 2 classes rather than parts of application.
Tomislav
|
|
| Back to top |
|
 |
Bob Dawson Guest
|
Posted: Sun Dec 04, 2005 4:06 pm Post subject: Re: OPF questions |
|
|
"Tomislav Stamac" wrote
| Quote: | regardless of whether it's actually hitting an RDBMS, a flat file, or
other
data storage object.
I lost You there.
What do You mean by internal composition or plug-in implementation
objects?
|
Composition is an OO term. It means that an object is getting its methods
and properties not by inheriting from a class that has them, but by
including that other class internally. An example is the way that TEdit,
TMemo etc. don't inherit from TFont, but get their Font characteristics by
owning a TFont object. Composition can be either public (as it is with the
objects using TFont) or hidden.
In the case of the persistence service object, you create a general class
that does nothing but talk to the datastorage, and have descendent classes
that customize this object to specific storages: MSSQLServer, Interbase,
ASAnywhere, etc.
The persistence service object handles all the common logic for persistence
handling, then passes the actual db calls off to the internal db-handler
class.
| Quote: | I haven't thought of that. (Dirty meaning has to be saved?)
|
Right--basically dirty means 'different than when originally loaded.' In my
framework calling Save on a clean object works, but doesn't actually do
anything.
| Quote: | but it has to be notified about that by the transaction itself.
|
right.
| Quote: | Or You said that it has to become a TransactionObserver, is
appropriate place to put an interface something that observes
|
Interfaces would be a common approach, but aren't necessary. For example,
the object could request a notification object from the transaction, which
would keep a list of all the notification objects it has given out. The
persistent object would then attach the notification object to its own
OnTransactionCompleted event. When the transaction completes, it iterates
its notification object list firing the Notify method, and everybody finds
out what happened.
| Quote: | You couldn't possibly get the idea about its magnitude
|
We've all seen them on one issue or another.
| Quote: |
I'm glad that I could help.
Are there any fights sometimes.
|
Nothing violent lately.
| Quote: |
Why is that so (code intensive), I can't see that far.
|
Brown's frienddship approach means that you have to write code to transfer
the data between layers for each persistent object class individually. If
you replace the internal F-fields of an object with a self-describing
collection, then you just write code for handling this collection once, and
all persistent classes use it.
| Quote: | You mean not to override and code different code in a speciffic DMObject
for save method or load...
...but to use value type framework that Joanna metioned?
|
Joanna's VTF approach is a good example of this approach, yes. Essentially,
the persistence layer only has to handle a value type collection--it doesn't
need to know the class that the collection belongs to, so everything becomes
generic.
| Quote: | I've seen a <g> sign in many posts.
I have no idea what does it mean, and since this one was addressed to
me...
what is it? )
|
<g> is short for <grin>. It's like a smily face.
bobD
|
|
| Back to top |
|
 |
Joanna Carter [TeamB] Guest
|
Posted: Sun Dec 04, 2005 4:45 pm Post subject: Re: OPF questions |
|
|
"Tomislav Stamac" <tstamac (AT) vip (DOT) hr> a écrit dans le message de news:
439301b7$1 (AT) newsgroups (DOT) borland.com...
| Quote: | I'll do some more googling and reading,
and then I'll probably bother You some more.
|
That's what this group is here for :-)
| Quote: | I forgot to mention, but didn't think it was relevant:
I'm still using Delphi 6 pro.
|
My first complete VTF, OPF, MVP frameworks were all written in D6; you
really don't need anything more. I am currently using .NET 2.0 in C# with
reflection and generics and it really does save an absolute ton of coding;
but if you use template classes in D6, you can also save a lot of time and
coding.
| Quote: | If I was clever back then (and if the task was not supposed to be done
yesterday)
I would have written Clients as classes so now I would just
rewrite 2 classes rather than parts of application.
|
Fight for the time to change to OO practice, once you get the hang of how it
works, your bosses will start to see how easy and fast it can be :-)
Joanna
--
Joanna Carter [TeamB]
Consultant Software Engineer
|
|
| Back to top |
|
 |
preston Guest
|
Posted: Tue Dec 06, 2005 3:55 pm Post subject: Re: OPF questions |
|
|
Bob Dawson wrote:
| Quote: | I don't do paging, so I'll leave that one. My prejudice is that datasets
large enough to require paging are suspicious from a design point of view,
but that might not be true for all domains.
|
How do you handle a list where the user wants to see everything or do
you not allow this sort of thing. Do you force some sort of filter to be
done up front?
Preston
|
|
| Back to top |
|
 |
Bob Dawson Guest
|
Posted: Tue Dec 06, 2005 4:55 pm Post subject: Re: OPF questions |
|
|
"preston" wrote
| Quote: | Bob Dawson wrote:
I don't do paging, so I'll leave that one. My prejudice is that datasets
large enough to require paging are suspicious from a design point of
view,
but that might not be true for all domains.
How do you handle a list where the user wants to see everything or do
you not allow this sort of thing. Do you force some sort of filter to be
done up front?
|
There's no absolute limit on collection size, but generally all pre-written
queries are going to be much less than full-table scope. The programmer is
responsible for the size-vs-responsiveness trade-off there.
Where the question really becomes more important is in allowing ad-hoc
queries--support for the user being allowed to select the object properties
that they care about and the values and value conditions (=, <, >, !=, etc)
they want.
By default we limit those queries to 300 rows, and a property on the
colllection will indicate that additional data exists. The user can use the
collection to guide them in refining the query until they're sure they're
getting the right objects.
The 300 row default is metadata on the collection--it can be set higher or
lower on a by-collection basis for specific needs.
bobD
|
|
| Back to top |
|
 |
Marcos Guest
|
Posted: Tue Dec 06, 2005 5:19 pm Post subject: Re: OPF questions |
|
|
Hi Tomislav Stamac
We are building a big set of frameworks (Infra Framework) to cope with
OO on Dephi w32. It is free and OpenSource. It have many frameworks done:
Value Type;
Validation;
Notification;
Aspect;
Interface Injection;
and more...
Model-View-Presenter (ending to basic vcl components);
OPF (next one and we need help, maybe u want join to team);
Infragen Code Generator (outdated)
OCL Parser (Building)
if anyone want know more about Infra send mail to:
[email]mrbar2000 (AT) no_spam_yahoo (DOT) com.br[/email] without no_spam_
We will put a new version on Attachments or CodeCentral very soon.
|
|
| Back to top |
|
 |
Tomislav Stamac Guest
|
Posted: Tue Dec 06, 2005 8:32 pm Post subject: Re: OPF questions |
|
|
Hi, Marcos
| Quote: | and more...
Model-View-Presenter (ending to basic vcl components);
OPF (next one and we need help, maybe u want join to team);
Infragen Code Generator (outdated)
OCL Parser (Building)
|
What kind of a team are You when You are inviting total beginers to the
subject? ;)
Sure, more brains is better than one.
I'm now learning the basic concepts and interfaces from the people in this
group,
and about from Joanna's articles.
I'll contact You if I feel that I learned something useful.
Oh, ant those actonyms in informatics drive me crazy, i don't know what is
an OCL. :)
Tomislav
|
|
| Back to top |
|
 |
|
|