Domain Tier Design - 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 9

Domain Tier Design

This chapter shows how to design interaction among objects (dynamic modeling) in order to implement system operation contracts using UML interaction diagrams (communication and sequence). Initially, object responsibility and visibility are explained in order to show that objects may only communicate to each other to perform their responsibilities if they have a visibility line to follow. Next, the chapter discusses how to build dynamic models in which objects change messages in order to perform the actions that are necessary to meet the postconditions of a contract, and how the preconditions and exceptions affect it. Design patterns for responsibility distribution among objects are presented in order to improve the design of object-oriented code with delegation and low coupling. Finally, the chapter explains how to use the information learned during dynamic modeling to improve the design class diagram.

Keywords

Object responsibility; object visibility; delegation; low coupling; communication diagram; interaction diagram; design class diagram

Key Topics in this Chapter

• Responsibility

• Visibility

• Low coupling

• Delegation

• Dynamic modeling

• Design class diagram

9.1 Introduction to domain tier design

Software design in general aims to produce a solution to a problem that has already been sufficiently clarified by analysis. The static aspects of the problem are represented in the conceptual model and the functional aspects are represented in the expanded use cases, system sequence diagrams, and system operation contracts. Now it is time to design a software (and possibly hardware) solution to implement the logical and technological aspects of the system. In this sense design is a solution for a problem modeled by analysis.

During iterations, after expanding use cases, refining the conceptual model, and writing contracts, the team may conduct design activities, which can be divided into two groups:

Logical design (this chapter), which includes the aspects of the problem that are related to the business logic. Usually the logical design is represented in the design class diagram (DCD), which evolves from the conceptual model, and by interaction diagrams, which show how objects exchange messages in order to perform system operations and achieve the postconditions of the contracts.

Technological design, which includes all aspects of the problem that are inherent to the technology used: interface, data storage, security, communication, fault tolerance, etc. Activities related to technological design are addressed later:

Interface tier design (Chapter 12), which consists of designing the user interface and preferably keeping it decoupled from the domain tier.

Persistence tier design (Chapter 13), which consists of the definition of a persistency mechanism to allow data load and save, which can be performed automatically, preventing the designer from being (over) concerned about those aspects.

Logical design is also known as domain tier design. The domain tier corresponds to the set of classes that perform all data transformation and queries. Other tiers implement technological aspects of the system: persistency, interface, communication, security, etc. They are usually derived from and dependent on the domain tier and their utility is to connect the pure logic of the domain to the physical computer aspects (communication networks, human interfaces, storage devices, etc.).

The design of the domain tier consists basically of two activities that can be performed iteratively:

Dynamic modeling, which consists of building execution models for the system operation contracts. In object-oriented systems, such models are usually represented by interaction diagrams such as communication and sequence diagrams, or even as pure text algorithms.

DCD building, which consists basically of adding to the conceptual model some information that was not possible or desirable to obtain earlier, such as, for example, the association’s navigation direction, and the methods to be implemented in each class. Those aspects can be effectively included in the software design if dynamic modeling is done. Additionally, the structure of design classes may be a little different from the conceptual model as new classes or a different class organization may be necessary to perform some functions that will be required at this point.

The logical design can be performed systematically if the team is in possession of two artifacts:

• The evolving design class diagram.

• The functional model represented by system operation contracts.

The logical design activities consist of building interaction diagrams for system operations found in the system sequence diagrams (especially system commands, as explained later), taking into account the DCD and the respective system operation contract.

Dynamic modeling, as mentioned before, can make use of communication or sequence diagrams, or even algorithms. Each of those forms has advantages and disadvantages:

Algorithms, for programmers, are easier to write, but it may be difficult to realize clearly the connections among objects in simple text. Thus, the choice for algorithms to use object-oriented dynamic modeling may raise the risk of high coupling among classes, generating bad design.

Communication diagrams are better than algorithms for visualization and responsibility distribution purposes, and they are better than sequence diagrams for providing an explicit view of the visibility lines between objects. However, these diagrams may be difficult to organize and in the case of complex collaborations they may become harder to read than sequence diagrams.

Sequence diagrams are usually easier to read than communication diagrams, but they do not show explicitly the visibility lines between objects. If the designer is not vigilant, invalid or impossible communications may be included in the diagram.

This chapter uses communication diagrams for dynamic modeling in order to ease the visualization of the visibility lines between objects. In some cases, the equivalent sequence diagrams are shown for comparison.

The class diagram examples in this chapter assume that it is design time. Thus, associations may be directed and classes may have methods and private attributes.

The chapter initially discusses object responsibility, which is a key concept in understanding how to distribute methods among objects. Knowing which object should implement which responsibility allows design classes to be highly cohesive.

Another key concept that is presented in detail is visibility, which establishes that communication among objects must occur only when visibility lines exist. If the quantity of those lines is minimized, low coupling (Meyer, 1988) is achieved in the design.

Finally, the chapter explains how to transform contracts into dynamic models by using responsibility distribution and visibility, as well as other design patterns, to produce the best design possible.

9.2 Object responsibility distribution

Responsibility distribution among objects has to do with the following question: What methods must be implemented in which classes?

Many designers find it difficult to build an elegant solution to that problem when they simply try to add methods to a class diagram. The use of interaction diagrams and design patterns may, however, provide a more efficient and effective way to discover the most suitable place to implement each method.

The DCD is built from the conceptual model, which provides the basic set of classes, attributes, and associations that are needed. Then, it is necessary to develop interaction diagrams for each system operation contract. Those diagrams show objects exchanging messages in order to achieve the postconditions of the respective contracts. There are techniques that help in building those diagrams in an elegant way. The use of diagrams is better than algorithms, especially for novice designers, because with algorithms, the designer may fall for the temptation of concentrating all responsibilities in a single class, while the diagrams allow better visualization of the distribution of responsibilities among the objects. An example of that situation is presented later in this chapter.

When a team builds object-oriented code without an adequate method for dynamic modeling (that is, simply making a class diagram and adding ad-hoc methods to it) there is a risk that the responsibilities may be poorly distributed among the classes, and the final result may be as unstructured as the old spaghetti code. Usually, object-oriented systems are well organized at the class level, but unhappily the code written inside methods is often poorly designed.

Thus, for a system to be elegant, responsibilities have to be well distributed. If a systematic method is not used, responsibilities can become concentrated in the controller class, or in classes that represent business actors, such as Customer or Clerk. In the end, classes such as Book, Order, and Payment would have no relevant method besides their own attribute getters and setters.

When one or two classes do all the work, and others are passive, there is no object-oriented code worthy of that name, but a concentrating structure. It would be preferable perhaps to make a good structured design than a bad object-oriented design.

On the other hand, designers, sometimes, make the mistake of believing that an object-oriented system is a simulation of the real world. But that is not usually true. An object-oriented model represents the information about the real world, and not the things themselves. The difference is subtle: methods do not correspond to real-world action, but to the internal information processing of the system. That is why concepts such as Book that are passive in the real world may perform commands in object-oriented systems.

Thus, designing object-oriented software must be understood as a precise method, guided by learned patterns, and not simply as the act of creating classes and associating ad hoc methods to them.

Before explaining how to distribute responsibilities among objects, a taxonomy of those responsibilities will be defined. Basically, there are two high-level groups of responsibilities:

• The responsibility of knowing, which corresponds to the queries on objects.

• The responsibility of updating, which corresponds to the commands performed by objects.

Both high-level groups are divided into three subgroups:

• Things that the object knows or updates within itself (its attributes).

• Things that the object knows or updates in its neighborhood (its links).

• Other things that the object knows or updates that are not classified in the previous two groups. Normally these things correspond to derived knowledge (derived attributes and associations, and other queries in general) and coordinated activities (delegate methods).

In the case of the responsibilities of knowing, the three subgroups could be characterized as follows:

Things that the object knows about itself: This is equivalent to being able to access the attributes of an object. Such responsibilities are incorporated to the classes by basic query methods named with the prefix getfollowed by the name of the attribute.1 For example, if the class Person has an attribute birthDate, then the method getBirthDate has the responsibility of knowing the value of that attribute.

Things that the object knows about its neighborhood: This is equivalent to being able to access directly linked objects. This responsibility is incorporated in the methods that obtain the set of objects linked through a given role. Such methods are usually prefixed with get and followed by the role name (or the name of the class in the absence of the role name). For example, if Car is associated to Person and the role name for Person is driver, then the method getDriver returns the set of people that are drivers of a given car.

Derived things that the object knows: This is equivalent to information that is composed or calculated from other information. This responsibility may be associated to derived attributes and derived associations, in which case the methods are also prefixed by get followed by the name of the derived attribute or association. For example, if totalValue is the name of a derived attribute of class Order, then getTotalValue is a method that returns that derived attribute. Derived knowledge also includes other queries about an object that cannot be represented as derived attributes or associations, such as, for instance, queries with parameters; in that case, names may vary.

In the case of responsibilities related to updating information, the three subgroups could be characterized as:

Things that the object does over itself: This corresponds to the basic command that updates a single attribute, which is prefixed with set,2 followed by the attribute name. For example, if the class Person has an attribute weight then setWeight is a method that incorporates the responsibility of changing that attribute.

Things that the object does over its neighborhood: This corresponds to the basic commands that add and remove links between objects. These commands are identified respectively by the prefixes add and remove,3followed by the name of the association role. For example, if the class Customer has an association with Reservation, the methods addReservation and removeReservation incorporate that responsibility.

Things that the object coordinates: This corresponds to commands that perform multiple tasks, possibly in different objects. These commands are known as delegated methods and they are crucial for class design because the methods of the other groups are just basic commands with previously known behavior; but delegate methods must be designed. For example, if all the books in a given order must be marked as delivered, the coordination of the marking activities must be made by the instance of Order itself, because it is the object with the most immediate access to the objects that participate at the operation.

Table 9.1 summarizes the six kinds of responsibilities and the methods typically associated to each kind.

Table 9.1

Object Responsibilities

Image

There is no standard name for delegated methods and general queries, which are named depending on the operation they perform. Usually, these names must not include the name of the class where they are implemented. For example, if Livir implements a system command finishOrder, and that command delegates that responsibility to the class Order, then the name of the method implemented in class Order must be simply finish, because it would be probably invoked as anOrder.finish().

9.3 Visibility

For two objects to be able to exchange messages to perform their responsibilities, it is necessary that some kind of visibility exists between them. There are four basic forms of visibility between objects:

By association: If there is a link between two objects defined by an association between their classes.

By parameter: If an object, when performing a method, receives another object as parameter.

Locally declared: If an object that is performing a method receives another object as a query return.

Global: If an object is visible by any other object.

None of the visibility forms are symmetric. That is, if x has visibility to y that does not mean that y necessarily has visibility to x. Visibility by association may be symmetric if the association is bidirectional, but this is not always the case.

9.3.1 Visibility by association

There is visibility by association between two objects only when there is an association between their respective classes. The kind of visibility allowed depends on the role multiplicity and other features, such as the association being qualified, ordered, and so on.

An association role may be considered a collection of instances, especially if its upper bound is greater than 1. But, in the case of multiplicity 1 or 0..1, it is possible to interpret the role as a set or as an individual object. OCL, in these cases, considers the role both as an object and a collection. Regarding multiplicity, then, the following kinds of visibility may be identified:

• If the multiplicity is 1, then there is straight visibility to one instance and to the set that contains one instance.

• Any other multiplicity allows only visibility to a set of instances.4

The DCD is not the only specification that determines the multiplicity of a role. It is complemented by the preconditions of the contracts, which can constrain multiplicity bounds even more, as seen in the following sections.

9.3.1.1 Visibility to a single object

Figure 9.1 shows a Payment class that has a unidirectional association to Order with multiplicity 1. In this case, any instance of Payment has straight visibility to an instance of Order.5

image

FIGURE 9.1 Association role with multiplicity 1.

In this way, when an instance of Payment is represented in an interaction diagram, as shown in the communication diagram in Figure 9.2, it can send messages directly to the instance of Order that is linked to it.

image

FIGURE 9.2 Communication diagram where a payment has visibility to a single order.

9.3.1.2 Visibility to multiple objects

Multiplicity values different from 1, such as *, 1..*, 0..1, or 5, provide visibility to a collection6 of objects, not just to a single instance. From now on, all those cases will be addressed as * multiplicity, or association to many.

Figure 9.3 shows class Order with a unidirectional association with multiplicity * to class Item.

image

FIGURE 9.3 Association role with multiplicity *.

In this case, an instance of Order can send a message to a set of instances of Item. It is possible to send messages to the set structure itself,7 as shown in Figure 9.4. In the example, the message verifies if the set is empty.

image

FIGURE 9.4 Communication diagram with a message being sent to a set structure.

But it is possible to send a message iteratively to each element of the set, as shown in Figure 9.5. In the example, the message requests the subtotal value of each item.

image

FIGURE 9.5 Communication diagram with a message being sent to each element of the set.

Thus, messages, in communication diagrams, may be addressed to the collection structure or to the collection elements individually. When a message is sent to the structure, the type of the object that receives the message should be a collection, such as Set<Item> in Figure 9.4. The fact that the message is iterative and sent to all elements in the set is represented by the “*” that precedes the message. The condition [for all] indicates that all elements of the set receive the message.

A filter could be used here if the message was intended just for some selected elements. For example, if the message getSubtotal is intended only for items with quantity greater than 1 then it could be written as *[quantity>1]: anAmount:=getSubtotal().

9.3.1.3 Visibility by association with ordered roles

If the association role is ordered with the use of the {ordered} or {sequence} constraints, the visibility to the collection as a whole is kept, but additionally straight visibility to individual elements indexed by their position is allowed.

Figure 9.6 presents a class diagram with a unidirectional association with an ordered role.

image

FIGURE 9.6 Ordered association role.

In Figure 9.7 the message is sent to each element of the ordered collection, as in the case of Figure 9.5.

image

FIGURE 9.7 Communication diagram with a message being sent to an ordered set structure.

Part (A) of Figure 9.7 is the explicit way to represent an iterative message sent to the elements of an ordered set or sequence. Part (B) is a shorter acceptable way to represent the same thing. The form *[for all]: used for sets would be acceptable as well.

In Figure 9.8 only the third element of the ordered set receives the message, and visibility is not to multiple objects like before, but to a single object. This is indicated in the index used to represent the name of the object: orders[3]. The value between brackets may be a constant, as in the example, or a variable known by the Customer instance, or even a math expression that results in an integer.

image

FIGURE 9.8 Communication diagram with a message being sent to a specific element of an ordered set.

In Figure 9.9 only the last element of the ordered collection receives the message.

image

FIGURE 9.9 Communication diagram with a message being sent to the last element of an ordered set.

9.3.1.4 Visibility by association with qualifiers

If the association is qualified as a map (only one element for each value of the qualifier), there are at least two forms of straight visibility:

• Visibility to the set of elements as a whole (as if it were a regular set).

• Visibility to a single element if the object has a key to access the element in the association role.

Figure 9.10 presents a class diagram with a qualified association from Customer to CreditCard.

image

FIGURE 9.10 Class diagram with a qualified association defining a map.

Figure 9.11 shows that in this case, an instance of Customer has visibility to the set of instances of CreditCard that are linked to it.

image

FIGURE 9.11 Communication diagram with a message being sent to the structure of the mapped set.

Figure 9.12 shows a message being sent to each of the credit cards in the mapped set.

image

FIGURE 9.12 Communication diagram with a message being sent to each element of the mapped set.

Finally, Figure 9.13 shows that if the instance of Customer has a key value for the qualifier (aNumber in the example), then it has straight visibility to the element of the mapped set qualified by that key value.

image

FIGURE 9.13 Communication diagram showing an object with straight visibility to a qualified object in a mapped set.

On the other hand, if the qualified association represents a partition, as in Figure 9.14, a set of values is associated to each qualifier.

image

FIGURE 9.14 Class diagram with a qualified association defining a partition.

In this case, the whole set may be accessed (all the books in the example of Figure 9.15).

image

FIGURE 9.15 Communication diagram with a message being sent to each element of the partitioned set.

But the use of the qualifier allows for access to books belonging to a given subset (thriller books, in the example of Figure 9.16).

image

FIGURE 9.16 Iterative message to each element of a partition of a qualified set.

Both examples in Figure 9.15 and Figure 9.16 show messages that are sent to the set elements and not to the set structure.

9.3.1.5 Association visibility with association class

As explained in Section 6.6.2, an association class instance is created and deleted when links are added and removed from the participating classes’ instances. Thus, if Person has a unidirectional association to Company and that association has an association class Job, as shown in Figure 9.17, when a link between instances of Person and Company is added, an instance of Job is created; and every time a link from Personto Company is removed, the respective instance of Job is deleted. Therefore, an instance of Person has visibility to two sets8 with the same size: one with instances of Company, and another with instances of Job. An instance of Job has visibility only to a single instance of Company.

image

FIGURE 9.17 Association class.

If the association in Figure 9.17 were bidirectional, then an instance of Company would also have visibility to a set of Person and a set of Job. Additionally, any instance of Job would have visibility to one instance of Person and one instance of Company.

Figure 9.18 represents the visibility that an instance of Person has to the set of instances of Company. That corresponds to the default visibility obtained for multiplicity *.

image

FIGURE 9.18 A message being sent to the elements of the set linked by an association with an association class.

Figure 9.19 represents the visibility that an instance of Person has to a set of instances of Job, the association class.

image

FIGURE 9.19 A message being sent to the elements of the set of instances of the association class.

The association class also works as a mapping in a way that is very similar to the qualified association. The model in Figure 9.17 is equivalent to mapping a job for each company a person works for. Thus, if an instance of Person has visibility to a specific instance of Company, it may find its job at the company, as illustrated in Figure 9.20.

image

FIGURE 9.20 A message being sent to a specific element of the association class.

Also in Figure 9.20, we can see that aCompany must be an instance of Company and that the instance of Person must have some kind of visibility to it in order to use it to access the respective instance of Job.

Finally, Figure 9.21 shows the visibility that instances of the association class have to the instances of the participating classes: a visibility that is strictly one.

image

FIGURE 9.21 Visibility from the association class instance to one of the participating classes’ instances.

9.3.1.6 The influence of preconditions on visibility by association

When a precondition in a contract states a multiplicity that is more restrictive than the one allowed in the class diagram, it is the visibility established by the precondition that must be considered valid in the context of that operation.

For example, consider the class diagram of Figure 9.22, which defines that an order may or may not have a payment linked to it.

image

FIGURE 9.22 Association with optional role.

Consider now that a given operation contract has a precondition that states

pre:

anOrder.payment->notEmpty()

Then, in the context of that operation, and for the specific instance anOrder, a more restrictive visibility must be considered, as shown in Figure 9.23.

image

FIGURE 9.23 How the original visibility of Figure 9.22 must be considered for a specific instance if a contract has a more restrictive precondition.

9.3.2 Visibility by parameter

Visibility by parameter is obtained when an object A, performing a method, receives an object B as a parameter. In that case, the object A can communicate to B even if their classes are not associated.

Consider the example of Figure 9.24, where the Book class has no association to the Delivery class. Therefore, no instance of Book may send a message directly to an instance of Delivery or vice versa.

image

FIGURE 9.24 Reference class diagram.

Consider an instance of Order that is linked to an instance of Book and to an instance of Delivery as shown in Figure 9.25.

image

FIGURE 9.25 Initial situation where the instance of Delivery has no direct visibility to the instance of Book.

Now, consider that the instance of Order sends a message registerWeight to the instance of Delivery, passing a reference to the instance of Book as an argument, as shown in Figure 9.26. Then, the instance of Delivery acquires visibility by parameter to the instance of Book.

image

FIGURE 9.26 The instance of Delivery acquires visibility by parameter to aBook.

In the case of visibility by parameter, it should be assumed that the object that sends the argument originally has some kind of visibility to the object that was sent.

Good design practice demands care with visibility by parameter. This is not the same kind of coupling that is obtained with visibility by association. With visibility by association, the participating classes are already coupled by a semantic association, that is, they are linked anyway because the meaning of one is attached to the meaning of the other (for example, an order and its items). But in the case of visibility by parameter, any object may be passed as a parameter to another object, even if they have absolutely no relation between them.

Each time a class declares methods with parameters belonging to classes it is not associated to, a new (and possibly unnecessary) coupling may be created. High coupling means poor design. Thus, it is convenient to avoid that whenever possible.

The best use for visibility by parameter is to receive and resend objects until they reach the terminal (basic) operations. In practice, what happens is that certain objects will be passed to other objects in a chain until they reach some object that must be linked or unlinked to them, as in the example below.

Initially, consider the class diagram of Figure 9.27, with classes Livir (the façade-controller), Book, Customer, and Reservation.

image

FIGURE 9.27 Reference class diagram.

Figure 9.28 presents a collaboration where instances of these classes cooperate to perform the system command reserveBook, which adds a reservation for a given book to a given customer. The system command, implemented in the controller, is called by the interface tier, which passes as arguments the ISBN of the book and the ID of the customer. The controller, initially, gets visibility to the specific instance of Book (message labeled “1”) by sending to itself a query message on the role book: the message getBook(anIsbn). Then it gets visibility to the instance of Customer (message labeled “2”) in a similar way. The instances returned by the messages are assigned to the local variables aBook and aCustomer. Finally, the controller delegates to the instance of Customer the reservation of the book (message labeled “3”).

image

FIGURE 9.28 Creation of visibility by parameter.

The message labeled “3,” reserveBook(aBook), is sent from the controller to the customer whose ID is aCustomerId. The controller should not be responsible for creating the reservation because it has no association to Reservation, and thus it delegates the responsibility to aCustomer. At this point, aCustomer acquires visibility by parameter to aBook.

At this point, aCustomer can start performing its responsibilities. It creates a new instance of Reservation and links it to itself and to aBook. Figure 9.29 shows the constructor message Create being sent in order to create a reservation with its initialization parameters: aBook and aCustomer. This message is labeled with 3.1 because it is called inside the implementation of message 3, which was received by aCustomer.

image

FIGURE 9.29 A communication diagram where a new instance of Reservation is created.

As a last observation on the example, try to figure out what would happen if instead of passing the instance of Book to the customer, the controller had passed its ISBN code as received from the interface. When the instance of Reservation is going to add the book to its links it would have to obtain the instance, because it cannot just add the ISBN code. How could the instance of Reservation seek an instance of Bookby its ISBN if it has no access to the set of all books? It would have to send a message to the controller, asking for the book. This is not good quality design because the flow of messages in this case goes back to the controller for no reason. In the example shown in Figure 9.29, the controller obtains the book as soon as it gets the original message. There is no motive to delay it in order to reverse the flow of control later.

9.3.3 Locally declared visibility

Another form of visibility happens when an object sends a query to another and receives as return a third object, as shown in Figure 9.31. The collaboration is based on the class diagram of Figure 9.30.

image

FIGURE 9.30 Reference class diagram.

image

FIGURE 9.31 An example of the creation of local visibility.

In the example, an instance of Customer sends a message getPayment to an instance of Order. The customer and the order are initially linked, but the customer and the payment are not. However, after the getPayment method is performed, and an instance of Payment is returned to the customer, it acquires local visibility to the payment, because, at that point, the payment is assigned to a local variable (aPayment) of the method the customer is executing.

From this moment on, the instance of Customer acquires local visibility to the payment and may communicate directly with it.

However, a design pattern known as Don’t talk to strangers (Larman, 2004)9 does not recommend that an object send messages to objects that are only locally visible. According to that pattern the customer should not send messages to aPayment. As seen in Section 9.6, there is always a design option that avoids this kind of communication. The problem here again is high coupling: if the customer acquires local visibility to an instance of Payment, it acquires a link that is not semantically supported by the conceptual model. On the other hand, the price to pay for low coupling in this case is sometimes having to implement more methods, as seen later.

An acceptable use for local visibility occurs when a basic command that creates an object is invoked. When an object is created, the new instance is usually referenced by a local variable. That local visibility may be immediately transformed into visibility by association, as shown in Figure 9.32.

image

FIGURE 9.32 An example of new local visibility being transformed into visibility by association.

In the example, the first message is a basic command, a constructor that produces the instance of Payment. At that moment the instance of Order gets a new local visibility to it, through the local variable aPayment. The second message links that new instance to the order, which acquires visibility by association to it.

Both local and parameter visibilities are valid only in the scope of the method that originated them; just like parameters and local variables in programming languages, they are only valid inside the methods where they have been declared, and disappear after the methods go out of scope. But visibility by association is permanent, persisting until it is explicitly removed by a basic command that removes a link.

9.3.4 Global visibility

There is global visibility for an object when it is accessible by any object at any time. The singleton design pattern (Gamma, Helm, Johnson, & Vlissides, 1995) suggests that an instance may be globally visible if it is the only instance of a class. This makes sense, because if that class has only one instance, it is not necessary to create associations from other objects to it. Also, if that class has a single instance, it is not necessary to pass it as an argument to a function because the idea with parameters is that they may vary, and that does not happen if the class has a single instance. Global visibility is the choice in this case.

Examples of singletons are classes that represent services, such as CurrencyConverter, TimeCounter, OrderNumberGenerator, etc. There must be a single instance of service classes that can be accessed by any object at any time. An example is shown in Figure 9.33.

image

FIGURE 9.33 Global visibility.

If the singleton pattern is overused it may turn into an anti-pattern. Designers must not use singletons for entities of the conceptual model; for example, if the system is single user and a single shopping cart may exist at a time, it should not be declared a singleton because it is a conceptual entity, not a service class. A conceptual entity that is unique today may admit multiple instances in the future and declaring it a singleton would hinder the evolution of that class.

Also, a singleton, in principle, should not have direct references to entities, because that reduces its reusability potential and creates high coupling problems. Service classes must not be associated to entity classes; they are globally visible but grant access to no other instances.

9.4 Dynamic modeling based on postconditions

We saw that system command contracts present a set of postconditions that correspond to certain basic commands for creating and deleting instances, adding and removing links, and modifying attribute values. These contracts only indicate what is supposed to happen, but they do not show how messages are exchanged between objects in order to achieve those goals. UML interaction diagrams may be used to exactly design how these collaborations may happen. The following principles are recommended:

• The kind of visibility among objects depends fundamentally on the role features, such as multiplicity, ordering, and qualification, that are defined in the class diagram and sometimes further constrained by preconditions (Section 9.3).

• Each postcondition in the contract must be achieved in the interaction diagram by a message that calls a basic command in the object that holds the responsibility (Section 9.2).

• The control flow in a dynamic model begins at the instance of the façade controller that receives a message from the interface.

• When an object that is executing has no visibility to the object that must perform a basic operation, it should delegate the responsibility and the execution to another object that is closer to the one that holds the responsibility.

Santos (2007) presents a systematization of those principles by defining an automatic search-based production system capable of generating well-designed communication diagrams from a broad selection of contracts.

In addition to these principles, design patterns should be applied whenever possible to help the designer build methods that are effectively reusable and maintainable.

9.4.1 Instance creation

When a contract establishes that some instance was created, some other object must actually create it by sending a basic create message in the respective communication diagram. The creator design pattern (Larman, 2004) states that the choice should be preferably:

• An instance of a class that has a composite or shared aggregation to the class of the object to be created.

• An instance of a class that has a one to many association to the class of the object to be created.

• An object that has the initialization data for the object to be created, and that is preferably associated to it.

Each rule applies only if the previous one does not apply.

To serve as a reference for the following examples, let’s go back to the operations of the use case 01: Order books, presented in Chapter 8. The reference conceptual model of Figure 8.1 is repeated here as Figure 9.34.

image

FIGURE 9.34 Reference class diagram.

The first system command of the system sequence diagram of use case 01 is add2Cart(aCartId:CartId, anIsbn:ISBN, aQuantity:Natural). Its contract establishes that if the book is not in the cart already, a new item is created and linked to the book and cart. The item quantity is set to the value received as an argument, and unitPrice is set to the price of the book. Otherwise, if the book is already in the order, the new quantity is added to the current one. The contract again is the following:

Context Livir::add2Cart(aCartId:CartId; anIsbn:ISBN; aQuantity:Natural)

def:aCart=cart[aCartId]

def:aBook=book[anIsbn]

def:anItem=aCart.item->select(book.isbn=anIsbn)

pre:

aCart->notEmpty() and

aBook->notEmpty()

post:

if anItem->isEmpty() then

newItem.isNewInstanceOf(Item) and

aCart^addItem(newItem) and

newItem^setQuantity(aQuantity) and

newItem^addBook(aBook) and

newItem^setUnitPrice(aBook.price)

else

anItem^setQuantity(anItem.quantity@pre + aQuantity)

endif

Among other postconditions, an instance of Item must be created if the book is not yet in the cart. The question at this moment is: If the control flow starts at the façade controller (Livir), which class has to create an instance of Item? The options are:

• The façade controller itself, which already holds the control flow (it would be the choice of novice designers).

• An instance of the Book class, because it has a one-to-many association to Item.

• An instance of the Cart class, because it is a composite aggregation of Item.

If the controller option is chosen, then a coupling that did not exist before between it and the class Item would be created. That would be a bad choice for design.

Following the creator design pattern, the best choice would be Cart. The association between Item and Book is normal while the association between Cart and Item is a composite aggregation.

For the first version of the communication diagram, let us forget for a while the conditional if, and the postconditions that add links and change attributes values. Let us concentrate on creating an instance of Item.

The first task for the designer is to obtain the correct instance of Cart given its ID, and delegate to it the creation of the new instance of Item. This is shown in Figure 9.35.

image

FIGURE 9.35 Communication diagram that shows an instance being created.

This diagram implements only one postcondition: creation of an instance of Item. Initially, the controller must determine which cart is given aCartId. This is performed by the message labeled “1,” getCart, which queries the cart role of the controller Livir and stores the resulting instance in the variable aCart, which is a local variable for the system command add2Cart that is being modeled. As the precondition aCart->notEmpty() assures that aCartId is valid, the dynamic model does not need to check it again.10

In the sequence, the message labeled “2,” createItem, delegates to the cart the responsibility of creating an item, because the controller does not have any association to Item. If the controller has created the new item without delegating, then Livir would need to be coupled to class Item and issues related to high coupling would occur. The parameters of createItem were left to be added later as they are not yet necessary.

Finally, the instance of Cart achieves the postcondition by sending a basic message Create, labeled “2.1” in the diagram, which produces a new instance of Item. This instance is stored in a variable named newItem that is local to the createItem method in the Cart class.

Some designers would prefer to visualize the collaboration among objects shown in Figure 9.35 as a sequence diagram. Figure 9.36 presents a sequence diagram that is equivalent to the communication diagram of Figure 9.35.

image

FIGURE 9.36 Sequence diagram equivalent to the communication diagram of Figure 9.35.

The sequence and communication diagrams used for dynamic modeling here have three kinds of messages:

• A message that invokes a system operation, which is sent by the interface and received by the controller: add2Cart in the example.

Basic messages that invoke operations that need not be refined further: getCart and Create in the example.

Delegate messages that may be used when an instance does not have visibility to the object that must perform a responsibility: createItem in the example.

The first and second types of messages are always present in those diagrams. The message that invokes the system operation is the root of the messages tree of the dynamic model. The basic messages are the leaves of the messages tree, which correspond to the final object queries and updates.

Basically, there is one basic message for each individual postcondition. Those messages are basic in the sense that they do not need to be refined further: the control flow ceases when they are reached.

The third kind of messages referred to above, delegate messages, are optional. They may be used if the controller, instead of sending the basic messages itself, has to delegate responsibilities to other objects that have more immediate visibility to the objects that must be queried or updated.

Delegated messages are the branches of the messages tree, and the flow usually must continue after them. The message flow only finishes when a basic message is reached.

9.4.2 Link addition

It was mentioned that when an instance is created, it must be immediately linked to another instance so that it may be accessible from the façade controller. The contract for add2Cart presented in Section 9.4.1states that the recently created instance of Item must be linked to a book and an order. Figure 9.37 shows an evolution of the dynamic model with those two postconditions being achieved.

image

FIGURE 9.37 Evolution of the model in Figure 9.35 showing the addition of links.

Now, when the cart is performing the createItem method, it creates the new item (message 3.1), adds a link between itself and the new item (message 3.2), and adds a link between new item and the book identified by anIsbn (message 3.3).

This design could be acceptable because it reproduces almost literally what is specified by the contract. However, this would be the case only if the code generated by that design would not be manually changed later. If the final programming code is going to be directly changed some time in the future then additional precautions must be taken, and that is almost always the case.

The problem is that when an Item is created by a basic constructor with no parameters, it is not consistent with its definition until every mandatory attribute and link is defined for it. In the case of Figure 9.37, newItem is inconsistent in the interval between messages 3.1 and 3.3. The solution to this problem is to avoid having the constructors return inconsistent objects. The constructor in this case has to receive all parameters that are needed to generate a consistent instance. Therefore, the Create constructor is not simply a basic message in the initial conception of the term, because it must be further refined. Figure 9.38shows a new version of the dynamic model where the constructor of Item produces an instance with the necessary mandatory associations.

image

FIGURE 9.38 A variation of the model of Figure 9.37 where the constructor of Item produces a consistent instance.

Now, even if the final programming code is changed manually in the future, the designer knows that anytime an instance of Item is used it is consistent to its definition.

9.4.3 Attribute value modification

Another kind of postcondition that must be considered in dynamic modeling is the attribute value modification, which may be achieved by the basic setter message that can be sent by the object that owns the attribute or by one of its neighbors. Continuing the example, the quantity attribute of the new instance of Item must be filled with the respective aQuantity parameter of add2Cart, and the unitPrice attribute of Itemmust be initialized with the respective book price, as stated by the initial value declaration for that attribute in the diagram. These postconditions are added to the diagram and shown in Figure 9.39.

image

FIGURE 9.39 Evolution of the model in Figure 9.38 showing attributes being changed.

9.4.4 Instance destruction

The destruction of an instance is dynamically modeled by sending a destroy basic message to an object. The same principles of the creator design pattern apply here: the object that destroys an instance must have a shared or composite aggregation relationship with the object that is supposed to be destroyed, or an association from one to many, or, at least, be associated to the object.

Care must be taken to avoid leaving objects with mandatory links inconsistent after an object is destroyed. If the contract is well formed no orphan objects will be left, and the communication diagrams only need to represent the postconditions that are mentioned in the contract.

Figure 9.40 presents a dynamic model for the CRUD delete command for an instance of Book. The contract assumes that only ISBNs of instances that can be deleted may be passed as an argument:11

image

FIGURE 9.40 Communication diagram with an object being destroyed.

Context Livir::deleteBook(anIsbn:ISBN)

pre:

book[anIsbn]->notEmpty() and

book[anIsbn].item->isEmpty()

post:

book[anIsbn]^destroy()

9.4.5 Removing and replacing a link

When a postcondition establishes that a link must be removed, some object in the dynamic model has to send a remove basic message to one of the participating instances. Figure 9.42 shows an example of a dynamic model, based on the class diagram of Figure 9.41, where a link is removed and replaced by another; it is the dynamic version of the following contract that specifies how a car changes its owner:

image

FIGURE 9.41 Reference class diagram.

image

FIGURE 9.42 Communication diagram with a link replacing command.

Context Control::

changeOwner(newOwnerId:IdNumber,aLicense:CarLicense)

pre:

person[newOwnerId]->notEmpty() and

car[aLicense]->notEmpty

post:

car[aLicense]^removeOwner() and

car[aLicense]^addOwner(person[aNewOwnerId])

As the owner role has multiplicity 1, the command removing the link does not require the specification of the parameter.

Also, as the role has multiplicity 1, a new basic message to replace the association could be created instead of the remove/add sequence. That message could be called setOwner(newOwner:Person) or replaceOwner(newOwner:Person), and it would be thread-safe. The remove/add sequence may be dangerous if in the short time between those commands some other process (thread) makes a query on the object that is in a temporarily inconsistent state. If a single command such as replace is used instead and if it is assured that while it is being performed the object is locked even for reading, then the dangerous situation described above would not occur.

9.4.6 Conditional postconditions

As explained before, sometimes a postcondition must be obtained only if the given conditions are valid. In these cases, it is possible to use conditioned messages in interaction diagrams that are similar to the guard conditions used in machine state and activity diagrams.

Resuming the add2Cart example, the conditional expression verifies if the cart already has one item with the book. That expression does not qualify as a derived attribute because it is parameterized (it is Boolean, but its value depends on the book), and therefore it may be defined as a normal query in the Cart class:

Context Cart::includeBook(aBook:Book):Boolean

body:

item.book->exists(aBook)

Figure 9.43 shows the evolution of the model of Figure 9.39, now testing if a book already belongs to the cart or not. Notice that message 3 was renamed insertItem, because it is not just creating a new item anymore: it may be created or incremented.

image

FIGURE 9.43 A complete draft of a dynamic model for the add2Cart contract.

If the conditional is simply an “implies,” then a conditional message would be sufficient in the diagram. But if the conditional involves a structure of the if-then-else-endif kind, as in Figure 9.43, two mutually exclusive conditional messages must be used. In Figure 9.43, message 3.1 is executed only if includeBook(aBook) is true, and message 3.2 is executed only if that condition is false.

Notice that messages 3.1.1 to 3.1.5 are subordinated to 3.1 and they are executed only if 3.1 is executed. Similarly, messages 3.2.1 and 3.2.2 are subordinated to 3.2 and are executed only if 3.2 is executed.

Message 3.2.1, selectItemFor, is another query that is useful for the Cart class. It may be defined as

Context Cart::selectItemFor(aBook:Book):Item

body:

item->select(book=aBook)

Message 3.2.2 is also important: if only basic messages (as defined earlier) were used, instead of message 3.2.2, a sequence of currentQuantity:=getQuantity() and setQuantity(currentQuantity+aQuantity) would be used. However, that construction is not thread-safe. Unless concurrency is perfectly controlled, that sequence of messages could leave the quantity in an inconsistent state if it is updated between those two messages by another process. Thus, usually, instead of performing a sequence of get and set to increment numeric attributes, it is preferable to define an increase basic message that simply adds the quantity passed as a parameter to the respective numeric attribute.

Up to this point, dynamic modeling produced two new queries for the Cart class: includeBook and selectItemFor. It also produced two delegate methods for the Cart class: insertItem and incrementItem. For the Item class, a constructor was defined, and as the quantity item must be incremented, a new basic message was defined too: increaseQuantity. The other messages in the diagram such as getCart, setQuantity, etc., are all basic messages.

However, another concern must be addressed now: Are the items, after their creation, supposed to change? This question is related to the immutability design pattern that establishes that an object that is not supposed to change should not implement public update methods. Specifically, in the case of the Item class, which has an instance created in Figure 9.43, we must consider if its book, cart, price, or quantity may be changed later. That depends, of course on other dynamic models that will be constructed for other system operations, but we can assume now that quantity and unitPrice may change while the linked book and cart are immutable. Therefore, the item must not leave addBook and addCart as public methods. Private methods may be declared in UML class diagrams by adding a minus (“−”) sign before the method name. They are shown in the diagram in Figure 9.50. Private methods exist but can be called only by the instance that owns them; for other objects they are not accessible.

9.4.7 Exceptions

Conditionals may also be used to model exceptions. Consider again the contract for deleting a book, but this time, instead of assuring that the book may be deleted by precondition, an exception is raised if the book is linked to at least one item:

Context Livir::deleteBook(anIsbn:ISBN)

pre:

book[anIsbn]->notEmpty() and

post:

book[anIsbn]^destroy()

exception:

book[anIsbn].item->notEmpty() implies

Exception.throw(‘Cannot delete a book that was sold’)12

Exceptions usually are the first conditions to be tested in the dynamic model, because if one of them occurs, all other postconditions must not be obtained. Thus, a conditional message would be used to test the exception condition before anything else.

The diagram in Figure 9.44 shows how to test and raise an exception if the book has items.

image

FIGURE 9.44 A dynamic model for a contract with an exception.

The model in Figure 9.44 assumes that the throw message is terminal, that is, the execution of the system operation is interrupted by it. This means that if the exception condition is true, message 3 (the actual destructor) is not executed.

9.4.8 Postconditions over collections

When system commands have contracts with postconditions that specify updates over collections of objects, the iteration structure may be used in the communication diagram to indicate that a message is iteratively sent to all of the elements in the collection. For example, the following system command increases the price of every book by aPercentage:

Context Livir::raisePrices(aPercentage:Percentage)

post:

book->forAll(aBook|

aBook^setPrice(aBook.price@pre*(1+aPercentage))

)

The diagram in Figure 9.45 is a dynamic model for that contract.

image

FIGURE 9.45 Dynamic model for a contract with a postcondition over a collection of objects.

Again, the choice to define a new basic message raisePrice instead of a sequence of getPrice and setPrice is because it is thread-safe. The basic message raisePrice changes the value of the attribute price by multiplying it by 1 plus the parameter aPercentage.

Another common situation occurs when the iteration must not occur over every object in the collection, but only over those that satisfy a given condition. The following command raises by aPercentage the prices of books with more than aPageCount pages:

Context Livir::raiseThickBooks(aPercentage:Percentage; aPageCount:Natural)

post:

book->select(pageCount>aPageCount)->forAll(aBook|

aBook^setPrice(aBook.price@pre*(1+aPercentage))

)

The communication diagram for that contract is shown in Figure 9.46.

image

FIGURE 9.46 Communication diagram with iteration and selection.

9.5 System queries

The system query contracts usually define only combinations of data that are obtained from the objects. It is possible, although not always useful, to develop communication or sequence diagrams for such contracts, as, for example, the communication diagram of Figure 9.47 that was built for the contract of the listBooks system query below:

image

FIGURE 9.47 Collaboration diagram for a system query.

Context Livir::listBooks():Set

body:

book->collect(aBook|

Tuple {

isbn=aBook.isbn,

title=aBook.title,

authorsName=aBook.authorsName,

price=aBook.price,

pageCount=aBook.pageCount,

publisherName=aBook.publisher.name,

coverImage=aBook.coverImage,

quantityInStock=aBook.quantityInStock

}

)

The most difficult aspect related to these diagrams when they are used to model system queries is that they do not ease the representation of data composition. For example, in Figure 9.47 the controller sends eight messages to each instance of Book; each message produces a different return. What does the controller do with all those values? We know, from the contract of the query, that it forms a tuple with the eight values and groups the tuples into a set that is the final return data for the query. But representing that on the diagram would only repeat what is already known in the contract. Therefore, the utility of these diagrams in the case of queries is limited.

There are some important issues about modeling queries, however. First of all, three types of query messages may be identified:

• The system query, which is implemented in the controller and is called directly by the interface. As the domain objects are encapsulated into the domain tier and the interface deals with fields and buttons, system queries should receive alphanumeric data as parameters and return alphanumeric data as well.

Basic queries, which consult actual attributes and links corresponding to the basic get queries.

• The intermediate queries, which correspond to the derived attributes and associations. Derived attributes return alphanumeric data and derived associations return objects or data structures holding objects. Another type of intermediary query is the parameterized query, which cannot be defined as a derived attribute or association, such as includeBook in Figure 9.43.

In Figure 9.47, the only intermediary query is getPublisherName, sent by the controller to the book. The book, during its turn, sends the basic query getName to its publisher. Thus, publisherName may be considered a derived attribute of Book.

With regard to the question, “Should the intermediary query return the publisher name or the publisher itself?” some considerations must be taken into account:

• If a single attribute of an object is needed, avoid returning the object, because it may generate a risk of nonconformance to the “Don’t talk to strangers” pattern. If a single alphanumeric value is needed, then it is better to define it as a derived attribute such as publisherName in the Book class.

• When a set of attributes or values must be obtained from one or more objects, and the intention is to operate over them, for example, performing a sum, or finding the highest value or the average, it is preferable to perform the operation on the class that has the most immediate access to the values. In other words, the operation should be performed as soon as possible, and the already processed result should be returned as a derived attribute. For example, totalValue, which is a derived attribute of Cart, must be calculated by the cart itself, not by the customer or the controller. It would be a bad design if the cart returned the items to the controller for it to perform the sum over the subtotals.

• When collections of values with a complex structure must be returned and cannot be transformed into a single alphanumeric value, it is preferable to return that information as a collection of objects, defining it as a derived association, because that way, the methods that the classes already have to perform calculations and operations over the data remain available. However, this choice can possibly create more coupling, and it must be used only if the other choices are really not applicable.

9.6 Delegation and low coupling

To this point, we have seen some techniques for building communication diagrams. But in most situations there is more than one design option, and more than one way to send a message.

As discussed earlier, sometimes the object that has control does not have visibility to the object that can perform the responsibility. In this case, two opposite design approaches can be identified:

• The object that has control tries to obtain a reference to the object that can perform the responsibility, and communicate directly with it.

• The object delegates the message to another object that is closer to the object that can perform the responsibility.

In order to understand the difference between these two approaches, imagine that Mary is the boss of Peter. The boss wants to order a buffet for the office party, but she does not know any catering companies. The boss knows that Peter has contact with some catering companies, and then, two approaches are possible:

• The boss asks Peter for the phone numbers of the companies and makes the arrangements herself. The only thing that Peter does is to pass his contacts to the boss.

• The boss sends Peter the parameters for the party: how many people, what kind of food, how much to spend, etc., and asks Peter to do the arrangements. The party happens, and the boss was never informed about the catering contact: he delegated to Peter.

Although in real life it may be interesting to make contact with many people and companies, this is not the case in object-oriented systems. When objects are highly coupled, the system is more complex than it really needs to be. Thus, it is necessary to avoid creating connections between objects that were not connected in the first place.

The first approach discussed just above is called concentration. With it the object that has execution control manages to obtain references to all objects needed and performs the operation itself. The second approach is called delegation, and it consists of making the objects delegate responsibilities when they cannot perform them. The second approach is usually preferable, because it provides more potential for reusing classes and methods, because the intermediary queries or delegated methods that this approach creates usually are reusable.

A concrete example of this is shown below. Consider a system query getCartTotal defined as follows:

Livir::getCartTotal(aCartId:CartId):Money

body:

cart[aCartId].item->sum(quantity*unitPrice)

The system query contract above has as objective of obtaining the total value of a given cart (the definition of the contract intentionally does not use the derived attributes defined in Figure 9.34, assuming that they are not yet defined). This value must be calculated from the value and quantity of each of the items of the order.

With the concentration approach, the whole process would be implemented in the Livir class. This class would obtain all necessary data to perform the calculation and return the result to the interface. The concentration communication diagram has all the messages that are sent by Livir. No delegation happens, as no object sends messages besides the Livir instance. The pseudocode for that system query could be described as follows:

CLASS Livir

ASSOCIATION VAR carts:MAP[CartId->Cart]

METHOD getCartTotal():Money

LOCAL VAR aCart:Cart

LOCAL VAR cartsItems:SET<Item>

LOCAL VAR total:Money

aCart:=carts.at(aCartId)

cartsItems:=aCart.getItem()

total:=0

FOR EACH item IN cartsItems DO

total:=total+(item.getValue()*item.getQuantity())

END FOR

RETURN total

END METHOD

END CLASS

We can take the following away from this code:

• It solves the problem.

• It produces no reusable software element besides the query itself that is implemented in the Livir class.

• It increases coupling among classes, because Livir acquires visibility to Item, which was not the case in the class diagram of Figure 9.34.

By using the delegation approach, the query can be implemented correctly as well, but at the same time, new reusable structures with more cohesion and less coupling are created.

In the first place, the Livir class must be prevented from having access to any instance of Item, since there are no connections between those classes in the class diagram. In order to perform the required responsibilities, Livir must delegate the calculation to the cart. As the return value is Money (a specific alphanumeric), creating this intermediate query for the Cart class is equivalent to defining a derived attribute for it with the following OCL specification:

Context Cart::total:Money

derive:

item->sum(subtotal)

Besides creating a derived attribute in Cart, a derived attribute should also be created in the Item class, in order to return the subtotal:

Context Item::subtotal:Money

derive:

unitPrice*quantity

Now, with those two derived attributes, the pseudocode may be defined with responsibilities distributed among the classes:

CLASS Livir

ASSOCIATION VAR carts:MAP[CartId->Cart]

METHOD getCartTotal(aCartId:CartId):Money

RETURN carts.at(aCartId).getTotal()

END METHOD

END CLASS

CLASS Cart

ASSOCIATION VAR items:SET<Item>

METHOD getTotal ():Money

LOCAL VAR total:Money

total:=0

FOR EACH item IN items DO

total:=total+item.getSubtotal()

END FOR

RETURN total

END METHOD

END CLASS

CLASS Item

ATTRIBUTE VAR quantity:Natural

ATTRIBUTE VAR unitPrice:Money

METHOD getSubtotal():Money

RETURN quantity*unitPrice

END METHOD

END CLASS

With that approach, most of the calculation happens in the class that effectively has the most immediate access to the necessary information, which is Cart. Thus, the calculation procedure here is more reusable than the one produced by the concentration approach, because it is now inside the Cart class. If the Cart class is reused it can calculate its own total without depending on a class that is not a component of it (Livir). The same is true for Item, which now knows how to calculate its own subtotal.

In the case of system commands, the low coupling principle is also obtained when the designer chooses to delegate instead of returning an object. Consider again the class diagram of Figure 9.34, but suppose that the role from Cart to Item is marked with {ordered}. The communication diagram of Figure 9.48 shows the system command deleteLastItem, using the concentration approach. Figure 9.49 shows the same command with the delegation approach, avoiding unnecessary coupling among classes.

image

FIGURE 9.48 Concentration design style.

image

FIGURE 9.49 Delegation design style.

The price for using delegation is usually having more methods declared in the intermediary classes. However, reusable methods are usually produced to counteract this issue. The price for using the concentration approach is having a design where responsibilities are concentrated in the façade controller instead of being distributed among the objects; this produces a model that is hard to evolve and maintain in the long term.

9.7 Design class diagram

One of the goals of the logical design is to build and refine the design class diagram (DCD), which is created from the conceptual model and from information obtained during dynamic modeling (when the interaction diagrams for the contracts are being built).

The first version of the DCD is an exact copy of the conceptual model, which will be modified later. The basic modifications that are made in the DCD during domain tier design are the following:

Methods are added. During analysis, only system commands and queries were discovered and added to the façade controller class. During design, delegate and basic methods belonging to other classes are added.

Associations are directed. During analysis, associations were nondirectional. During design their direction is determined by the direction of the messages flowing between objects in the interaction diagrams.

Attributes and associations may be detailed. It is not mandatory to present details about attributes and associations during analysis. These details may be added during design. Additionally, in analysis only abstract data types are used to specify association roles, and they may be replaced by concrete data types during design (for example, replacing sequence by array list or linked list).

The structure of classes and associations may change. It may be necessary to create new classes to implement certain design structures, such as strategies and services, for example. Thus, it is possible that the class structure of the DCD does not correspond exactly to the structure of the conceptual model, in some cases.

Possible creation of protected and private attributes. In the conceptual model all attributes are public, because they represent static information and not behavior. However, when the dynamic aspects of objects are represented, it may be necessary to work with private attributes in order to encapsulate internal states that determine the behavior of some objects. These attributes sometimes may not be directly accessible through a getter or setter, but their influence on some methods is felt.

During the construction of a communication diagram, each time an object receives a delegated message, its class must implement the corresponding method.

In Figure 9.43, the communication diagram shows that a number of messages must be implemented as methods in some classes:

Livir must implement:

add2Cart(aCartId:CartId, anIsbn:ISBN, aQuantity:Natural) – the system command.

getCart(aCartId:CartId):Cart – basic query.

getBook(anIsbn):Book – basic query.

Cart must implement:

insertItem(aBook:Book, aQuantity:Natural) – delegate command.

includeBook(aBook:Book):Boolean – query or predicate.

incrementItem(aBook:Book, aQuantity:Natural) – delegate command.

selectItemFor(aBook:Book):Item – query.

Item must implement:

Create(aBook:Book, aCart:Cart, aQuantity:Natural) – constructor.

addBook(aBook:Book) – private basic command.

addCart(aCart:Cart) – private basic command.

setQuantity(aQuantity:Natural) – basic command.

increaseQuantity(aQuantity:Natural) – basic command.

setUnitPrice(aValue:Money) – basic command.

Book must implement:

getPrice():Money– basic query.

The DCD may also include information about the direction of the associations if the designer chooses not to implement every association as bidirectional. This is usually the case because unidirectional associations tend to be simpler to implement and more efficient when compared to bidirectional associations.

Deciding whether an association is unidirectional or bidirectional depends on the flow of messages on the interaction diagrams. No association should be, in principle, navigable in the direction of the controller: the controller sees the classes, but they do not see it. Other associations may be unidirectional if the messages only flow in one direction, and bidirectional if messages flow in different directions, even if that occurs in different diagrams. Figure 9.50 presents an evolution of the diagram in Figure 9.34 where methods and association directions discovered in the dynamic model of Figure 9.39 are included.

image

FIGURE 9.50 Design class diagram with methods and association direction.

Not every association in Figure 9.50 is directed because the diagram in Figure 9.39 shows messages being sent just from the controller to Cart, Cart to Item, and Item to Book. Associations that were not followed by messages in the communication diagram are left undefined until another diagram eventually decides on their direction.

If other collaboration diagrams developed for different system operations require messages navigating in the opposite direction as those that were already discovered, then the respective associations should be bidirectional.

As the direction of the associations depends on the direction of the messages sent in the dynamic models, it would not make sense to try to define that direction during preliminary conceptual modeling (Chapter 3). At that time the information available is insufficient to make those design decisions. The same is valid for deciding on which methods a class must implement.

It is assumed by default that unidirectional associations have no restrictions on their origin. However, that is not always the case. It is true, for example, for the association from Item to Book, which has no multiplicity restriction at its origin. But the association from Cart to Item is restricted at the origin, because it demands that an item is linked to exactly one cart. If the association is implemented simply as a variable in the Cart class holding a set of Items, then that constraint would not be easily assured. This is why, in the next chapter, unidirectional associations with restrictions at the origin are handled as bidirectional associations: they may not be navigated in both directions, but they must be implemented at both ends.

The logical design activity finishes when the DCD has enough information to implement the domain tier classes. That usually happens when all system operation contracts have been examined and their dynamic models incorporated into the design.

9.8 The process so far

Image

Image

Image

9.9 Questions

1. What are the six kinds of responsibilities that objects may have? What kind of method is used to perform each responsibility?

2. What are the four kinds of visibility that may exist between objects? Are they symmetrical? How does each of them influence high coupling among classes?

3. Read again the last paragraph of Section 9.3.2. Try to draw a communication diagram that illustrates that situation. Why is it a worse design than the diagram presented in Figure 9.29?

4. Explain the influence of contract preconditions and exceptions in dynamic models. How do they affect the complexity of the dynamic model?

5. Draw a sequence diagram equivalent to the one in Figure 9.43.


1That is valid for normal attributes belonging to classes of the conceptual model, though. During the construction of the DCD, new design attributes that represent the internal state and influence the behavior of an object may become necessary and those attributes do not necessarily have to be accessed by other objects: in that case, they should not have getters (they are private or protected).

2Remember that not every attribute must have a setter. Immutable attributes should not have a setter. Derived attributes never have setters, and private attributes also should not have public setters.

3The add and remove prefixes could be used here even for associations to 1, because conceptually they are links like any other. However, usually associations to 1 do not implement commands such as add and remove: they implement the set or replace command instead, which can replace the current mandatory link with another, leaving the role always consistent.

4It could be considered that a 0..1 role is filled with a single object or a null value. However, that is not coherent with the interpretation of other multiplicities such as, for example, 0..2 or *, where 0 stands for the empty set and not for the null value. To avoid such inconsistency it is preferable to consider that multiplicity 0..1 refers to a set that can be empty or contain a single element.

5And additionally to a set that contains an order, even if that option would not be used often.

6A set by default.

7In this text it is assumed that the operations available for sets and other collections are the ones defined by OCL (Object Management Group, 2010). However, if different languages or packages are used, a different set of operations would be available.

8The association probably would be physically implemented not as two sets, though (see Chapter 10). Here the visibility means that two sets of objects may be obtained by an instance; this does not mean that they are implemented like that.

9A summarization of the Law of Demeter (Lieberherr & Holland, 1989)

10This is true in normal conditions. However, if the system is subjected to complex race conditions and if security is a big issue, sometimes double-checking would be advisable.

11For example, the delete command could be called only for books selected from a list that contains only books that can be deleted, i.e., books with no items linked to them.

12Usually exceptions would not receive the very message as a parameter, but an exception code that refers to a list of messages. However, to keep examples simpler they are left here as they are.