Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design(How-to) of classes containing collections of other classes

How to design classes involving collections of other classes?

General Example:

A Workspace contains number of Projects .
A Project contains large number of Resources .
Each Resource may contain large number of Files.

So here the classes identified can be Workspace,Project,Resource and File. Workspace will have list of Project.Project will have list of Resources and Resource will have list of Files. Of course each class has its related settings.

Now the basic questions are :
a) Who creates and adds a class to a particular collection? Another class or the class containing the collection?
b) Also how to keep track of a particular collection and how to store same?
c) Who audits changes to a particular collection?
d) What are the different design patterns that could be applied in such situations?

Basically I want to reduce the coupling between the different classes .

Thanks Everyone

like image 1000
Amitd Avatar asked Jun 25 '10 09:06

Amitd


2 Answers

There are many kinds of relationships - consider

  • Automobile and Wheels
  • Automobile and Driver
  • Automobile and Registered Owner
  • Customer and Order and Order Line
  • School and Class and instance of Class

If you look at UML modelling you'll see concepts such as Cardinality and Direction and distictions between Aggegration and Composition and questions relating to the life-cycle of the related objects.

It's then unsurprising that we need a range of techniques and patterns to deal with different kinds of relationships.

Concerning d). There's one overriding principle Law of Demeter or principle of least knowledge.

One important technique is then, Encapsulation decrease coupling by hiding information. Automobiles probably have little interest in many details of people, so we might have a IDriver interface on our Person class, IDriver offers the particular methods that Automobile cares about. The general principle being to favour programming to interfaces.

Following that through, we can think about a). Creation. As we're tending to use Interfaces, it often makes sense to use Factory patterns. That does leave the question of who calls the factory. Do we prefer:

   IPerson aPerson = myAutomobile.createDriver( /* params */);  

or

  IPerson aPerson = aPersonFactory.create( /* params */);
  myAutomobile.addDriver(aPerson);

Here I think it's pretty clear that Automobiles don't know much about people, and therefore the second is better division of responsibilities. However maybe Orders could reasonably create OrderLines, Classes create ClassInstances?

b). Keeping track? That's why we have rich sets of Collection classes. Which ones to use depend upon the nature of the relationship (one-one, one-many; etc.) and how we use it. So we pick Arrays and HashMaps etc. according to need. For a Car/Wheel we might even use names attributes of the Car - after all a Car has exactly six wheels (frontLeft, frontRight, backLeft, backRight, spare and steering). If by "store" you mean persist, then we're looking at techniques such foreign keys in a relational database. Mapping between RDBMS and in-memory objects is increasingly managed by nice persistence mechanisms such as JPA.

c). Audit? I've not seen auditing applied specifically at a relationship level. Clearly the automobile.addDriver() method may be arbitrarily complex. If there's a business requirement to audit this action, then it's pretty clear that this a decent place to do it. This is just a standard OO design question revolving around who owns the information. General principle: "Do Not Repeat Yourself" so pretty clearly we don't want every object that calls addDriver() to need to remember to audit, hence it's Auto's job.

like image 161
djna Avatar answered Sep 30 '22 15:09

djna


When designing software I find it useful to look at things from a type theoretic point of view and see where it leads me.

A WorkSpace is of type Project + Project^2 + Project^3... (meaning whatever is true of a list of projects is true of a WorkSpace)

Similarly,

A Project is of type Resource + Resource^2 + Resource^3...

A Resource is of type File + File^2 + File^3 ...

So in a language like C#† you might define you WorkSpace class thusly:

public class WorkSpace : IList<Project> //the important thing here is that you're declaring that things that are true for a list of Projects is true for a WorkSpace. The WorkSpace class may in fact do other stuff too...
{

}

and similarly for the other classes.

Then in your client code you'd use it like this:

foreach (var project in WorkSpace)
{
    //do stuff
}

or

Projects.Add(new Resource() { file1, file2, file3, file4, /* etc */});

Think about the types and their relationships first. Encapsulation is a bit of a low-level housekeeping concept. The principle there is to keep related code close together and unrelated code far apart (where far apart means separated by or behind some kind of boundary, e.g. a function or a class or whatever other boundary concept a language might offer).

†From your posting history I surmise that you are familiar with C# but this applies to other languages.

like image 23
Rodrick Chapman Avatar answered Sep 30 '22 16:09

Rodrick Chapman