Board index » delphi » Light-weight vs Heavy-weight business objects?

Light-weight vs Heavy-weight business objects?

Hi,

In the ongoing discussion of object persistency, is there a rule that
people are following in regards to the creation of light-weight (proxy)
objects versus heavy-weight (real) objects?

TLoginUser = class(TBizObject)
private
   ...
public
  function SetPassword(const NewPassword: string);
  property Name: string;
  property LoginID: string;
  property ExpiryDate: TDateTime;
  property Enabled: Boolean;
  property Permissions: TPermissionCollection;
end;

For example, all I require is the Name and LoginID fields to display in
a TListView, therefore I would create a list of light-weight TLoginUser
objects which only contain data for the Name and LoginID fields. If I
were to then select one TLoginUser object to display its details in a
detailed form, I would then have it call itself to load its complete
data.

My rule is if I'm creating a collection of objects, then I populate the
list with the light-weight version of the object. If I call for a single
object then I create a heavy-weight object.

In the TLoginUser example, even when I load the heavy version of the
object, it will contain data for Name, LoginId, ExpiryDate and Enabled
but for Permissions, it will only contain a PermissionCollections which
hold the lightweight versions of a TPermission object.

What are your opinions? Is there anyone else doing it differently?

Regards,
Colin

 

Re:Light-weight vs Heavy-weight business objects?


Quote
Colin Johnsun wrote:
> Hi,

> In the ongoing discussion of object persistency, is there a rule that
> people are following in regards to the creation of light-weight (proxy)
> objects versus heavy-weight (real) objects?

> TLoginUser = class(TBizObject)
> private
>    ...
> public
>   function SetPassword(const NewPassword: string);
>   property Name: string;
>   property LoginID: string;
>   property ExpiryDate: TDateTime;
>   property Enabled: Boolean;
>   property Permissions: TPermissionCollection;
> end;

> For example, all I require is the Name and LoginID fields to display in
> a TListView, therefore I would create a list of light-weight TLoginUser
> objects which only contain data for the Name and LoginID fields. If I
> were to then select one TLoginUser object to display its details in a
> detailed form, I would then have it call itself to load its complete
> data.

> My rule is if I'm creating a collection of objects, then I populate the
> list with the light-weight version of the object. If I call for a single
> object then I create a heavy-weight object.

I forgot to add that if I select a field in the lightweight object that is
not populated (eg ExpiryDate field in TLoginUser) or calls a method (eg
procedure SetPassword), then the lightweight object will call its Load
method to turn itself into a 'heavy' object. So the "users" of the object
will be oblivious to the fact that they are using a light or heavy weight
object.

- Show quoted text -

Quote

> In the TLoginUser example, even when I load the heavy version of the
> object, it will contain data for Name, LoginId, ExpiryDate and Enabled
> but for Permissions, it will only contain a PermissionCollections which
> hold the lightweight versions of a TPermission object.

> What are your opinions? Is there anyone else doing it differently?

> Regards,
> Colin

Re:Light-weight vs Heavy-weight business objects?


Quote
"Colin Johnsun" <col...@nospam.asia.com> wrote in message

news:39926AD9.134D6AEA@nospam.asia.com...

Quote
> What are your opinions? Is there anyone else doing it differently?

No, I think you have just about summed up my way of working !

--
Joanna Carter
Remove .Hat to reply

Re:Light-weight vs Heavy-weight business objects?


Quote
"Colin Johnsun" <col...@nospam.asia.com> wrote in message

news:39926AD9.134D6AEA@nospam.asia.com...

Quote
> Hi,

> In the ongoing discussion of object persistency, is there a rule that
> people are following in regards to the creation of light-weight (proxy)
> objects versus heavy-weight (real) objects?

(snip)

Personally I don't separate the concepts of light and heavy objects. I
have a single object concept that is, by definition, fully populated.
This keeps the model (and the business objects) simple by not having
multiple variations of the same thing, and also avoids the necessity
of having to provide "write" accessor functions for every property
just so that you can detect usage and know to populate the rest of the
object.

Now, I know that this is going to pull more data across the network
to populate a large list of objects just to display the name on
screen in a list view so that they can be chosen. But in my experience
this isn't any kind of a performance or bandwidth issue, probably
specifically because unlike many developers I don't think that loading
a combobox or a list view with 10,000 elements and expecting the
user to select one is a good idea.

If I had such limited bandwidth that it became an issue, I would
specifically design a secondary BO for those classes where it was
required that exposed a restricted property set - and I'd generally
make this class readonly, but with a construct-on-demand property
that exposed the "full" BO.

e.g.

TCustomerDescriptor = class
public
  property Name: String;
  property Customer: TCustomer;
end;

The data manager for this class would just populate the Name property
(reducing bandwidth requirements) but would also provide access to
the full object (by hiding the ID somewhere).

But, I'd stress that in my experience, unless your bandwidth really
is restricted, you don't need the complexities of additional objects.
Trust me, as the number of your BO's grow, you'll be glad not to have
to provide a write accessor for every property. I have tried this
method, and rejected it on the grounds of insufficient benefit for the
extra work and complexity involved.

Just my 2p.

Philip Brown
Informatica Systems Ltd

Re:Light-weight vs Heavy-weight business objects?


Quote
"Philip Brown" <phil.notinsof...@informatica.uk.com> wrote in message

news:39926feb_2@dnews...

My BO class simply has a Proxy flag which indicates whether to fully
populate the BO or just set a couple of properties for browsing. Like Colin,
when a call is made that needs any more, the BO is retrieved in full.

--
Joanna Carter
Remove .Hat to reply

Re:Light-weight vs Heavy-weight business objects?


Quote
>Personally I don't separate the concepts of light and heavy objects. I
>have a single object concept that is, by definition, fully populated.
>This keeps the model (and the business objects) simple by not having
>multiple variations of the same thing, and also avoids the necessity
>of having to provide "write" accessor functions for every property
>just so that you can detect usage and know to populate the rest of the
>object.

I think I agree with you. I'll take the "high" road and model my software
as close the real world as possible. If I later see that there are
performance issues involved, then it's time to look at Just-In-Time loading
and other alternatives.

I remember from looking at Poet (object database system) a couple years
ago, that they had the concept of Shallow vs. Deep loading (don't remember
their exact terminology). Basically, you specified in the metadata (schema)
how you wanted objects to load.

When it comes to loading collections, it's interesting to look at ODMG's
concepts. The items of a collection can be any type known to the language.
In other words you can specify a query where you only want to return a
subset of each object. For example, 'select customer from customers' would
return a collection of objects. 'Select Id, Name from customers' would
return a collection of records, where each record had an Id and a Name
field.

Tor Langlo

Re:Light-weight vs Heavy-weight business objects?


What I've done is this:

I give my objects the ability to know how much is loaded, thru a property
called:

Loaded

This is an enumerated set. When I load the object from the database I
usually set up the load method like this:

function Load( UniqueID: longint ; LoadAmount : ObjectEnumeratedSet )

In this way I can call the method like so:

MyHuman := THuman.create;
MyHuman.Load( 1, [arms, head, torso]; // don't need legs right now...

I echo what's been loaded into the Read-only property Loaded, so interested
object-users can read it. But I use the Loaded property internally as well
to see what's already there in case someone calls Load again. That way I
don't have to hit the database twice for something that's already there!

Of course, this approach makes several concessions to the database but it's
very self-contained and self-documenting. (where else would you look for a
problem loading the data from a database??)

exactly two cents,

Gary

Quote
Colin Johnsun wrote in message <39926C7A.F2230...@nospam.asia.com>...
>Colin Johnsun wrote:

>> Hi,

>> In the ongoing discussion of object persistency, is there a rule that
>> people are following in regards to the creation of light-weight (proxy)
>> objects versus heavy-weight (real) objects?

>> TLoginUser = class(TBizObject)
>> private
>>    ...
>> public
>>   function SetPassword(const NewPassword: string);
>>   property Name: string;
>>   property LoginID: string;
>>   property ExpiryDate: TDateTime;
>>   property Enabled: Boolean;
>>   property Permissions: TPermissionCollection;
>> end;

>> For example, all I require is the Name and LoginID fields to display in
>> a TListView, therefore I would create a list of light-weight TLoginUser
>> objects which only contain data for the Name and LoginID fields. If I
>> were to then select one TLoginUser object to display its details in a
>> detailed form, I would then have it call itself to load its complete
>> data.

>> My rule is if I'm creating a collection of objects, then I populate the
>> list with the light-weight version of the object. If I call for a single
>> object then I create a heavy-weight object.

>I forgot to add that if I select a field in the lightweight object that is
>not populated (eg ExpiryDate field in TLoginUser) or calls a method (eg
>procedure SetPassword), then the lightweight object will call its Load
>method to turn itself into a 'heavy' object. So the "users" of the object
>will be oblivious to the fact that they are using a light or heavy weight
>object.

>> In the TLoginUser example, even when I load the heavy version of the
>> object, it will contain data for Name, LoginId, ExpiryDate and Enabled
>> but for Permissions, it will only contain a PermissionCollections which
>> hold the lightweight versions of a TPermission object.

>> What are your opinions? Is there anyone else doing it differently?

>> Regards,
>> Colin

Re:Light-weight vs Heavy-weight business objects?


Quote
Gary Walker <boreal...@home.com> wrote in message news:39a84cea$1_1@dnews...
> This is an enumerated set. When I load the object from the database I
> usually set up the load method like this:

> function Load( UniqueID: longint ; LoadAmount : ObjectEnumeratedSet )

> In this way I can call the method like so:

> MyHuman := THuman.create;
> MyHuman.Load( 1, [arms, head, torso]; // don't need legs right now...

Interesting technique, but I'm not sure I see how it plays down an object
hierarchy--

That is, given

TProgrammer = class(THuman);
TManager = class(THuman);
TSurfer = class(THuman);

then I need
 MyProgrammer.Load( 1, [arms, head, techManual];
 Mymanager.Load( 1, [arms, head, projectSchedule];
 MySurfer.Load( 1, [arms, head, torso,legs,sunBlock];

How are you handling the extension of the enumerated list so that each
descendent can individualize its load specification without losing
polymorphism?

bobD

Re:Light-weight vs Heavy-weight business objects?


No question about it, you've pointed out the weakness in this
implementation. That's not to say it can't be done, only that it's difficult
and requires LOTS OF THOUGHT.

In my experience, business objects are the result of programmers taking
Encapsulation as far as they can, to the extent that Inheritance and
Polymorphism may suffer somewhat. They certainly take a back seat for me;
I'm often faced with short deadlines, and Inheritance *can* get you in
trouble unless you THOROUGHLY understand the problem domain. That takes
time, and again, I don't always have that.  So inheritance and to a lesser
extent, polymorphism are subordinate to encapsulation. IMHO, that's OK,
because from a certain perspective Encapsulation looks a lot like
Information Hiding, and nothing bad ever happened from too much Information
Hiding!

I've found that many programmers (I'm more of a designer/supervisor now)
will RUIN an objects encapsulation whenever they can. So this is my way of
REINFORCING good behavior.

Back to your original question: "How are you handling the extension of the
enumerated list so that each descendent can individualize its load
specification without losing polymorphism?"

Short answer: I dunno.

Longer answer:

I've seen some programmers avoid this issue by declaring the Load method in
the descendant, and not the ancestor. This solves the problem, but in an
ugly way that frankly, I don't like for obvious reasons. But while solving
that problem would be a great thing, I can't help but emphasize that the
benefits of such a 'flawed' (my words, not yours! :) ) implementation are
enormous.

To me, a programmer who uses such a construct will demonstrate a clearer
understanding of the problem domain, not to mention that, for me (acting as
the project manager, designer, whatever) the issues of maintainability and
interoperability are all but solved.

How's *THAT* for not answering your question???

:)

Gary

Quote
BobD wrote in message <8o9k87$8...@bornews.borland.com>...
>Gary Walker <boreal...@home.com> wrote in message

news:39a84cea$1_1@dnews...
Quote
>> This is an enumerated set. When I load the object from the database I
>> usually set up the load method like this:

>> function Load( UniqueID: longint ; LoadAmount : ObjectEnumeratedSet )

>> In this way I can call the method like so:

>> MyHuman := THuman.create;
>> MyHuman.Load( 1, [arms, head, torso]; // don't need legs right now...

>Interesting technique, but I'm not sure I see how it plays down an object
>hierarchy--

>That is, given

>TProgrammer = class(THuman);
>TManager = class(THuman);
>TSurfer = class(THuman);

>then I need
> MyProgrammer.Load( 1, [arms, head, techManual];
> Mymanager.Load( 1, [arms, head, projectSchedule];
> MySurfer.Load( 1, [arms, head, torso,legs,sunBlock];

>How are you handling the extension of the enumerated list so that each
>descendent can individualize its load specification without losing
>polymorphism?

>bobD

Re:Light-weight vs Heavy-weight business objects?


Quote
Gary Walker <boreal...@home.com> wrote in message news:39a91573$1_2@dnews...
> How's *THAT* for not answering your question???

Actually it was a very complete answer--I'm just still pondering the design
trade-offs you're making. It seems to me that since the Load routine is
parameterized, any reference to head within MyHuman has to adopt some level
of access protection [if assigned(FMyHead) then . . .] because it can't know
in advance what the Load parameters for this particular instance were.

But if that's true, why not just go with a fully lazy load and put the head
loading code into the access gate? It's just not clear to me what
pre-specifying an included member load is gaining you in return for its
costs.

bobD

Re:Light-weight vs Heavy-weight business objects?


One solution one of my staff members came up with was this:

Whenever someone needed to access an object (in this case let's use my silly
THuman class), they would do this:

MyHuman := THuman.create;
MyHuman.Load ( MyUniqueID, [] );

Note the empty set as the second parameter. Now, this empty set would still
work, it would simply just load a small set of data that represented the
object at it's 'highest' level. In other words, calling Load would ALWAYS
work and return SOMETHING valuable to the user to work with. In the case of
THuman, it might be something like:

published
    property Name : string;
    property DOB : TDateTime;
    property Age : integer;
    property IQ : integer;

Typically these would be fields that are stored in the 'topmost' table
representing the entity. In this way he was guaranteed that he would get
SOME useful information. Later (in the life of that object that is), some
other entity (another object, a user, etc..) might need some more
information or functionality from that object. That other entity would
simply invoke the methods needed (e.g. telling the human to RUN --
MyHuman.run(fast) )

That method Run needs legs, right? In the method itself we'd do something
like:

procedure Run
    begin
        if not loaded(legs) then
            Self.Load(MyUniqueID, [legs, knees, feet] );
            // code for running goes here
    end;

This way the user of the object need not worry about whether the object is
in a state where it can run or not. The method would take care of that IF it
needed to...

So this is what we'd gain: The user of the object (which invariably wasn't
the same person who created the object) didn't have to worry about whether
the object was prepared to do the task. They'd simply tell the object to do
it. The object would take care of the details...

I *think* this was what you were asking...

Quote
BobD wrote in message <8ob91t$o...@bornews.borland.com>...
>Gary Walker <boreal...@home.com> wrote in message

news:39a91573$1_2@dnews...
Quote
>> How's *THAT* for not answering your question???

>Actually it was a very complete answer--I'm just still pondering the design
>trade-offs you're making. It seems to me that since the Load routine is
>parameterized, any reference to head within MyHuman has to adopt some level
>of access protection [if assigned(FMyHead) then . . .] because it can't
know
>in advance what the Load parameters for this particular instance were.

>But if that's true, why not just go with a fully lazy load and put the head
>loading code into the access gate? It's just not clear to me what
>pre-specifying an included member load is gaining you in return for its
>costs.

>bobD

Re:Light-weight vs Heavy-weight business objects?


Quote
Gary Walker <boreal...@home.com> wrote in message news:39a94379$1_1@dnews...

> procedure Run
>     begin
>         if not loaded(legs) then
>             Self.Load(MyUniqueID, [legs, knees, feet] );
>             // code for running goes here
>     end;

Okay--interesting strategy. From your description then I'd infer that
  Self.Load(MyUniqueID, [legs, knees, feet] );
triggers construction of an SQL join to the secondary table(s) where
[OptionalPart(s)] properties reside?

bobD

Other Threads