In your design, the backend and the objects it holds are all aspects of the same representation, so I don't see anything wrong with allowing them to hold a reference to the backend object.
If you want to change your design, instead of creating an object hierarchy, you could have methods on the backend object are responsible for finding children of each object:
class Backend
{
List<ObjectC> GetObjectCInstances() { /* ... */ }
List<ObjectB> GetObjectBInstances(ObjectC parent) { /* ... */ }
}
This would make it obvious to the caller that you are performing another query each time you try to get the set of children. This is useful for anyone trying to understand your code at a later date, and is also very useful to allow you to switch out the underlying storage mechanism (either at a later date, or if you create an interface IBackend
, at runtime - for test purposes).
Another design you could consider is separating the query generator (BackendObject
) from the object that actually fetches the queries. Let BackendObject
populate query objects, but not invoke the queries. BackendObject
then passes the query objects to ObjectC
. ObjectC
can invoke the query when it needs to return its list of children.
class Query<T>
{
public Query(string query) { this.query = query; }
public List<T> InvokeQuery() { /* ... */ }
private string query;
}
class Backend
{
List<ObjectC> ObjectCInstances
{
get
{
// Do the query for C, since we have to
var result = new List<ObjectC>();
foreach(var instanceData in GetObjectCFromDb)
{
// But let ObjectC query for ObjectB when it needs to
var objectBQuery = new Query<ObjectB>(
"select * from ..."
);
result.Add(new ObjectC(objectBQuery));
}
return result;
}
}
}
One more option: Use delegates (regular, anonymous, closures, etc) to do your data hiding, and avoid passing references. This way, you can still do the queries in Backend, but you can avoid letting C/B/A know that Backend exists.
class Backend
{
public List<ObjectC> ObjectCInstances
{
get
{
var result = new List<ObjectC>();
foreach(var cInstance in DoQueryForC())
{
result.Add(new ObjectC(GetObjectBFromDb));
}
return result;
}
}
private List<ObjectB> GetObjectBFromDb(ObjectC parent)
{
var result = new List<ObjectB>();
foreach(var instanceData in DoQueryForB(parent))
{
result.Add(new ObjectB(GetObjectAFromDb));
}
return result;
}
private List<ObjectA> GetObjectAFromDb(ObjectB parent)
{
var result = new List<ObjectA>();
foreach(var instanceData in DoQueryForA(parent))
{
result.Add(new ObjectA());
}
return result;
}
}
class ObjectC
{
internal ObjectC(
Func<ObjectC, List<ObjectB>> queryForBChildren
)
{
this.queryForBChildren = queryForBChildren;
}
public List<ObjectB> Children
{
get
{
return queryForBChildren();
}
}
Func<ObjectC, List<ObjectB>> queryForBChildren;
}