Conceptual Modelingg - Object-Oriented Analysis and Design for Information Systems: Modeling with UML, OCL, and IFML (2014)

Object-Oriented Analysis and Design for Information Systems: Modeling with UML, OCL, and IFML (2014)

CHAPTER 7

Conceptual Modeling

Patterns

Building a conceptual model is much more than placing concepts, associations, and attributes together. Frequently the model does not work not for being wrong, but for being too complex to maintain. Analysis patterns consist of solutions already tested that can resolve families of recurrent problems. When adequately used these patterns may reduce significantly the complexity of an otherwise naïve conceptual model. This chapter presents and sometimes reinterprets some patterns such as high cohesion, quantity, strategy, account, temporal, and others.

Keywords

Design pattern; analysis pattern; high cohesion; temporal pattern; conceptual modeling

Key Topics in this Chapter

• High cohesion

• Specification classes

• Quantity

• Measure

• Strategy

• Composite

• Organizational hierarchy

• Account/transaction

• Range

• Temporal patterns

7.1 Introduction to conceptual model patterns

Class diagrams that represent conceptual models almost always become more complex and hard to maintain than analysts and other team members would like. There exist techniques that reduce the complexity of such diagrams, and at the same time, improve their expressiveness. Naïve models may be unnecessarily complex.

These techniques are called analysis patterns and may be understood as a special case of design patters (Gamma, Helm, Johnson, & Vlissides, 1995) that are applied specifically to the conceptual model. Most patterns presented in this chapter were first described by Fowler (2003).

Analysis or design patterns are not rules that must be obeyed, but recommendations based on previous experience. It is the responsibility of the analyst to decide when to apply a given pattern in the models.

7.2 High cohesion1

High cohesion is so important for object-oriented modeling that it is considered more a principle or guideline axiom than a pattern itself. A concept with high cohesion is more stable and reusable than a concept with low cohesion, which can become rapidly confusing and hard to maintain. Most systems could have all their information represented in a single table, but that would be an extreme worst case of low cohesion, and it would not be practical.

Earlier, we mentioned that concepts should not have attributes that belong to other concepts (e.g., a car should not have the owner’s ID as one of its attributes). Attributes also must not have class names or data structures (set, list, array, etc.) as data types because all those situations are evidences of low cohesion (a class with that kind of attribute is probably representing more than one single concept). For example, an instance of Order should not have an itemsList attribute because items have their own attributes and associations; they should appear related to Order by a 1 to * association.

In the example of Figure 6.65, when it was discovered that a customer could have more than one address, instead of the naïve solution of creating attributes like address1, address2, etc., or even address:Array of Strings, we created a new concept (Address), which was associated to the customer. Imagine how hard it would be to deal with information such as deliveryFee if the addresses were modeled as an array of strings.

Concepts and attributes should have a simple structure with high cohesion elements. Evidence of low cohesion includes scenarios where some attributes of a class may be null depending on the value of other attributes. If complex constraints (invariants) are necessary to keep a concept consistent, this is equivalent to using duct tape to keep the pieces of a broken pot together.

In Figure 7.1, the attributes paidValue and paymentDate are mutually dependent: both are null or both are not null. An invariant must be added to the class to avoid the situation where one of them is null while the other is not, in order to avoid inconsistent instances. Invariants are good to represent business rules that must always be valid. But when an invariant just relates two attributes of a class, it is a strong clue of lack of cohesion.

image

FIGURE 7.1 A class with low cohesion because attributes depend on each other.

A better way to model this situation is shown in Figure 7.2, where the concepts Order and Payment appear separated but with high cohesion. In this case, there are no optional attributes depending on each other and no need for constraints.

image

FIGURE 7.2 A modeling solution with high-cohesion classes.

Another potential problem related to low cohesion is the existence of groups of strongly correlated attributes, as seen in Figure 7.3 where it can be observed that groups of attributes have stronger relations inside the group, such as street, number, city, and state, which compose an address, or ddd (distance direct dialing) and phone, which are part of a complete phone number, or passportNumber and issuingCountry, which are part of passport information. The relation that exists inside each group appears implicitly on the order they are presented in the class. This is bad, because if the attributes were sorted in alphabetical order, for example, the weak structure of the groups would be messed up: city, ddd, issuingCountry, name, number, passportNumber, phone, state, and street. The understanding of a concept should not depend on the ways its attributes are sorted.

image

FIGURE 7.3 A class with low cohesion because of strongly related attributes.

A better solution to improve high cohesion in that model is shown in Figure 7.4. It also opens a way to other modeling possibilities such as, for example, allowing a person to have more than one address, or more than one phone. In those cases, it is simply the case of changing the association multiplicity.

image

FIGURE 7.4 A solution to Figure 7.3 with high cohesion.

Yet another situation occurs when some attributes repeat their values for a group of instances. Figure 7.5 presents an example. In this figure, the attributes customerName, customerId, and customerBirthDaterepeat the same values for different orders when the customer is the same.

image

FIGURE 7.5 A class with low cohesion because it has attributes that repeat values in different instances.

This type of situation can be eliminated by splitting the concept into two associated concepts with better cohesion, as shown in Figure 7.6. Notice that even attributes’ names are shortened with this choice of model.

image

FIGURE 7.6 A model for the example of Figure 7.5 with high-cohesion classes.

7.3 Specification classes

A special case of low cohesion happens when an object is confused with its specification. That situation is very frequent: sometimes, products or physical items share a common specification, while their exemplars may have some differences among them.

One example of specification already discussed in the Livir example is the difference between the concept of a book as a published title, and a book as a physical copy of a published title. If both book concepts are not distinguished, lots of trouble will occur. In past examples the class Book refers to the published title, while Item stands for a set of physical copies. Thus, an instance of Book is a specification for a set of undistinguishable instances of Item.

Another possibility is to consider that each item is a single copy of a book. That would eliminate the need to include the quantity attribute in class Item, but it would create more instances and associations for an order if the order includes more than one copy of the same book.

Depending on the information needs, it would be necessary to differentiate one copy from another. For example, if used books are to be sold, the conservation state should be kept for each copy. In this case, instead of a set of items, the Book class could be used as a specification for the class Copy, whose instances are single copies of a book.

The specification class Book should contain the attributes and associations that do not vary for different copies. For example, different copies of the same book have the same title, author, ISBN, base price, etc. However, conservation state and discount may be distinct for each copy. Thus, these attributes (and associations, if any) are placed in the Copy class, as shown in Figure 7.7.

image

FIGURE 7.7 A class and its specification class.

It is possible for a class to have more than one specification. For example, the discount of the book could be predetermined depending on the conservation state of the book (an enumeration with values such as new, used, damaged, etc.). Thus, a copy, besides being specified by the published title, may also be specified by its state of conservation. Thus, Conservation would be a class with some instances that may specify physical copies of books, and define their discounts as shown in Figure 7.8.

image

FIGURE 7.8 A class with two specification classes.

The instances of class Conservation have two attributes. The first is the conservation state, which is a value obtained from the enumeration ConservationState. There are initially only three possible values: new, used, and damaged. The second attribute is the discount percentage that would be applied to a copy depending on its conservation state. As it is expected that books with the same conservation state have the same discount, then the attribute state in class Conservation must be unique and therefore marked with entuniqueent. Otherwise, different discounts could be associated to the same conservation state.

7.4 Quantity

Frequently the team faces the need to model quantities that are not merely numbers. For example, the weight of a book could be defined as 400. But 400 what? Grams? Pounds? Kilos? One solution is to define a specific type for the weight (Pounds, for example), and then keep using it consistently. The attribute then could be declared as weight:Pounds. But that demands that all books have their weight expressed in pounds.

In some cases, the system must be configurable to support different weight units. In some countries, grams and kilograms are used, while others use pounds, or even other units. If the attribute has a type that refers to a specific unit, then the system must suffer refactoring to accommodate new weight units.

However, the quantity pattern allows different unit systems to coexist and to be easily exchangeable. The pattern consists of creating a new primitive type Quantity, which has two attributes, as shown in Figure 7.9.

image

FIGURE 7.9 Definition and use of Quantity.

This way, the weight of each book is specified as a primitive quantity formed by a numeric value and a unit, whose type is an enumeration with possible values defined as grams, pounds, and kilos.

If a unit conversion is needed, then an option is to transform the enumeration Unit into a normal class, and associate a new class Ratio to two units, origin and destination, as shown in Figure 7.10. When a quantity in the origin unit has to be converted to a quantity in the destination unit, then the value of the quantity associated to the origin unit is divided by the conversion ratio.

image

FIGURE 7.10 Unit with conversion ratio.

Thus, for example, an instance of Unit whose name is “grams” may be linked to an instance of Ratio as origin, which may be linked to another instance of Unit, named “kilos.” The value of that ratio should be 1000, because in order to convert grams into kilos, the original value in grams must be divided by 1000.

7.5 Measure

An evolution of the quantity pattern is the measure pattern, which can be used when a number of different measures over an object must be taken possibly at different times. For example, a person in observation at a hospital may have many measures taken from time to time: body temperature, blood pressure, blood glucose level, etc. Thousands of different measures could be taken, but usually just a few are actually taken for each patient. Therefore, to avoid creating a concept with thousands of attributes with most of them likely being null, a better option is to use the measure pattern as shown in Figure 7.11.

image

FIGURE 7.11 Definition and use of the measure pattern.

Thus, a patient may have a series of measures taken, each one assessing a different phenomena and presenting a value that corresponds to a quantity (following the quantity pattern).

It is still possible to make these patterns a little more sophisticated by adding attributes in the Measure class to indicate the instant of time at which the measure was taken, and also the validity of the measure. For example, the fact that a patient had a fever a couple of hours ago does not mean that she still has it. This evolution is explained in Section 7.12.1.

Another evolution for this pattern is to add an attribute to define the precision or accuracy of the measure taken. For example, body temperature usually is measured with a precision of 0.1 degrees (Celsius or Fahrenheit).

7.6 Strategy

It was mentioned that one of the great requirements challenges is to manage their change. Transitory requirements must especially be accommodated in the system design so that when they change the impact to the system, is minimized.

Some cases are relatively easy to address. For example, if it is already decided that the system must operate in different countries, the quantity pattern could be used to deal with currency and other measures that may vary from country to country.

But there are much more complicated situations. For example, the tax calculation procedure may vary a lot from country to country and even from state to state in some countries. There are some taxes that are calculated in relation to the profit, others with regard to the selling price, and so on. Methods for calculating taxes change from time to time, and in some countries new taxes are created every year. Systems must be prepared to cope with that, but the changes are completely unpredictable. Just configuring parameters usually is not enough.

Another example is the bookstore’s discount policy. Current policy could be applying 10% off on orders above 100 dollars, for example. However, after some time, creative and unpredictable policies may be defined by the sales department, such as:

• Give a free book with a value up to 50 dollars for orders above 300 dollars.

• Give 20% off for up to two books on the customer’s birthday.

• Give 5% off on horror books on Friday the 13th.

• The customer spins a wheel and it defines the discount she receives, from 1% to 10%.

Moreover, it would be possible to combine policies, if applicable, or choose the one that gives the higher discount.

The strategy pattern suggests that in these cases, the procedure (e.g., calculate taxes or discount) must be separated from the data to which it applies. In other words, if a discount is applied to an order, then the discount should not merely be a method implemented in the Order class. The discount should be defined as something easier to change. The solution proposed by the strategy pattern is to create an abstract class associated to the order. This abstract class may have concrete subclasses that represent concrete discount policies, as shown in Figure 7.12.

image

FIGURE 7.12 Strategy pattern.

Thus, each instance of Order is associated to an instance of one of the subclasses of the Discount strategy.

UML does not allow an attribute to be declared {abstract}; only classes and methods may be declared so. However, as derived attributes will be implemented by methods eventually, the idea of an abstract derived attribute stereotyped with entabstractent as shown in Figure 7.12 is coherent to the UML spirit.

Thus, the abstract class Discount implements an abstract derived attribute amount, which is implemented in a concrete way in each of its subclasses. If some of the strategies need data from the customer or from the order to calculate the discount, that data can be accessed through the associations from the discount abstract class to the classes that own the information.

This pattern minimizes two problems related to requirements change. First, it keeps the discount strategy applied when the order was issued, even if strategies change in future. Second, if new strategies are created later, it is sufficient to implement one or more subclasses for Discount. This does not affect the old strategies, or the old orders.

7.7 Composite2

As mentioned in Section 7.6, sometimes different strategies must be grouped and a combination or selection of them must be applied. The combination of different discount strategies can be obtained, for example, by the sum of the values obtained for the derived attribute amount for each individual strategy, or by choosing the highest or lowest values among them. The choice of one or another combination produces different aggregated strategies.

In Figure 7.13 there are two composite strategies shown. SumDiscount defines amount as the sum of the values returned by each component discount:

image

FIGURE 7.13 Example of the composite pattern.

Context SumDiscount::amount:Money

derive:

self.discount->sum(aDiscount|aDiscount.amount)

MajorDiscount selects the highest discount among each aggregated subclass:

Context MajorDiscount::amount:Money

derive:

self.discount->maxElement(aDiscount|aDiscount.amount)

The current version of OCL does not define a function maxElement to take the highest element of a collection, but it could be defined as a new operation. Another solution is defining the expression by using functions already provided by the language, such as

Context MajorDiscount::discountAmount:Money

derive:

self.discount->sortedBy(aDiscount|aDiscount.amount)->last()

The reader could find that it is not necessary to produce a sorted version of an entire list just to obtain its highest element. But remember that this is specification, not implementation; it is the final result that counts. Anyway, defining a new operation in this case, if possible, would be better because the meaning of the specification would be clearer.

7.8 Organizational hierarchy

Another common situation consists of the need to represent organizational hierarchies. It is commonplace, for example, to represent the administrative organization of a company as a composition hierarchy as shown in Figure 7.14.

image

FIGURE 7.14 Straight representation of a company’s organizational structure by using classes for the different levels of composition.

However, hierarchies like this usually do not behave well. First of all, this kind of organization is not followed by every company: other companies may have different levels or different compositions among them. Second, the company structure may change over time. New divisions or offices may be created, some may be joined, and others split. Last, but not least, different views of an organization may exist at the same time; for example, the finance department may have a different view of the same organization.

How to deal with all that complexity in a conceptual model? By using the organizational hierarchy pattern.

The solution consists of not considering the different levels of an organization as concepts, but as instances of a single concept, as shown in Figure 7.15.

image

FIGURE 7.15 Application of the organizational hierarchy pattern.

In this way, flexibility is gained related to dealing simultaneously with organizational structures from different companies. Also, eventual changes in the organizational structure, such as adding more levels, or changing dependencies, may be easier to accomplish.

This pattern has some variations that may be used if concurrent hierarchies, superseding structures, or equivalent structures should also be represented. Those variations are discussed in the following sections.

7.9 Object joining

One of the team’s assumptions that usually fails is that users will do everything right. That is not always the case. User failure (and sometimes sabotage) is still frequent, despite the efforts to build failsafe interfaces. A user could, for example, register a new publisher in the system, and later discover that it was already registered. Entering an incorrect code or the impossibility of having a unique identification for a real world object may cause this situation. The result is that two objects are registered in the system, both representing the same publisher, and each of them may possibly have its own set of books associated, some repeated, some not.

The solution when this kind of error happens is to join the objects, usually copying one over the other. This operation can be performed directly within the database, but in some situations it may be so common that it would be necessary that the system be prepared to allow this possibility as a user feature.

Besides, it is not always an error that causes the need for a join. In some situations, there are objects considered equivalent by one group of people and not by another, and both situations must be represented simultaneously. In other cases, as in multiple view organizational hierarchies, it may be necessary to indicate that two structure elements in different views are equivalent, or that one succeeds another in time.

The following subsections present the main strategies to deal with these kinds of situations.

7.9.1 Copy and replace

The first strategy that comes to mind when it is necessary to join two objects consists of copying the data of one object over the second (copy and replace). The copy/replace command could be defined by a contract (see Chapter 8), and the team should define, for each attribute and each association, what must happen during the copy/replace command. Rules should be defined to decide if an attribute is going to be copied over the other, or if their values are to be added, or if the newest or highest of them must prevail, etc. Regarding the associations, the team must decide what happens: if one replaces the other, or if their properties are added, and so on.

The registry of the date of the last inclusion or change of a concept may be useful for deciding which attribute must be kept in the case of a conflict. For example, if a registered customer makes a new record, and it is going to be resolved, then the most recent address must be kept, not the oldest one. On the other hand, all of her orders must be added to the resulting instance.

After performing the copy/replace command the instance that was copied must be destroyed, and any references to it must be redirected to the instance that received the data.

7.9.2 Superseding

Superseding is a technique that can be used when the original object must be kept and not destroyed. Superseding is applicable, for example, in the case of organizational structures that are succeeded in time. Suppose that the departments of marketing and sales are joined into a single department of customer contact; the original departments must be marked as no longer active, and a new department must be added as their successor. The superseding strategy may be implemented by a reflexive association, as shown in Figure 7.16.

image

FIGURE 7.16 An example of the superseding strategy.

The derived attribute active indicates if the structure is active or if it has been superseded. It is true if the set of superseders is empty and false otherwise.

To maintain the original organizational structure even if it is not active anymore may be important for registry purposes. Someday someone could need to know how much a given department that does not exist anymore spent on toothpicks.

It would be useful to add an association class to the superseded/supersede association; its attributes could indicate, for example, the date when the superseding event taken place, or the context or view in which it must be considered. If that kind of information is necessary, the team should consider applying one of the temporal patterns (Section 7.12).

7.9.3 Essence/Appearance

Another situation that can still often happen is the existence of objects that are considered equivalent but must be kept as separated objects. This is not a registry error and it is not an object that supersedes another: this is object equivalence.

Object equivalence may be modeled by the use of an essence object that is associated to a set of equivalent objects. As opposed to copy/replace, the original objects are kept, and as opposed to superseding there is no active or superseded object: all associated objects are equivalent. Figure 7.17 shows an example of a class (Book) that accepts that its members share a common essence.

image

FIGURE 7.17 Essence/Appearance technique.

In the example, we consider that the same book in essence, especially a classic work, may be published by different publishers. Each publication is distinct, with a different ISBN and number of pages. Even the title and author’s name may vary (proper names may be translated or abbreviated sometimes). But the essence of the text is the same. Some users may be interested in ordering Plato’s The Republic from a specific publisher, and others may be interested in the book independently of the edition or publisher. Thus, the users interested in a specific publication are looking for the appearance (a given instance of Book), and the users interested in the text are looking for the book in essence – the publisher does not matter. The second group of users, when viewing any edition of The Republic must also be allowed to view other instances of Book linked to the same EssenceBook if they exist. In other words, if you look for book aBook and if aBook.essenceBook->notEmpty(), then you will see aBook.essenceBook.book, which is a set of books with the same essence.

Not every Book must be linked to an EssenceBook, only those that participate in an equivalence class. Also, there must be at least two equivalent books to create an EssenceBook. This is why the association role from EssenceBook to Book is marked with 2..*.

Objects are considered equivalent if they are linked to the same essence object. The essence object exists only to establish that equivalence; usually it will not have any other properties. Otherwise, maybe the specification class pattern could be used instead.

7.9.4 Undoing a join

Just when you think that the real world cannot be more complex, it becomes so. Therefore, if there is a possibility of joining objects, it is also possible that joined objects must be separated again. Once again, these commands may be performed directly in the database, or through well-planned operations available at the system’s user interface.

To allow the joins of the copy/replace technique to be undone, a backup of the original objects must be kept, because the technique destroys one of the objects and disfigures the other. The superseding technique allows the join to be undone just by removing the association. In the case of essence/appearance, it is necessary to remove the association (and the essence object if less than two links to it remain).

However, in every case it is important to decide how to deal with eventual changes within the object that occurred while it was joined with the others.

A way to implement the possibility of undoing joins and any other operation is to use a variation of the temporal pattern (Section 7.12.3), which keeps track of former values of objects attributes and associations.

7.10 Account/Transaction

The account/transaction pattern is closely related to business, but it has wide applicability. It was mentioned before that books may be cataloged, ordered, received, delivered, discarded, etc. Such movements, as well as the financial transactions involved with them, give origin to concepts such as Order, Delivery, Discard, Return, etc., each one with its own attributes and associations.

However, it is possible to identify a common core for all these concepts and many more, which is constituted by a single and powerful pattern.

An Account is a concept that bears quantities of something (such as items, products, or money). An Account has a balance that usually consists of the sum of every deposit or withdrawal.

On the other hand, deposits and withdrawals are usually just entries of goods or money from an account to another. Thus, a Transaction consists of two entries: a deposit in one account and a withdrawal of the same value in another account. Figure 7.18 illustrates those classes.

image

FIGURE 7.18 Classes for the account/transaction pattern.

A consistent transaction with two entries such as the one in Figure 7.18 needs two entries with the same absolute value but opposite signs. In other words, if a transaction takes 5 dollars from an account, it must necessarily deposit 5 dollars into another account; the withdrawal has a negative sign and the deposit a positive sign. Thus, the Transaction class needs the following invariant:

Context Transaction

inv:

entry->sum(value)=0

This means that for each instance of Transaction the sum of the value of the two associated entries must be zero.

The derived attribute balance of class Account is defined as the sum of every entry linked to that specific Account:

Context Account::balance

derive:

entry->sum(value)

Many situations related to the bookstore example could be modeled from a set of instances of class Account, such as:

• For each publisher there is an instance of Account from which books are “withdrawn,” that is, it is an entry account, and its balance becomes more negative as books are ordered from the supplier.

• There is an account for orders expected, which contains the books that were ordered from the supplier but that had not yet arrived.

• There is an account for stock containing the available books.

• There is an account for sold books containing books sold but not yet delivered.

• There is an account for delivered books containing books delivered but not yet confirmed by the customer.

• There is an account for confirmed delivery containing books delivered and confirmed by the customer. Its balance represents the whole set of books that the customer has bought from the bookstore.

In parallel to book transactions there are concomitant money transactions. There are receivable accounts, payable accounts, received accounts, paid accounts, investment, debts, values saved for paying taxes, etc.

Thus, many transactions of the bookstore example could be modeled as an instance of Transaction. For example:

• An order is a transaction that takes books from a publisher account, and deposits them into an orders expected account.

• The arrival of the books is a transaction that takes from the orders expected account and adds to the stock account.

• A sale is a transaction that takes from the stock account and adds to the sold books account.

• A delivery is a transaction that takes from the sold books account and adds to the delivered books account.

• A return is a transaction that takes from the delivered books account and adds to the stock account (if the books are in good shape).

• A confirmation of delivery is a transaction that takes from the delivery account and adds to the confirmed delivery exit account.

As an example, imagine a company that just started, where five instances3 of the Account class were created: supplier, pendingOrders, stock, sold, and delivered. Figure 7.19 shows the initial state for those objects, when all accounts have a zero balance.

image

FIGURE 7.19 Initial state of an accounting system for products.

Figure 7.20 shows the resulting set of objects when a purchase order (instance of Transaction) is created. The order was issued for 50 books.

image

FIGURE 7.20 State of the accounts after a purchase order of 50 books is issued.

Figure 7.21 shows the state of the accounts if only 40 of the 50 ordered books arrive.

image

FIGURE 7.21 State of the accounts after 40 books arrive at the bookstore.

Figure 7.22 shows the state of the accounts after 25 books are sold.

image

FIGURE 7.22 State of the accounts after 25 books are sold.

And finally, Figure 7.23 shows how the accounts look like after the 25 books sold are delivered.

image

FIGURE 7.23 State of the accounts after the delivery of 25 books.

New transactions and accounts may be created dynamically (i.e., without having to recompile the system). For example, a new transaction may be created for returning books, which moves books from the delivered account back to the stock account. Also, a new transaction for discarding books could be created moving books from the stock or sold accounts to a new account for discarded books. Parallel financial transactions could be created as well by using Account and Transaction.

This pattern is very interesting as an example of how a simple powerful idea can deal with so many different situations. It has many variations and sophistications. For example, multi-legged transactions may be created if necessary, for moving from more than one account to a single account, or from a single account to more than one account, or even from more than one account to more than one account at the same time. To accomplish this variation, the multiplicity role from Transaction to Entry in Figure 7.18 should be changed to 2..*.

Another important variation is the use of memo entries. For example, when money is received, taxes must be paid, but not necessarily immediately. Thus, when a transaction moves money from a customer account to the bookstore balance account, a quantity of money equivalent to the taxes due could be registered into a tax memo account. When it is time to pay taxes, the balance of the memo account registers the amount to be paid.

Memo accounts and normal accounts could be distinguished. However, as they share the same definition, they were kept as instances of a single Account class in Figure 7.24. On the other hand, memo entries are distinguished. A transaction must have at least two normal entries, but any number of memo entries (zero in most cases). Furthermore, only normal entries are subjected to the invariant of the Transactionclass. Memo entries are ignored because they do not really move goods or money.

image

FIGURE 7.24 Evolution of Account/Transaction with memo entries.

Despite normal and memo accounts both having the same structure and behavior, it would be wise to clearly differentiate both kinds in order to avoid the system linking memo entries into normal accounts and vice versa.

7.11 Range

When an object has a pair of attributes indicating the beginning and end of some phenomena, such as initial and final date, instead of representing it as two separated attributes, it is advisable to declare a single attribute type such as Range (or Interval), as shown in Figure 7.25.

image

FIGURE 7.25 A generic primitive type Range.

The primitive Range is parameterized, meaning that when an instance is created it must indicate the Type to be used to define its attributes. For example, a range over Date could be referred to as Range[Date], and a range over a quantity defined as Distance could be referred to as Range[Distance].

There are two reasons for using a range instead of two attributes:

• If the lower and upper bounds are separated attributes in a conceptual class, they would have a strong relation between them. This goes against the high cohesion principle explained in Section 7.2.

• Operations that are specific to ranges (for example, checking if a value is inside a range) will be needed. If there is a specific Range primitive type, these operations may be implemented once just in the primitive. If two attributes are used instead, each class where they are declared should implement its own version of the operations on ranges.

It is much more reasonable to implement operations just once in a high-cohesion class, than implementing them many times in low-cohesion classes.

As seen in Figure 7.25, both attributes of the Range class are optional, which means that ranges may be undefined in one or both ends. For example, a range that starts at January 1st, 2013 and has no upper bound could be defined with lowerBound=“01/01/2013” and upperBound=null.

Evolutions of this pattern could take into account the mathematical properties of intervals. For example, there are open and closed intervals: [0..10] is an interval that includes 0 and 10, while (0..10) is an interval that excludes 0 and 10. Furthermore, (0..10] excludes 0 and includes 10 and [0..10) includes 0 and excludes 10. This evolution could be implemented simply by adding two Boolean attributes to the class: lowerBoundOpen and upperBoundOpen.

7.12 Temporal patterns

Frequently the analyst faces the need to deal with time. For example, it may be necessary to represent the relations between people and their jobs. But, if the association between people and jobs represents only the present time, then the information of former jobs will be lost when someone quits the job. This section discusses some patterns that deal with that important notion.4

7.12.1 Effectivity

When an object is valid for some time (for example, a temperature measure, which has a time it was taken and a time it is considered valid), then the effectivity pattern may be used. It consists of declaring an effective attribute in the class, which is typed with a range, as seen in Figure 7.26.

image

FIGURE 7.26 Use of the effectivity pattern.

7.12.2 History

If historical information has to be stored about the past states of an association, then the history association pattern may be used. This kind of association may be identified by the stereotype enthistoryent, as seen in Figure 7.27.

image

FIGURE 7.27 An example of the history association pattern.

The stereotype of the association of Figure 7.27 means that a person must have just zero or one job currently, but she may have had other jobs in the past. These past jobs may be recovered as items in a list. In other words, it is possible to recover the last job, the job before the last, and so on.

When it comes to design, each class with an association with a navigable role implements a method for accessing the elements linked. In the example of Figure 7.27, the design class for Person would have a getter method getJob() that returns the company of the current job of a person or the empty set if there is no current job. If the association is stereotyped with enthistoryent, in addition to that standard method there would exist another method with a parameter, getJob(index:Natural), where getJob(1) returns the current job, getJob(2) returns the last job, getJob(3) returns the job before the last, and so on. If there is no job for the index given, then the method returns the empty set.

In practice, this pattern can be modeled by two associations, as shown in Figure 7.28. The stereotype is, then, a way to abbreviate that complex structure, replacing it with a simpler one.

image

FIGURE 7.28 Concrete model for the <<history>> stereotype.

7.12.3 Temporal

The history association pattern is not capable of answering what the job of a person was at a given date, or if a person was unemployed in the past. In order to represent that kind of information, an evolution of that pattern may be used: an association that in addition to sequential memory also has time memory, as shown in Figure 7.29.

image

FIGURE 7.29 A temporal association.

A temporal association has, in addition to the aforementioned two get methods, a method for obtaining the value of the association at a given time. In the example, that method could be getJob(date:Date). Thus, the Person class presented in Figure 7.29 would have at least three getters for the temporal job role:

getJob(): Returns the current job of a person or the empty set if the person is currently unemployed.

getJob(index:Natural): Returns the current or former job of a person depending on the index argument, or the empty set if there is no job at that index.

getJob(date:Date): Returns the job a person had at a given date or the empty set if the person was unemployed at that date.

If it was required to know the job of a person at a given time of the day, then instead of the Date the primitive type Time could be used, which refers to year, month, day, hour, minute, and second. For some applications, milliseconds, microseconds, and so on could be used instead.

Figure 7.30 presents a model for implementing the enttemporalent stereotype.

image

FIGURE 7.30 A possible implementation of the enttemporalent stereotype.

The Job class does not have a pair of attributes such as initialDate and endDate, but a single attribute interval with type Range[Date], representing an interval between two dates.

Not only can associations be temporal, but attributes as well. If an attribute must keep a record of its former values, then the enttemporalent stereotype can be used to keep track of them.

For example, some countries allow people to change their names after marriage, divorce, or decision of a judge. In Figure 7.31, the ID and birthdate of a person are not supposed to change or, if they change, old values are not kept. However, former names must be kept.

image

FIGURE 7.31 Temporal pattern applied to an attribute.

Another variation of this pattern is to define a temporal class. In the case of a class stereotyped with enttemporalent, all of its attributes and association roles are temporal. The class also could provide a method for producing a version of its instances at a given instant of time; for example, if x is an instance of a temporal class, one could obtain a previous state of x by using a function such as x.atInstant(aTimeInstant). Each time one of the attribute’s values or links is changed, a new version of the object is created.

7.12.4 Bitemporal

Not only in science fiction, but also in many current information system applications, time may be considered two-dimensional. There is a dimension of time in which events occur and another dimension where we acknowledge that an event occurred.

Look back at Figure 7.29, and imagine that Mary is currently working for Company X. Today (August 11th, 2013) we discover that in fact Mary changed jobs on March 1st, 2013. We update Mary’s job, and the date she changed jobs is recorded as March 1st, 2013. However, we only knew about the change on August 11th, 2013. Any actions the company could have performed between March 1st and August 11th were taken with the knowledge that Mary still worked at Company X. Sometimes, especially for accountability or legal procedures, knowing when a record that was made is very important. The bitemporal pattern is an elegant way to keep track not only of the line of events but also the line of our knowledge about events (Figure 7.32).

image

FIGURE 7.32 Bitemporal pattern.

Now a new getter may be introduced for the role job: getJob(date,knowledgeDate:Date). The first argument is the instant of time we are interested in and the second argument is the instance of time of our knowledge about it. For example, if we want to answer the question “On June 15th, 2013, where did we believe that Mary worked on June 1st, 2013?” we could use getJob(“06/01/2013”,“06/15/2013”) to get the answer, which would be “Company X.”

If we want to know where Mary worked on June 1st, 2013 based on our current knowledge, we could use the getter mentioned for the temporal pattern (Section 7.12.3): getJob(“06/01/2013”), which is short for getJob(“06/01/2013”, today).

Figure 7.33 presents a possible model for implementing that stereotype.

image

FIGURE 7.33 A model that implements the bitemporal pattern.

Now there are two date ranges in the class Job, one for registering the time the job was effective and the other to represent the range of time when the registered time was recorded.

Each time our knowledge about the job of a person is changed a new instance of Job is created. The interval attribute establishes when the job was effective, and the record attribute establishes the point from when we believed that. The upper bound of the record range will be kept undefined while it represents our current knowledge, that is, while that information was not overridden by newer (eventually contradictory) information.

The bitemporal pattern can also be applied to attributes or entire classes, just like the temporal pattern.

7.13 Discussion

A good conceptual model produces an organized structure that is suitable to generate an already normalized database. It incorporates structural rules that prevent information from being represented in an inconsistent way; it also simplifies the code that will be generated because it minimizes and organizes consistency checks, and several verifications that are guaranteed by the model itself do not need to be performed by the code.

The use of suitable design patterns in situations where they are necessary simplifies the conceptual model and gives it more flexibility and quality. It is, therefore, a powerful tool. Many other patterns exist, and analysts may create their own new patterns. It just has to be kept in mind that the creation of a pattern is justified only when its benefits pay for the effort expended in creating it.

7.14 The process so far

Image

Image

7.15 Questions

1. In Figure 7.18, the Transaction class needs another invariant. A transaction cannot be linked to entries that are linked to the same account. Elaborate that invariant in OCL.

2. Apply the account/transaction pattern to the parallel financial transactions of the example shown in Figures 7.19 to 7.23. As the bills may be paid forward it may be necessary to use memo entries. Define the set of instances of Account and Transaction that are necessary and describe them.

3. Use Google to find examples of conceptual models including classes with attributes. Analyze those classes and check if they present cohesion problems. Propose alternative solutions to them if necessary.

4. A system for registering genealogical trees allows different users to check if they have coincident people in their own trees. If this is the case, the shared person is marked in each tree as equivalent to the other. Thus, two cousins, for example, who have the same grandfather, may have a coincident registry in their trees. Which of the three strategies of object joining is best for dealing with this case? Why? Elaborate a model for representing people in the genealogical trees using that pattern.

5. Complete the diagram of Figure 7.12 with information about orders, items, books, and customers. Then use OCL to define the derived attribute amount for both subclasses of Discount.


1Usually, high cohesion is mentioned in conjunction with low coupling. However, as coupling problems appear to emerge when object collaborations are designed, discussion on low coupling is left for Section 9.6.

2Composite is the classic name for this pattern. However, if basic elements may be shared by different composite elements, it is the shared aggregation association that is used instead of the composite aggregation. This happens when strategies are composed.

3The example deals with only five instances for simplification. Many others could be included.

4Martin Fowler presents a nice summary on many temporal patterns at http://martinfowler.com/eaaDev/timeNarrative.html.