LECTURE 7: Object Oriented Design Ivan Marsic Rutgers University Topics • Assigning Responsibilities to Objects • Design Principles • Expert Doer • High Cohesion • Low Coupling • Business Policies • Class Diagram 2 System Sequence Diagrams We already worked with interaction diagrams: System Sequence Diagrams : System User Timer «initiating actor» select function(“unlock") «offstage actor» prompt for the key enter key verify key signal: valid key, lock open open the lock, turn on the light start ("duration“) System Sequence Diagrams considered interactions between the actors 3 Design: Object Interactions Design Sequence Diagram System Sequence Diagram Controller «initiating actor» select function(“unlock") : KeyStorage : LockCtrl checkKey() ystem : System User : Checker Timer sk := getNext() «offstage actor» prompt for the key alt enter key val != null setOpen(true) verify key signal: valid key, lock open open the lock, turn on the light [else] val == null : setLit(true) start ("duration“) • System Sequence Diagrams considered interactions between the actors • Object Sequence Diagrams consider interactions between the objects 4 Metaphor for Software Design: “Connecting the Dots” Resident :InterfacePage :SearchRequest :Controller :PageMaker :DatabaseConn :Archiver :Notifier :InvestigRequest We start with objects/concepts from the Domain Model and modify or introduce new objects, as needed to make the system function work. Database Landlord 5 Types of Object Responsibilities • Knowing responsibility: Memorizing data or references, such as data values, data collections, or references to other objects, represented as a property • Doing responsibility: Performing computations, such as data processing, control of physical devices, etc., represented as a method • Communicating responsibility: Communicating with other objects, represented as message sending (method invocation) 6 How To “Connect the Dots” Starting Points: Domain Model from UC-1 (domain concepts): Symbolizes “worker”-type concept. Use Case UC-1: Unlock (flow of events): 1. User enters the key code 2. System verifies that the key is valid 3. System signals the key validity 4. System signals: (a) to LockDevice to disarm the lock (b) to LightSwitch to turn the light on «entity» KeyChecker «boundary» KeycodeEntry «boundary» StatusDisplay Symbolizes “thing”-type concept. «entity» KeyStorage «entity» Key «control» Controller LockDevice «boundary» HouseholdDeviceOperator Scenario Walkthrough: User LightSwitch for mapping a Use Case scenario to the Domain Model Q: who handles this data? Interface objects and Controller message: checkKey(k) Q: who performs the verification? Based on what data? return value Key Checker, based on entered key-code and stored valid keys message: ??? Q: who signals? Based on what data? Controller and Interface objects, based on key verification; because they are «boundary» Q: who signals? Based on what data? Controller or Key checker ???, based on key verification Design: Assigning Responsibilities ? : DatabaseConn ? : PageMaker accessList := retrieve(params : string) : Controller : Checker : DeviceCtrl checkKey() R1. interfacePage := render(accessList : string) ? activate( "lock" ) R2. (a) (b) 8 How Data Travels Option A: “expert” (Key Checker) passes the information (key validity) to another object (Controller) which uses it to perform some work (activate the lock device) key-code key-code Controller is-valid Checker activation-params Controller LockCtrl “expert” on key validity Option B: “expert” (Key Checker) directly uses the information (key validity) to perform some work (activate the lock device) key-code key-code Controller activation-params Checker Advantage: Shorter communication chain LockCtrl Drawback: Extra responsibility for Checker “expert” on key validity Characteristics of Good Designs • Short communication chains between the objects • Balanced workload across the objects method_1() method_1() method_2() … method_N() • Low degree of connectivity (associations) among the objects 10 Design Principles • Expert Doer Principle: that who knows should do the task • High Cohesion Principle: do not take on too many computation responsibilities • Low Coupling Principle: do not take on too many communication responsibilities There are many more … 11 Design: Assigning Responsibilities : Controller : Checker : DevCtrl checkKey() : Controller : Checker : DevCtrl ok := checkKey() ? (a) setOpen(true) setOpen(true) (b) • Although the Checker is the first to acquire the information about the key validity, we decide to assign the responsibility to notify the LockCtrl to the Controller. • This is because the Controller would need to know this information anyway—to inform the user about the outcome of the key validity checking. • In this way we maintain the Checker focused on its specialty and avoid assigning too many responsibilities to it. 12 Cohesion Low cohesion High cohesion 13 Responsibility-Driven Design 1. Identify the responsibilities • • domain modeling provides a starting point some will be missed at first and identified in subsequent iterations 2. For each responsibility, identify the alternative assignments – if the choice appears to be unique then move to the next responsibility 3. Consider the merits and tradeoffs of each alternative by applying the design principles – select what you consider the “optimal” choice 4. Document the process by which you arrived to each responsibility assignment 14 UC-4: View Access Log Resident «html» interfacePage : specify query request : Controller : PageMaker : DatabaseConnection Database get( queryRequest : string ) accessList := retrieve(params : string) retrieve records result interfacePage := render(accessList : string) alt accessList != NULL page := renderList() [else] result displayed page := warning() «post page» 15 Example … Communicating responsibilities identified for the system function “enter key”: Responsibility Description Send message to Key Checker to validate the key entered by the user. Send message to DeviceCtrl to disarm the lock device. Send message to DeviceCtrl to switch the light bulb on. Send message to PhotoObserver to report whether daylight is sensed. Send message to DeviceCtrl to sound the alarm bell. 16 Unlocking Sequence Diagram : Controller : Checker : KeyStorage : LockCtrl : LightCtrl : Logger checkKey() sk := getNext() alt val != null [else] setOpen(true) val == null : setLit(true) logTransaction(val) sk = stored key; the process either terminates by matching a stored key or exhausting the key store. Key is a dynamic object, unlike others which are static contains keycode, name, other forms of ID, timestamp, door ID, … -- disposed of after checking 17 Unlock Use Case : Controller enterKey() k : Key : Checker : KeyStorage : DeviceCtrl : PhotoObsrv : Logger «create» val := checkKey( k ) loop [for all stored keys] sk := getNext() compare(k, sk) logTransaction( k, val ) «destroy» alt val == true activate( "lock" ) dl := isDaylight() opt [else] alt dl == false activate( "bulb" ) numOfAttempts++ numOfAttempts == maxNumOfAttempts denyMoreAttempts() activate( "alarm" ) [else] prompt: "try again" 18 Unlock Seq. Diag. Variation 1 : Controller k : Key : Checker : KeyStorage : LockCtrl k := create() checkKey(k) loop sk := getNext() setValid(ok) controlLock(k) ok := isValid() opt ok == true setOpen(true) To avoid an impression that the above design is the only one possible!! Sets a boolean attribute of the Key object: ok = true/false; Business logic (IF-THEN rule) relocated from Controller to LockCtrl 19 Unlock Seq. Diag. Variations 2&3 : LightCtrl : PhotoSObs controlLight() : LightCtrl checkIfDaylightAndIfNotThenSetLit() dl := isDaylight() opt : PhotoSObs dl == false setLit(true) dl := isDaylight() The caller could be Controller or KeyChecker opt dl == false setLit(true) Depends on which solution you consider more elegant; It is helpful that checkIfDaylightAndIfNotThenSetLit() is named informatively (reveals the intention), but the knowledge encoded in the name of the method is imparted onto the caller. 20 Summary of Design Variations : Controller enterKey() k : Key : Checker : KeyStorage : DeviceCtrl : PhotoObsrv : Logger c k := create() val := checkKey(k) loop : DeviceCtrl : PhotoSObs [for all stored keys] sk := getNext() compare() activate( "light" ) dl := isDaylight() logTransaction(k, val) «destroy» alt val == true activate(“lock”) opt dl := isDaylight() a : Controller opt k : Key [else] prompt: "try again" dl == false : Checker numOfTrials++ dl == false setLit(true) activate(“bulb”) : KeyStorage : DeviceCtrl k := create() opt checkKey(k) numOfTrials == maxNumOfTrials activate(“alarm”) loop b sk := getNext() : DeviceCtrl : PhotoSObs setValid(ok) checkIfDaylightAndIfNotThenSetLit() dl := isDaylight() controlLock(k) ok := isValid() opt ok == true setOpen(true) The caller could be Controller or Checker opt dl == false setLit(true) 21 Are We Done w/ UC-1: Unlock ? • Didn’t check that the user is at the right door – Missing: Managing access rights • Didn’t distinguish critical and non-critical functions – For example, what if logTransaction() call to Logger does not return, e.g., no access to database (network outage) or disk-space full ? – Missing: Independent execution of non-critical functions • Adding new household devices causes major design changes • Business rules are interleaved with authentication and device management, but business rules are most likely to change • Etc. Business Policies IF key ValidKeys THEN disarm lock and turn lights on ELSE increment failed-attempts-counter IF failed-attempts-counter equals maximum number allowed THEN block further attempts and raise alarm Should be moved into a separate object: • Make them explicit part of the model • Confine the future changes in business policies 23 Class Diagram Base Class Container PhotoSObsrv Key – code_ : string – timestamp_ : long – doorLocation_ : string + isDaylight() : boolean 1 sensor 1..* KeyStorage 1 + getNext() : Key validKeys 1 Controller # numOfAttemps_ : long # maxNumOfAttempts_ : long + enterKey(k : Key) – denyMoreAttempts() 1 1 1 logger Logger + logTransaction(k : Key) devCtrl KeyChecker checker + checkKey(k : Key) : boolean – compare(k : Key, sk : Key) : boolean DeviceCtrl # devStatuses_ : Vector + activate(dev : string) : boolean + deactivate(dev :string) : boolean + getStatus(dev : string) : Object 24 Traceability Matrix (3) Mapping: Domain model to Class diagram Controller-SS1 StatusDisplay Key KeyStorage KeyChecker HouseholdDeviceOperator IlluminationDetector Controller-SS2 SearchRequest InterfacePage PageMaker Archiver DatabaseConnection Notifier InvestigationRequest DatabaseConnection PageMaker «html» interfacePage SearchRequest Controller-SS2 Logger PhotoSObsrv DeviceCtrl KeyChecker KeyStorage Key Domain Concepts Controller-SS1 Software Classes X X X X X X X X X X X 25 Types of Object Communication S1 A A B B S2 P SN (a) (b) (c) 26