Inheritance and data access methods.

Inheritance and data access methods.

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


dctc42 posted on Friday, January 23, 2009

Hi All,

The project I'm working on involves a somewhat complex business objects hierarchy with several layers of inheritance. It's more complex than I would like it to be but we're gradually upgrading an existing application and can't change the feature set or program flow so we don't have many options.

To give you an idea of what I'm dealing with:

POSDocumentBase is a parent class (abstract) of PayOnAccountDocument as well as SaleTicket. Both concrete classes share the same backend data model (one of things I'd like to change but can't because it impacts about a billion reports and data analysis tools). They also share about 20 properties in the base class.

Do I write data access methods in the base class and override on concrete classes to deal with newly introduced attributes or should I keep the base class out of this and rely on good old copy/paste?

My main concerns are maintainability and performance (I need to handle very large transaction volumes) but loose coupling is also important in case we get chance to refator some of this.

Is using polymorphic techniques in data access methods a good approach? What are some of the pros and cons?

Thanks

RockfordLhotka replied on Friday, January 23, 2009

This is tough.

Inheritance and loose coupling are in direct conflict. Inheritance is one of the most tightly coupled relationship types in all of OOP...

To have any hope of loose coupling, you need to make each class in the inheritance hierarchy be as unaware of its base or subclasses as possible. This is especially true with virtual methods.

When discussing this in terms of data access, what it means is that each class in the hierarchy must be responsible for loading only its data. Which means doing a database query for each class. Which means a given object instance might do 2+ database queries depending on the depth of your hierarchy.

Obviously that can be a performance problem, so few people do that.

An alternative, is to accept a higher level of coupling, and have the leaf node subclass do the database query to retrieve its data, and the data for all its base classes. This clearly breaks encapsulation, because the leaf subclass must know all the fields required by all its base classes up the chain. But it provides good performance.

Yet another alternative, is to define a way by which you generate a dynamic SQL statement (specifically the SELECT clause) by allowing all classes in the inheritance hierarchy to inject their required column names. Then the leaf subclass runs the query. This can obviously become very complex, especially if 2+ tables (or databases) are involved in loading a single object.

ajj3085 replied on Friday, January 23, 2009

As Rocky has pointed out, inheritance is a tightly coupled design.. so that ship has sailed, more or less.

As for subclasses have extra properties / attributes or whatever, what I've done is provide in the base class an InsertSelf method, and an OnBeforeInsertSelf and OnAfterInsertSelf virutal or abstract methods.  These will provide subclasses with a known place in the data update process to do their processing. 

These seems to work pretty well, and I have a similar situtation as you; a base Document class, and in my case I have Quote, Order, Invoice, etc inhertiting behavior from that base class.  But the key is to well define what the base class does, and what the subclass will do (and what it should NOT do), and to only keep common behavior in the super class.  As soon as behavior is not shared, move it down the chain.  For example, I have an OrderBase which is a subclass of Document, and Order, RMA and other documents considered orders inherit OrderBase.  Invoice directly inherits Document, and it adds it's own behavior as well.

So the pros are that, if done right, you have a good design and can add behavior at each subclass level as needed, the con is that by definition using inheritience is creating a tightly coupled design.  It can work... but pay close attention to detail.

HTH
Andy

dctc42 replied on Friday, January 23, 2009

Thanks Rocky and Any for your insight.

I've made my piece with coupling the base class and all the node classes. As you say this is inevitable. What I'd like to decouple as much as possible are the concrete document types. One of my design goals is to be able to deploy a SaleDocument component in a "Cash and Carry" POS app and be able to deploy an OrderDocument component on the web for e-commerce apps.

Currently all these classes live in a single, mountrous, monolith object chuck full of branching logic everywhere. I need to support existing UI's (Win32 app + COM) so I'm having to make some tough choices.

Thanks again for your help.

dlambert replied on Friday, January 23, 2009

I've seen a pattern for populating collections of objects where a parent object runs a query and passes the resulting Reader around to child classes so they children can populate themselves.  This is (I believe) what Rocky meant when he said, "few people do that" (referring to a query per object).  In this case, you're accepting limited coupling in return for performance.

But I've also done something similar with inherited classes.  Run your query and grab the reader in your base class, populate all the base class properties, then pass the reader to an abstract method that derived classes can use to populate their specific data.  If your queries vary from subclass to subclass (which they're likely to do if the data model varies), then you can use another abstract method to get the command to execute, or variations on that theme (for instance, run the query in the derived class and pass the reader to the base).

Again, you're trading increased coupling for (in some cases) dramatically reduced code size.  You'll have to consider whether you like that tradeoff in your application, but it can work pretty well.

JoeFallon1 replied on Friday, January 23, 2009

I use a pattern very similar to what dlambert describes.

Here is a sample for a child BO:

Protected Overridable Sub Fetch(ByVal dr As SafeDataReader)
SetDefaults()
FetchData(dr)
PostFetchData()
FetchChildren()
MarkOld()
ValidationRules.CheckRules()
End Sub

Protected Overridable Sub FetchData(ByVal dr As SafeDataReader)
With dr
If .Read = True Then
  mKey = .GetInt32("key")
 
 'etc.
End If
End With
End Sub

Protected Overridable Sub PostFetchData()
'marker method that can be overridden in child class
End Sub

Protected Overridable Sub FetchChildren()
'load child objects here if there are any using a second datareader
Dim dr2 As SafeDataReader = Nothing
Try
dr2 = New SafeDataReader(DAL.ExecuteReader(DAO.SelectByKey(mKey)))
mLines = Lines.GetLines(dr2)
Finally
dr2.Close()
End Try
End Sub

In a derived class you can choose to Override any of these methods and optionally call back in to the Base class to fill the common data first. You can also decide which level will determine the query to populate the dr and branch/override accordingly.

Joe

 

Copyright (c) Marimer LLC