Many-to-one relationships are very common, especially in applications
involving databases. The reason for this is that the many-to-one
paradigm is a common scenario in the real world, and programming
languages, databases, and other technical constructs attempt to
represent the real world.
Consider the relationship between a
car and its tires. Composition solves the problem of how a car "has"
tires. It would go something like this:
///Here is a generic implementation of a Car class
public class Car
{
private List<Tire> _allFourTires;
public Car()
{
_allFourTires = new List<Tire>();
}
}
///And here is the class "Tire", which composes Car
public class Tire
{
private string _model;
private string _type;
private int _width;
private double _aspectRatio;
///You get the idea
}
The
List<> type is a great way to solve the problem of many-to-one
composition, but there is another way in C#, and it is called
"Collections."
Collections are implemented in a lot of the base .Net objects, including DataSets and DataTables.
For instance, consider the below code:
///Assume I have a DataSet called myDataSet, and it has
///a bunch of datatables in it
foreach(DataTable table in myDataSet.Tables)
{
foreach(DataRow row in table.Rows)
{
///Do some stuff with the table
}
}
The reason DataSet has a .Tables and a DataTable has .Rows is because they implement the IEnumerable .Net interface.
As
mentioned above, an alternative to implementing the IEnumerable is to
have a DataSet have a List<> variable with DataTables, and to
have a DataTable have a List<> variable of DataRows, but over
time, the IEnumerable is easier to manage.
Interfaces are a
public contract between classes. They're basically a grocery list, and
if you plan on using them, you must have everything on the grocery
list. Luckily, IEnumerable has only one "item" on the list. That item
is a public method called GetEnumerator().
When implementing a
paradigm such as this, the effect you're really having is to create a
bridge between the container class (Car), and the containee class
(Tire). So, in between Car and Tire goes the IEnumerable class. In the
below example, we'll call it TireCollection
/// The container class
public class Car
{
///A global variable of the bridge (IEnumerable) class
private TireCollection _tireCollection;
/// Constructor - makes a tire collection based
/// on the model
public Car(string model)
{
makeTireCollection(model);
}
/// This is just sort of a factory method - I could
/// get more abstract, and create a TireCreatorFactory
/// class, but I think this is sufficient for this example
private void makeTireCollection(string model)
{
if (model == "Ford")
makeFordTireCollection();
}
/// Makes the tire collection for Ford cars
private void makeFordTireCollection()
{
List<Tire> tireList = new List<Tire>();
tireList.Add(new Tire("Bridgestone", "P", 215, 75));
tireList.Add(new Tire("Bridgestone", "P", 215, 75));
tireList.Add(new Tire("Bridgestone", "P", 215, 75));
tireList.Add(new Tire("Bridgestone", "P", 215, 75));
_tireCollection = new TireCollection(tireList);
}
/// This is an unnecessary attribute, but if I didn't have this
/// I would have to do:
/// foreach(Tire in car)...
/// With this attribute, I can do
/// foreach(Tire in car.Tires)
/// I like that better
public TireCollection Tires
{
get { return _tireCollection; }
}
}
Notice
in the above, I have an attribute called Tires. As mentioned in the
comments, this is an unnecessary attribute, but I think it looks
cleaner when we go to implement the Car class.
Below is the IEnumerable class TireCollection. Note that it has a method called GetEnumerator():
/// This essentially acts as a bridge between
/// the Car class and the tire class
public class TireCollection : IEnumerable
{
///We do have a List<> variable in the TireCollection.
///So, in effect, we don't get away from having a List<>
///variable...the collection simply buys us a "decoupling"
///effect
List<Tire> _tireList;
public TireCollection(List<Tire> tireList)
{
_tireList = tireList;
}
public IEnumerator GetEnumerator()
{
return (_tireList as IEnumerable).GetEnumerator();
}
}
Then, the Tire class looks pretty typical
/// This is the Tire class - there are many tires on a car
/// Well...four, anyway
public class Tire
{
private string _model;
private string _type;
private int _width;
private double _aspectRatio;
/// Constructor
public Tire(string model, string type, int width, double aspectRatio)
{
_model = model;
_type = type;
_width = width;
_aspectRatio = aspectRatio;
}
/// This is simply a public method
public string GetTireProperties()
{
return "Tire is of model " + _model + " with type " + _type;
}
}
Now comes the question of how to implement this code. This part is the simple part:
Car car = new Car("Ford");
foreach (Tire tire in car.Tires)
{
///Output tire.GetTireProperties()
}
It
is a few more steps to implement an IEnumerable interface, but it's
worth it for those solutions that wish to have looser coupling between
the composition relationship.
In the comments in the
makeFordTireCollection() method in Car, I mention that I could have
just as easily implemented a TireCreatorFactory class that assembles a
Tire collection for me. The effect of doing so would have provided a
higher degree of decoupling between Car and Tire, which is probably a
more optimal situation, since loosely coupled class interactions are
highly valued in object oriented design.