600 likes | 634 Views
CS251 – Software Engineering Lectures 15 Implementing UML Models in Code Slides are borrowed from the net. Overview of This Lecture. Basic Implementation Steps. Implementing Classes: Class Association: Navigation direction; Multiplicity constraint; Qualified associations;
E N D
CS251 – Software EngineeringLectures 15 Implementing UML Models in CodeSlides are borrowed from the net
Overview of This Lecture • Basic Implementation Steps. • Implementing Classes: • Class Association: • Navigation direction; • Multiplicity constraint; • Qualified associations; • Association classes. • Implementing Statecharts
Requirement Analysis Design Implement Testing Where are we now? • Translate Design into Code Software Process Software Version Control
Implementation: Overview • Inputs: • Detailed UML Class Diagram; • UML Sequence Diagram; • UML Statechart. • Activities: • Implementation of class diagram; • Reminder: • Sequence diagrams show the basic behavior of some of the operations. • A statechart describes a complete lifetime for classes with complex behavior
1. Basic Steps in Implementation • Define methodinterface: • Give a formal/informal description of the functionality of an operation; • Useful in reducingmisunderstanding; • Allows splitting the workload. • Decide the order of implementation: • Which class/subsystem to start. • Implement the design using a suitable programming language.
Step 1. Defining Method Interface • During design: • Detailed information of the method header are defined: • Returned Type, Parameter Type; • Accessibility. • public, private, protected • However, the functionality is not formally defined: • What should the method do? What is the algorithm? • What are acceptable values for the parameter? • Etc…
Operation Contract • Writing the operation contract is one possible way to define the functionality. • Define the precondition: • Express the constraint regarding the attributes of the class and the actual parameter. • Define the postcondition: • Express the effect of an operation: • Instance Creation/Deletion; • Attribute Modification; • Association Formed/Broken. • Description can scale in formality.
+ deposit (amt : float)+ withdraw (amt : float) BankAccount - balance Operation withdraw()Contract: Example Precondition: - amt > 0 - amt <= balancePostcondition: - New balance = Old balance - amt
Step 2. Order of Implementation • Many classes with dependencies in the design: • Need an order of implementation. • Two basic strategies: • Top-Down: • Starts with high-level components. (e.g., Sys Manager class) • Then continue to implement the dependent components. • Bottom-Up: • Starts with the lower level classes, i.e., classes with least dependency. (Book, Dish, etc.) • Then proceed to code classes that make use of the implemented classes.
Example A myB B • myB may be an attribute in class A of class B; • The navigability is one possible indicator of the dependency. methodA() methodB() { myB.methodB();} A B A depends on B, i.e., Amakes use of B
Top Down Implementation • Starts with Class A, then Class B. • Advantages: • Class A represents ahigher level component as it makes use of other classes. • Starting with high level components enables earlyvalidation of the overall system. • Disadvantages: • Need temporary implementation (known as stub) for lower classes. • Example: • A temporary implementation of methodB()is needed for the implementation of methodA().
Bottom Up Implementation • Starts with Class B, then Class A. • Advantages: • Low level classes can usually stand alone. • No need for stub. • Disadvantages: • Have to postpone the generation of a complete executable program.
Implementing Classes • Basic mapping: • A UML Class is implemented by a class in Java, C++, etc; • Attributes are implemented by fields; • Operations are implemented by methods. • There may be some language specific issues: • E.g., the implementation of abstract classes, interfaces, and generalization.
# # + + + Example: The Booking Class public abstract class Booking { protected int covers; protected Date date; protected Booking(int c, Date d) { covers = c; date = d; } public void setCovers(int c) { covers = c; } public Date getDate() { return date; } public void setArrivalTime(Time t) { } } The getCovers(c:Integer) operation should be setCovers(c:Integer).
Example: Generalization public class WalkIn extends Booking { public WalkIn(...) { ... } } public class Reservation extends Booking { Time arrivalTime ; public Reservation(...) { ... } public void setArrivalTime(Time t) { arrivalTime = t ; } } The setArrivalTime() operation should be setArrivalTime(Time t).
Implementing Associations • Associations (Class Diagram) describe properties of links (Object Diagram). Account Guarantor :Account :Guarantor • A link gives one object access to another and enables message passing. • Java: References (C++: ObjectPointer) share these properties, so associations can be implemented with references. • References support only one direction of navigation. • Should restrict navigability to make implementation easier. • References should not be manipulated explicitly by other classes.
Optional Unidirectional Association • Distinction between associations: • direction; • multiplicity. • Reference variables can hold: • a reference to another object; • or the null reference. • So the ‘default’ multiplicity of a reference is ‘0..1’.
Implementation • The association is defined by a field in the Account Java class holding a reference to an instance of the DebitCard Java class: • public class Account { • private DebitCard theCard ; • ... • } Good match for simple object reference. 0..1 Account DebitCard
Maintaining the Association • As only Accountis “aware” of this association, it should also maintainthe association: • Setup/Removal of the link; • Appropriate methods for accessing the linked object. e.g. public DebitCard getCard(){return theCard; } Return the linked object to outsider public void setCard(DebitCard card){ theCard = card; } Changing the linked object
Mutability for Association • The last example assumes that the DebitCardassociation is mutable: • An Accountobject can be linked to a differentDebitCardobject during its lifetime. • Hence, it makes sense to have the following methods for setup/removal the link: public void setCard(DebitCard card){ theCard = card; } public void removeCard( ) { theCard = null; }
{Immutable} Immutable Association • Some associations are immutable: • once assigned, a link must not be altered. • The designer can state this in the class diagram: • The programmer has to check the link’s existence explicitly: Explicit Check to maintain Immutable link If there is a card, you cannot change it public void setCard(DebitCard card) { if (theCard != null) { // throw ImmutableAssociationException } theCard = card ; }
Compulsory Association • Multiplicity with lowerbound larger than 0 is compulsory, i.e., “At all times, the link(s) cannot be null”. • Example: • The above means “An Accountobject must have exactly one link to a Guarantorobject at all times”. • Programmers have to check explicitly to ensure this is true.
Implementation • The earliest possible opportunity to setup the link is in the constructormethod: public Account(Guarantor g) { if (g == null) { // throw NullLinkError } theGuarantor = g ; } Make sure we have a guarantor • If the association is mutable, similar checks must be performed in methods that changes the link, e.g.: public setGuarantor(Guarantor g) { if (g == null) { // throw NullLinkError } theGuarantor = g ; } Make sure we have a guarantor
1-to-Many Unidirectional Association • Some associations require more than one link to be stored: • Data structures, such as arrays and ArrayLists, can be used to store the references. • For a specific upper bound, e.g., “0…8”, specific checks must be implemented: public class Manager { private ArrayList<Account> theAccounts ; public void addAccount(Account acc) { theAccounts.add(acc) ; } } Stores * Accounts references.
Account DebitCard Account DebitCard theCard theAccount theCard: DebitCard theAccount: Account Bidirectional Association • Some associations need to be navigated in both directions: • because references are unidirectional, it will take two references to implement a bidirectional link; • the association can be implemented by including a suitable field in each of the associated classes. Bidirectional Association stated in Class Diagram Closest implementation of the Class Diagram in Java or C++
Bidirectional Association Implementation • DebitCardis optional (“0..1” from Account). • Accountis compulsory (“1” from DebitCard). • Hence, the best way to satisfy the conditions is: • Create the Accountfirst, then the DebitCard: • { • Account acc1 = new Account() ; • DebitCard aCard = new DebitCard(acc1) ; • acc1.setCard(aCard) ;} Question: Why is that the order of associations creation to satisfy the conditions?
theCard acc2: Account aCard: DebitCard acc1: Account theAccount Referential Integrity • The bidirectional association is implemented as two separate references, it is important to keep them consistent. • The links should be “inversed” one to each other. • Known as referential integrity. • Example of referential integrity violation: • Account acc1 = new Account() ; • Account acc2 = new Account() ; • DebitCard aCard = new DebitCard(acc1); • acc2.setCard(aCard) ; The two links do not correspond to a valid bidirectional association!
Maintaining Referential Integrity • The problem with the example code on last slide: • DebitCardcreation and reference setup (setCard() method) are two separate operations. • Responsibility of programmer to make sure the two operations are performed in a consistent fashion. • To minimize the human error, delegate one of the classes to maintain the referential integrity instead. • Example: • public class Account { • private DebitCard theCard ; • public void addCard() { • theCard = new DebitCard(this); • } • // other methods as well • } Accountclass handles both the DebitCardcreation and reference setting.
Joint Creation of Objects • Suppose an association is intended to be immutable and also needs to be traversed in both directions. • Constructors of both classes should take in an instance of the other to satisfy the constraint specified by the class diagram. • Java and C++ do not allow simultaneous object creation. • Must create one of the objects with the default constructor, i.e., to violate the constraint for a short time.
Code Example • Attempts to satisfy the compulsory link: Account a = new Account(g) ; Guarantor g = new Guarantor(a) ; g is not initialized. Account a = new Account(new Guarantor(a)) ; Guarantor g = a.getGuarantor( ) ; a is not initialized. • Have to violate the constraint for a while: gis initialized by default constructor, no Accountreference. Guarantor g = new Guarantor( ) ; Account a = new Account(g) ;g.setAccount(a) ; Setup the reference after Accountais properly initialized.
Domain Model Implementing Bidirectional Association myG Account Guarantor myG: Guarantor myA myA: Account Some likely methods getMyG( ): GuarantorsetMyG( Guarantor ) getMyA ( ): AccountsetMyA ( Account ) Class Diagram
Maintaining Bidirectional Association Methods that change the reference should maintain referential integrity. Take setMyG() for example: Have to set the reversed link too. If Accounthasalready a Guarantor: Either do not allow the link change. OR, remove the link with previous Guarantor. a :Account oldG: Guarantor The Reverse link has to be removed too. newG: Guarantor a.setMyG(newG)
Code Example //Class Account public void setMyG(Guarantor newG) { if (newG == null) //throw NullLinkError if (myG != null && myG != newG) { //previous Guarantor exists myG.removeMyA(this); } if (myG == newG) return; myG = newG; //set the reverse link myG.setMyA(this); } //Class Guarantorvoid removeMyA(Account a) { //only allows holder of //the reverse link call //this method if (a == myA) myA = null; }
Code Example (Cont) //Class Guarantor public void setMyA(Account newA) { if (newA == null) //throw NullLinkError if (myA != null && myA != newA) { //previous Guarantor exists myA.RemoveMyG(this); } if (myA == newA) return; myA = newA; //set the reverse link myA.setMyG(this); } //Class Accountvoid RemoveMyG(Guarantor g) { //only allows holder of //the reverse link call //this method if (g == myG) myG = null; }
Observations • Even with the complicated code, there are still some problems: • The old guarantor will be without an Accountobject. • Mutual recursive functions: • setMyA calls setMyG, which calls setMyA. • Very easy to get into infinite loop. • Should avoid compulsory bidirectional association if possible. • If bidirectional association must be used, immutability is recommended to curb the coding complexity.
One-to-Many Association • Raise no new issues: • Customer holds a collection of references. • Account holds a non-null reference. • Customer is a better choice as the “maintainer” for the referential integrity: • It provides a method like addAccount() that creates a new account and setup the reference. • Similar to the addCard() method on slide 29.
Many-to-Many Association • Previously discussed implementation techniques can be used. • Recommendation: • Allows only one of the classes to modify the links. • E.g., It makes sense to allow only the Customerto change links to Accountsand maintain the reversed link also. • Simplify the implementation as the coding logic can be considered from one direction only.
Many-to-Many Association • Another implementation technique is to reify the association: • The new class (e.g., Signatoryin the example above) is in a good position to maintain referential integrity. :Account :Signatory :Customer
Qualified Association • The purpose of a qualifier is often to provide efficient access to linked objects: • For example, to access accounts given only the account number; • Also to avoid a linear search through all accounts.
Implementing Qualifier • The run-time structure involves some kind of index to accounts: Using accno as index.
Code Example • Hash Table is a good choice to implement the index: public class Bank{ private HashTable theAccounts; public void addAccount (Account a) { theAccounts.put(new Integer(a.getNumber()), a); } public void removeAccount(int number) { theAccounts.remove(new Integer(number)); } public Account lookupAccount(int number) { return (Account) theAccounts.get(new Integer(number)); } } put (Object Key, Object Value) get(Object Key): Object
Hash table - reminder • It means a data structure that associates keys with values. • The primary operation it supports efficiently is a lookup: given a key (e.g., a person's name), find the corresponding value (e.g., that person's telephone number). • It works by transforming the key using a hash function into a hash, a number that is used to index into an array to locate the desired location ("bucket") where the values should be.
Hash table - reminder • Like arrays, hash tables provide constant-time O(1) lookup on average, regardless of the number of items in the table.
Association Class • Common strategy: • Transform the association class into a simple class; replacing it by two associations.
Module Student 1 * * Module Student :Module :Student :Module :Student :Registration Registration Registration :Registration :Registration :Registration Mark = 89 Mark = 76 Mark: Integer Mark = 89 Mark: Integer Mark = 76 :Student :Student Implementing Association Class Class Diagram Class Diagram Object Diagram Object Diagram
Code Example public class Module { private Vector registrations; public void enrol(Student st) { registrations.addElement( new Registration(st) ); } ... } Maintain the link to Registration Pass the Student to Registration. class Registration { private Student student; private int mark; Registration(Student st) { student = st; mark = 0; } ... } Registration keeps track of the Student reference.
Implementing Statecharts • If a statechart for a class exists: • it gives an overall specification of the class behaviour. • it can be used to structure the implementation of the operations in a systematic way. • Example: Account class • States: InCredit, OverDrawn, Suspended. • Operations: deposit, withdraw, suspend, unsuspend.