Tuesday, January 19, 2010

Relationships between objects

This article implements the beginner pattern, but it can also be useful to recap the terms we use lightheartedly in discussions about object-oriented programming.

In the definitions, we will talk about a relationship between a Source and Target objects, or equivalently between a Source and Target class. In general, there are some orthogonal properties that can be assumed by nearly any relationship:
  • multiplicity: a relationship may link to one target or to N targets
  • directionality: a relationship may be unidirectional (Target has no idea it exists) or bidirectional (Target has an inverse relationship towards Source).
Is-a relationships
In this kind of relationship, Source is an instance of Target and the operator instanceof, where available, returns true:
$source = new Source(); 
var_dump($source instanceof Target);
There are two ways to obtain an is-a relationship.
Inheritance (aka subclassing) is a unidirectional, many-to-one relationship which is commonly declared with the keyword extends in php and other programming languages. Source inherits all the protected and private methods and properties of its unique Target.
Implementation is a relationship towards a Target interface where the Source class declares (with the implements keyword) that its public methods conform to Target's signatures definition. A Source can implement multiple Targets, but it does not inherit concrete code from any of them.
Subclassing is often abused to exploit the methods inheritance. Moreover, it is a static property because you cannot change the Target superclass at runtime. Substituting it with an implementation often improves the situation because Source can then have multiple parent classes (but you must be careful that a real is-a relationship is still present).

Has-a relationships
An has-a relationship is established when a Source object maintains a field reference to a Target object; this paradigm is applied to collaborators which are used throughout many methods of Source.
Has-a relationships are called associations in Uml jargon, and there are two special forms of it.
Aggregation is an association which implies a part-owner relationship. It is indicated with an empty diamond in Uml class diagrams.
Composition is a particular aggregation in which the parts (which are the Source objects) cannot be shared with other Targets. This difference is reflected by a black diamond. Both specializations have a multiplicity of one.
There may be associations which are neither aggregations nor compositions, since they are references to an external service for example. The term composition is often used for antonomasia to indicate every kind of association, particularly in the catch phrase Favor composition over inheritance.
If done right (without an hardcoded new operator or creation method), an association is not a static relationship because you can decide at runtime what the pointer on Target or Source links to, as long as the destination object is-a implementation or subclass of the original one.

A dependency is established when Source knows Target someway and if we delete Target, the Source class cannot work correctly anymore. Maybe it does not compile, maybe it generates errors or exceptions when certain methods are called. Maybe Source creates Target (if it is a Factory), but the common case is an object passed as a method parameter.
Another cases we may encounter is a dependency towards a Singleton. This is an awkward case because the dependency is hidden and the Api does not show it clearly. The same goes for collaborators created in the constructor or methods instead of being asked for.

It can be said that the has-a and is-a relationships are stronger cases of dependency, since an hypothetical deletion of the Target breaks Source in both cases.
Here's the catch: most of these dependencies cannot be avoided; what we can do is keeping abstractions as Target of dependencies, such as interfaces and base classes. The less code classes depends on, the more decoupled and versatile is the design: interfaces have no code at all (except for method signatures) and are the best thing to depend on in object-oriented programming. They are the ideal point of conjunction of different modules and object graphs.
Abstract and base classes have little code too, but they can grow quickly and you can inherit only from one of them, while not being able to change the Target class easily.

I hope this formal definitions of relationships will help you better comprehend the articles in the Design Pattern series, especially the ones to come.

1 comment:

gabriel solomon said...

Congrats on this great article series