Object responsibility suggestions.

Object responsibility suggestions.

Old forum URL: forums.lhotka.net/forums/t/1881.aspx


jlazanowski posted on Friday, December 01, 2006

In my model I have several classes that have a myriad of child collections. For example my Driver class has the following collections:

 

            Address

            Contact

            Schedule

            HoursOfService

            Events

            Registrations

            Training

            Vacation

            Clock

            Differential

            Rotation

 

Now not all of these are “only” child collections, but from the perspective of this question that’s the only consideration. When I load the Driver object, I want to fetch all of the children. It’s the children’s responsibility to load itself  save itself etc.

 

I’m using LLBLGen Pro for my DAL so I have a BLSupport class that “brokers” the object loads and defines the predicates. LLBL uses what are called pre-fetch paths to load related entities in a single call, so the broker class is set-up to predefine these pre-fetch paths.

 

Now to the crux of the question. When I load the driver class for edit I want ALL of the child objects loaded which is no problem, however there will also be times that I use a readonlybase to load the driver object and “some” or “all” of the children depending on the case.  The way I have it setup right now, I have a single class that passes a secondary item into the criteria class _childType that then uses a switch to see what child objects to load. I’m wondering if this is the right approach, or if I should be defining one read-only base per collection type that I wish to load or combination thereof.

 

The problem I see with that is that it can get pretty messy pretty quick, I could potentially need to create a TON of read-only bases to get the correct combination. Anyone doing something similar or have suggestion on how to handle a situation like this?

 

Thanks,

Justin

Fintanv replied on Friday, December 01, 2006

This comes up on a fairly frequent basis, and the answer is the oft repeated mantra of "Design your classes based on what they do (target functionality), not based on the data relations".  In other words having a Driver class with 11 child collection classes is an indication that your design is off.  This will not scale well, and as you are finding out, it leads to messy solutions.  Rather you should design your business objects around the use cases your customers have identified (usually with your help to keep them on task ;-).  For instance there may be a DriverPersonalInformation class that holds the Address and Contact information.  Another may be DriverTrainning that will hold the trainning info for a specific driver.  You would pull the info for a specific driver by having the user select from a list maintened by your DriverReadOnlyList collection.  Yes you are going to have many more classes, but they will ultimately be more focused for the task at hand, and will improve extensibility and maintainability.

Best Regards,

Fintan

jlazanowski replied on Friday, December 01, 2006

I agree, this topic does come up quite a bit, however I thought this one was a bit different.

 

11 child collections is large, but it is in fact based on the cases, and as stated in the previous post some of these are not only children, but parent objects depending on the case.

 

My question was more relating on what the best method for dealing with a situation like this is, when I pull a single driver to edit, I do what to load the entire object graph because that is what the case states, however there are many times that I will be dealing with a collection of drivers, and loading the entire graph for each of these is quite obviously inefficient.

 

So from the Responsibility standpoint, the Driver object should call its children and the children should load their data.

 

When I’m dealing with other cases, (I’ll use scheduling in this example), I want to call a collection of driver object, and child object scheduling. For this case I don’t need all of the other items, I don’t care about contacts, or events, but I would need collections like HoursOfService and Clock to make sure that the driver has the hours available to schedule.

 

I’m working on the principal that behavior should only exsit once within my object model, using this principal I should say that I should have a DriverSchedule object that loads Driver objects and children Schedule, HOS, and Clock. That seems fine to me, what concerns me is the sheer number of objects that are possible (maybe not now but later) within the system to handle all of these responsibilities.

 

I’m wondering if it’s better to create a class as I have now where depending on the passed criteria, it loads the appropriate child object(s), however I’m not sure that this will end up causing a maintainability nightmare down the road.

 

I’m not trying to discount your suggestion, just not sure that I expressed my original question well enough I have a tendency to ramble..Wink [;)]

 

TIA,

Justin

malloc1024 replied on Friday, December 01, 2006

If you need to load a specific driver and edit it, you would return the driver and its children.  If you need lists, you would use read-only lists.  To keep the number of read-only lists down, you would flatten the lists as much as possible.  I usually end up with many read-only lists, but I really do not find them that difficult to maintain.  If you are still concerned about many read-only classes, you could use datasets (gulp) or you could create your own generic read-only collection that would mimic a dataset in the way it handles data.  This would allow you to create as many lists as you need from only a few classes.

Your idea of passing a criteria object that tells the driver object to load which children is not a bad idea; however, it does complicate the driver object a bit.  What happens when you only want to load one property on a child?  Do you return all the children’s data for that object?  If so, you are returning too much data.  Further, the driver object now has more responisblies and covers more use-cases.  This will make it more difficult to maintain the driver class.

jlazanowski replied on Friday, December 01, 2006

Exactly this is specifically the problem that I am struggling with it really I suppose comes down to the lesser of two evils.

 

a.)    do I complicate the driver object by having it decide what children to load

b.)    complicate the object model by creating a multitude of classes to figure out what object graph I need to load

 

Problem a presents as having complexity within a single object, adding more responsibility and perhaps unwanted or unexpected behavior

 

Problem b presents as having maintainability issue. Since I’m hand coding the BL If I need to have say 10 different classes for combinations of parent/children objects and I need to change a field in the parent class, I need to “fix” ten different classes.

 

Maybe I’m just missing something simple, or my idea of a concept is out of whacky with reality.

 

On a side note

malloc1024:

What happens when you only want to load one property on a child?  Do you return all the children’s data for that object?  If so, you are returning too much data.

 

Since it’s the child’s responsibility to load itself with data, wouldn’t this really be a different behavior and thus a different class one with simply the property to be returned?

Or, does this really need to be separated out, if I had a very heavy class (resource wise i.e. a picture) then I could see where that would be important, but if we’re talking about 10 “normal” properties of any object being loaded from persistence would it really be that big a performance benefit to separate out one field?

 

P.S. Dataset’s are out Wink [;)]

 

I appreciate the responses.

 

Justin

 

 

Wal972 replied on Friday, December 01, 2006

When you are defining the driver you would have different types of children which are used in natural combinations as you illustrated before. When you edit the driver are you working on all these different ones or could break them down into groups and USE them thus allowing you to get the ones you need. The Driver doesn't need to act as the gatekeeper for the whole show.

malloc1024 replied on Friday, December 01, 2006

You are correct that both options have downsides.  Programming is about making compromises and choosing the best solution for the problem.  This is what makes it challenging and fun.

 

You are correct that option b can be a problem to maintain.  However, if you need to add additional properties to the driver class, it doesn’t necessary mean you will need to add those properties to the read-only version.  It depends on what you want to display to the user.  If you want to make sure both have the same properties, you could use an interface to make it easier to maintain.

 

Keep in mind that objects are defined by behavior not data.  Properties are considered data.  Basing your decision to have your driver class act as a read-only list based on data might not be wise.  I have never tried option A, therefore I can’t say it won’t work; however, I have my doubts that is it a better option that B.  If you feel that option A will work for you, go for it.  At the very least you will find out if it works for you.  

ajj3085 replied on Friday, December 01, 2006

Actually solution B is the most maintanable solution, because if your use case changes, it only affects one set of objects (assuming none are shared between use cases). 

Now, the data access code may be shared through refactoring to internal helper classes.  That way if the schema changes, the changes should be mainly to the data access class and affected use cases.

B is more complex though because there are more objects to learn, but if you really have so many, y ou can use namespaces to group objects which handle a single use case together.  In that way you can more easily figure out which objects you need, because namespace X helps you acomplish these tasks.

Remember, code reuse does not equal maintainablity.  It can help, but it can also reduce maintainability in many scenarios as well.

HTH
Andy

Wal972 replied on Saturday, December 02, 2006

Does the Driver act as a container or does it have its own properties. Because I had a complicated Customer object which I broke down like you did. Then I simply created different ROOT objects which pulled together the relevent child objects for the task at hand. Like the Driver Schedule. etc.

swegele replied on Wednesday, January 31, 2007

Hi Justin,

I have been using CSLA for years and am currently writing a CSLA business layer that uses the LLBLGen DAL.

Let me tell you what many, including myself, have learned...making those readonly classes is worth it (using a code generator of course)

ReadOnly is a valid behavior that warrants an object.  When you think of the "Behavior" you see a readonly object.  But when you think of the "Data" or "Properties" you think Sheesh these all look the same.  And so you begin to try and make them all inherit from one object or something like that.  Things get messy and complex quickly.  I know because I tried and just kept hitting walls.

I agree it is a lot of code to write.  So the solution....don't write it, rather generate it!  Figure out the pattern and use LLBL or CodeSmith to make a template.  It is well worth your time.

Quote from Rocky at SanFran Dev conference:
"Anyone who isn't using code generation is an idiot"

If you know him you know he isn't a harsh guy...just a big teddy bear so he wasn't being cruel as much as making a passionate point.  But regardless that statement motivated me to go home and starting learning more.  I found CodeSmith to be a good fit for CSLA.

Regarding changing one property and then having to change many other places...I know what you mean.  But using interfaces helped me at least to immediately see which ones needed changing.

Other tip I will give you...don't change the framework unless absolutely necessary because it becomes very painful to upgrade later and Rocky is cranking these out as quick as I can check this forum!

HTH

Sean Wegele

SonOfPirate replied on Thursday, February 01, 2007

Justin, I am actually in the same boat.  I have a "root" object with 6-8 child collections similar to what you've described.  This is for a web app and I am running into performance problem because I am forced to populate all of the child collections along with the parent because of the use case involved.

In my case, the object is being displayed using a tab strip style interface where the parent object's properties are displayed on the "General" tab and each child collection has its own tab with its elements displayed in a GridView on each.  I have debated the approach I have used and found this post very interesting to read.

In my case, I approach the design with a couple "tenets" in mind:

  1. Unless I plan on implementing inline/in-cell editing, all of my collections contain read-only versions of my objects.  Along with this, I am able to flatten any relationships and return a single, readable result set.  I don't need the foreign keys used in the relationship to the Contact, for example, I need the Contact's name instead.
  2. Any time I need to perform edits I will have an editable root object.

This is well and good but does not address the child collection issue except that I've already declared that each child collection should contain read-only objects.  Note that I did not say that the collection was read-only - have to add and remove items somehow, ya know?

My default implementation of a child collection is based on the examples Rocky has provided where the parent instantiates the child collection and populates it from the same data reader that the parent used for itself.  Unfortunately, as you have found and I am experiencing, this has a performance impact when the number of collections and number of child items elevates.

The option in my case is to "break" or decouple the relationship between the parent and the child collections.  So, instead of having a Driver object with an HoursOfService child collection that is instantiated internally and populated when the Driver object is fetched, the DriverHoursOfService collection becomes a root collection that is responsible for retrieving its own data.  To make this possible, the factory method/constructor requires the corresponding identifier for the Driver object to lookup.  I can still have a method/property in my Driver class that returns the collection (for simplicity) but my DriverHoursOfService collection is free-standing and can be used with any UI configuration.

In addition, by decoupling the relationship between the parent and the child collections, if I need to remove a collection - or add a new one - I don't have to change the parent's class.  I simply remove the existing or add the new class(es) into the application and they are good to go.

For example, let's say we needed to add Attachments to the Project class in Rocky's ProjectTracker application.  By creating a ProjectAttachments collection that accepted the ProjectId as a parameter in the GetProjectAttachments method, we can simply "plug" it into the app and make use of it in our client app.  If we use the other approach, not only would we have to create the ProjectAttachments class, but we'd have to modify the Project class to instantiate and populate the collection and expose a property so we could access it.  Then, we'd have to modify the sproc (assuming we're using one) to return the additional result set as well.  So, three changes for the price of one.  This is what Andy was getting at.  And, I don't remember the exact wording, but there is an OOP principal that dictates that we should only be modifying one thing per change.

So, as much of a headache as it will be for me to change my UI to adapt to this approach, I am changing my app in this manner.  I can then use lazy-loading on the UI tabs and save the initial load time by not incurring the overhead of creating, retrieving and populating the child collections that the user isn't interested in.

HTH - just my two cents.

P.S. It's actually not that difficult to implement the child collections BOTH ways although I am not sure if it breaks the "one responsibility" rule.  Having a factory method/constructor that accepts the parent's identifier and uses the DataPortal_Fetch method to populate its contents AND having a second factory method/constructor that accepts a DataReader and populates itself directly allows you to decide how to use the collection but gives you the freedom to choose either method.

 

swegele replied on Thursday, February 01, 2007

SonOfPirate

That last statement about doing BOTH is very wise...it gets messy fast trying to do both because there are so many different permutations of workflow.  I did my last full CLSA project with all my root objects as switchable and it cost me ALOT of time and headache.

I won't do that this time.

Sean

Copyright (c) Marimer LLC