Chapter 2 • Object-Oriented Software Engineering
85
By implementing this rule, the object possesses the knowledge of conditions under which a method can or cannot be invoked. Hence, the question is which object is responsible to know this
rule? The needs-to-know responsibility has implications for the future upgrades of modifications. Changes in the business rules require changes in the code of the corresponding objects. More on
anticipating change in Chapter 5 below.
Apparently, we have built an undue complexity into the Controller while striving to preserve high degree of specialization for all other objects. This implies low cohesion in the design; poor
cohesion is equivalent to low degree of specialization of some objects.
The reader should by no means get impression that the design in Figure 2-11 is the only one possible. Some variations are shown in Figure 2-12. For example, in variation a the Checker
sets the key validity as a flag in the Key object, rather than reporting it as the method call return value. The Key is now passed around and LockCtrl, LightCtrl, and others can check for
themselves the validity flag and decide what to do. The result: business logic is moved from the Controller into the objects that operate the devices. Personally, I feel uneasy with this solution
where the correct functioning of the system depends on a flag in the Key object. A more elegant solution is presented in Section 5.1 below.
Although the variation in Figure 2-12b is exaggerated, I have seen similar designs. It not only assigns an awkward method name,
checkIfDaylightAndIfNotThenSetLit
, but
«destroy»
prompt: try again
opt
k := create sk := getNext
logTransactionk, val setOpentrue
: Controller : Checker
: KeyStorage : LockCtrl
: Logger : PhotoObsrv
dl := isDaylight
alt
[else]
enterKey k : Key
val := checkKeyk
loop
: LightCtrl : AlarmCtrl
setLittrue
soundAlarm
val == true
dl == false
compare
[for all stored keys]
numOfTrials++
opt numOfTrials == maxNumOfTrials
«destroy»
prompt: try again
opt
k := create sk := getNext
logTransactionk, val setOpentrue
: Controller : Checker
: KeyStorage : LockCtrl
: Logger : PhotoObsrv
dl := isDaylight
alt
[else]
enterKey k : Key
val := checkKeyk
loop
: LightCtrl : AlarmCtrl
setLittrue
soundAlarm
val == true
dl == false
compare
[for all stored keys]
numOfTrials++
opt numOfTrials == maxNumOfTrials
Figure 2-11: Sequence diagram for the system function “enter key.” Several UML interaction frames are shown, such as “
loop,” “alt” alternative fragments, of which only the one with a condition true will execute, and “
opt” optional, the fragment executes if the condition is true.
Ivan Marsic • Rutgers
University 86
worse, it imparts the knowledge encoded in the name onto the caller. Anyone examining this diagram can infer that the caller rigidly controls the callee’s work. The caller is tightly coupled to
the callee since it knows the business logic of the callee. A better solution is in Figure 2-12c.
Note that the system GUI design is missing, but that is OK since the GUI can be designed independently of the system’s business logic.
2.4.2 Class Diagram
Class diagram is created by simply reading it off of the interaction diagrams. The class diagram of our system is shown in Figure 2-13. Notice the differences and similarities with the domain
model, Figure 2-7.
Since class diagram gathers class operations and attributes in one place, it is easier to size up the relative complexity of classes in the system. The number of operations in a class correlates with
the amount of responsibility handled by the class. Good OO designs distribute expertise and workload among many cooperating objects. If you notice that some classes have considerably
greater number of operations than others, you should examine the possibility that there may be undiscovered classes or misplaced responsibilities. Look carefully at operation names and ask
yourself questions such as: Is this something I would expect this class to do? Or, Is there a less obvious class that has not been defined?
prompt: try again
opt
k := create sk := getNext
logTransactionk, val setOpentrue
: Controller : Checker
: KeyStorage : LockCtrl
: Logger : PhotoObsrv
dl := isDaylight
alt
[else]
enterKey k : Key
val := checkKeyk
loop
: LightCtrl : AlarmCtrl
setLittrue soundAlarm
val == true dl == false
compare
[for all stored keys]
numOfTrials++
opt numOfTrials == maxNumOfTrials
: LightCtrl : PhotoSObs
dl := isDaylight controlLight
opt dl == false
setLittrue : LightCtrl
: PhotoSObs
dl := isDaylight controlLight
opt dl == false
setLittrue
c
a
checkIfDaylightAndIfNotThenSetLit : LightCtrl
: PhotoSObs
dl := isDaylight
opt dl == false
setLittrue The caller
could be Controller or
Checker checkIfDaylightAndIfNotThenSetLit
: LightCtrl : PhotoSObs
dl := isDaylight
opt dl == false
setLittrue The caller
could be Controller or
Checker
b
k := create sk := getNext
: Controller : Checker
: KeyStorage : LockCtrl
k : Key checkKeyk
loop
setValidok controlLockk
ok := isValid
opt ok == true
setOpentrue k := create
sk := getNext : Controller
: Checker : KeyStorage
: LockCtrl k : Key
checkKeyk
loop
setValidok controlLockk
ok := isValid
opt ok == true
setOpentrue
Figure 2-12: Variations on the design for the use case “Unlock,” shown in Figure 2-11.
Chapter 2 • Object-Oriented Software Engineering
87
Class Relationships
Class diagram both describes classes and shows the relationships among them. I already discussed object relationships in Section 1.4.1 above. Generally, the following types of
relationships are possible:
• Is-a relationship hollow triangle symbol
∆: A class is a “kind of” another class •
Has-a relationship: A class “contains” another class -
Composition relationship filled diamond symbol
♦
: The contained item is a integral part of the containing item, such as a leg in a desk
- Aggregation relationship hollow diamond symbol
◊: The contained item is an element of a collection but it can also exist on its own, such as a desk in an office
• Uses-a relationship arrow symbol
↓: A class “uses” another class •
Creates relationship: A class “creates” another class In our particular case, Figure 2-13, there is an aggregation relationship between KeyStorage and
Key; all other relationships happen to be of the “uses” type. The reader should also notice the visibility markers for class attributes and operations:
+
for public, global visibility; for
protected visibility within the class and its descendant classes; and,
−
for private within-the-class- only visibility.
Class diagram is static, unlike interaction diagrams, which are dynamic.
Generic Object Roles
As a result of having specific responsibilities, the members of object community usually develop some stereotype roles.
KeyChecker + checkKeyk : Key : boolean
KeyChecker + checkKeyk : Key : boolean
Key – code_ : string
– timestamp_ : long – doorLocation_ : string
LightCtrl lit_ : boolean
+ isLit : boolean + setLitv : boolean
KeyStorage + getNext : Key
PhotoSObsrv + isDaylight : boolean
Logger + logTransactionk : Key
Controller numOfTrials_ : long
maxNumOfTrials_ : long + enterKeyk : Key
1 1
1 1
1 sensor
lightCtrl 1
checker
alarmCtrl lockCtrl
logger 1
1.. validKeys
1
AlarmCtrl + soundAlarm
LockCtrl open_ : boolean
+ isOpen : boolean + setOpenv : boolean
Figure 2-13: Class diagram for the home access system under development.
Ivan Marsic • Rutgers
University 88
• Structurer
• Bridge
Note that objects almost never play an exclusive role; several roles are usually imparted to different degree in each object.
Object Communication Patterns
Communication pattern is a message-sending relation imposed on a set of objects. As with any relation, it can be one-to-one or one-to-many and it can be deterministic or random. Some of
these are illustrated in Figure 2-14.
Object-oriented design, particularly design patterns, is further elaborated in Chapter 5 below.
2.4.3 Why Software Engineering Is Difficult 2
Another key cause is the lack of analytical methods for software design. Software engineers are aiming at optimal designs, but quantitative criteria for optimal software design are largely
unknown. Optimality criteria appear to be mainly based upon judgment and experience.
2.5 Software Architecture
Bottom-up design approaches at the local level of objects, reviewed in the previous section, are insufficient to achieve optimal designs, particularly for large systems. A complementary approach
are system-level macro-level, global design approaches which help us to “see the forest for the trees.” These approaches partition the system into logical units or follow some global
organizational patterns.
A A
B B
P P
S
2
S
2
S
1
S
1
S
N
S
N
A A
B B
a b
c
Figure 2-14: Example object communication patterns. a One-to-one direct messages. b One-to-many untargeted messages. c Via a shared data element.