Summary: This set of structural patterns deals with resolving the pressures caused by an attribute that turns out to have multiple occurrences.
Motivation
When we want to keep track of an attribute of an object, deeper analysis often reveals that the attribute occurs multiple times, or takes on different values over time.
For example, in the Envision document database [1], we wanted to keep track of documents and their authors. A simple analysis had People objects with a Name attribute. The true situation is more complex: people are sometimes known by their initials or nicknames. Their names are mis-spelled or the order of first and last names is reversed. They get married, divorced, or change their names. A simple Name field does not support these possibilities.
Another problem occurs in the same database. People are in a many-to-many relationship to documents (as they write many documents, and documents may have several authors). However, “Person related-to Document” misses an important characteristic of their relationship: people are related to a document for a particular reason: they are an author, or an editor, or the subject, and so on.
Another example occurs in the phone system. At first glance, it might seem that a phone number has an area code associated with it. Again, reality is more complex: area codes change. When a code is to change, there is first a time when only the old area code works (even though the change is planned). Then there is a “permissive dialing period” when both area codes work (though the new one is preferred). Finally, there comes a time when only the new area code is valid.
Treat Repeated Attributes as Separate Objects |
| []-[] | |
[]-[] |
Forces:
- Attributes of an object turn out to have multiple occurrences, or interesting but changing values over time.
Resolution: Treat the attribute as a separate object. Use relations to and from the original object.
Original Revised
+---------+ +------+ +---------+
| Object | |Object| |Attribute|
+---------+ | |*---*| |
|Attribute| +------+ +---------+
+---------+
The relation to the attribute is usually many-to-many, although in a particular case it may only be one-to-many.
When implementing the relation, you must keep in mind the questions you ask your objects, to know how to best represent the relation. For instance, you may only need to find an object given its attribute.
Related Patterns If a particular instance of the attribute is important, Distinguish an Important Attribute. If there are several categories of attributes, Relate Attributes via Roles. If attributes change over time, Track Time-Dependent Attributes via Roles. (Some design methodologies avoid this pattern by not folding “owned” attributes into a “containing” object in the first place.)
Distinguish an Important Attribute |
| []=[] | |
[]=[] |
Forces:
- An attribute has been made a separate object.
- One of the attribute values is particularly important.
Resolution: Maintain a separate relation for the distinguished attribute instance.
Discussion: Some objects have a preferred version of an attribute. While we might usually traverse from Attribute to Object, we may need to go from the Object to its preferred Attribute. For example, we may wish to find a person using any of the many variants of their name, but we prefer to address letters to them using their legal name.
Original Revised +------+ +---------+ +------+ preferred +---------+ |Object| |Attribute| |Object|*-------------|Attribute| | |*---*| | | |*-------------| | +------+ +---------+ +------+ +---------+
Related Patterns: If there are several categories of attributes, Relate Attributes via Roles. If attributes change over time, Track Time-Dependent Attributes via Roles. The Account Number pattern [2] introduces an artificial distinguished name used to disambiguate people.
Relate Attributes via Roles |
| []-()-[] | |
[]-()-[] |
Forces:
- An attribute has been made a separate object.
- There are many distinguished attributes, not just one.
Resolution: Introduce a Role object that represents the important relationships. A role connects an Object to its Attribute, with additional information about the relationship.
In the second document database example, recall that a person may have a variety of roles in relation to a document: author, editor, subject of the document, or others.
There are often constraints on the roles. For example, a person might have one legal name, one birth name, but any number of other names.
Track Time-Dependent Attributes via Roles |
| []-(<)-[] | |
[]-(<)-[] |
Forces:
- The relation between Object and Attribute is mediated by a Role.
- Attributes’ relationships depend on time.
- We need to track historical (or future) values of the attribute.
Resolution: Store the effective time as part of the information in a role object.
Discussion: If we don’t need historical values, we could just add or remove relationships as attributes become related or unrelated to an object.
When historical information is important, we can add “time” information to the roles. For example, names might have a starting and ending date (to tell when they are valid). A time-based query language might let us ask things like “What was this person’s legal name on 9-2-95?”. (For a discussion of time-based queries, see Fowler’s Recurring Events [3].)
The telephone area code example also demonstrates this problem. When area codes change, there is a lead time when the planned number is known but not yet valid. Thus, the query “What is the area code for this number?” should really be asked “What is the area code for this number as of this date?”.
Summary
These patterns show a progression in complexity, from a simple attribute to a complex time-based set of relationships. Programs may naturally grow in the direction of more complexity, as their models reflect reality more closely.
References
[1] Envision
[2] Account Number, in Pattern Languages of Program Design
[3] Fowler – Recurring Events.