Practical Software Engineering 

Moving From Design to Code 


Table of Contents


 

Introduction
The processes of analysis and design results in the creation and subsequent refinement of several artifacts or deliverables. The following table shows when specific artifacts are created and when subsequent revision occurs:

ArtifactCreated duringRefined During
Use CasesAnalysisLater Analysis Iterations
Design Iterations
Class Diagrams AnalysisLater Analysis Iterations
Design Iterations
ScenariosDesignLater Design Iterations
Implementation Iterations
Sequence DiagramsDesignLater Design Iterations
Implementation Iterations
Collaboration DiagramsDesignLater Design Iterations
Implementation Iterations
StatechartsAnalysis and DesignLater Analysis and Design Iterations


When a system is being implemented, the artifacts are used in the following ways:

ArtifactUse During Implementation
Use Cases Not usually directly applicable to implementation. Used as reference during implementation to ensure that the implementation remains true to the original requirements. Useful if questions regarding business requirements arise during implementation. Used as a basis for creating test scripts which are to be applied against the code.
Class Diagrams Used extensively during implementation. Class Diagrams show entities and their attributes, operations/behaviour, and relationships with other entities. Developers implement classes and the resulting code must remain consistent with the class diagram.
Scenarios Scenarios describe the dynamic structure/behaviour of the system. They are derived from Use Cases, so a properly defined Scenario supports the user's requirements. Scenarios are a bridge between Use Cases and Interaction diagrams. Scenarios are consulted during implementation because they contain information which is not easily shown on sequence or collaboration diagrams.
Sequence Diagrams Define very specifically how the system responds to a particular scenario. Sequence diagrams show all objects involved with the interaction and how they work together to satisfy the request. Sequence diagrams are consulted often when method bodies are being implemented.
Collaboration Diagrams Show the same information as sequence diagrams, but in a more concise manner. Collaboration diagrams may contain added design notations which cannot be easily shown on sequence diagrams. Consulted during implementation when method bodies are being implemented.
Statecharts Statecharts define very formally the behaviour of complex objects. Statecharts are only created for objects which exhibit complex state. Therefore, they are consulted regularly when the classes of complex objects are being implemented.


 

Where to Start?
The artifacts of the system contain a large amount of information and many developers feel somewhat overwhelmed when confronted with this information. This section outlines a standard "plan of attack" which can be used when implementing a system. This plan can be easily adapted for differing contexts.

Static Structure

All object oriented languages support the definition of classes. This includes the ability to define attributes and methods. Developers should begin implementation by implementing the structure contained in the Class diagram. The Class Diagram defines all of the classes which must be implemented. If the design is sufficiently complete, the classes in the class diagram will contain attributes and method signatures. The programmer must provide a mapping between the class structure and the implementation. For simple class and method structures, many developers employ the use of code generators. Essentially, these tools reduce the amount of typing that has to be done to define the class structure for the system.

Although code generators can save a lot of time, be mindful of their limitations. Depending on the generator, the resulting code may be more or less maintainable depending on the complexity of the coding structures that are being generated. Regardless of how the code is created, humans will always have to maintain the code at some point. Avoid code generators which create incomprehensible code. The following outlines the mapping of a Class to Code:

Figure 1: Class Diagram with uni-directional association

Code Snippet 1:

public class Driver
{
private Car theCar;

public void setCar(Car aCar)
{
theCar = aCar;
}

public Car getCar()
{
return theCar;
}
}

 

Coding Associations
Implementing classes from a Class diagram is relatively straight forward. The attributes have defined types and the skeleton methods have parameter lists with defined types. Some methods are simple enough that their bodies can even be implemented by code generators. The implementation of associations, however, can become more difficult because of two factors:
  • Association navigability
  • Multiplicity
    • One-To-One
    • One-To-Many
    • Many-To-Many

Navigability

All object oriented languages support some form of uni-directional assocation. Smalltalk and Java provides support for associations through object references, Objective-C provides support for associations through object pointers, and C++ provides support through pointers and references. No language, however, provides direct support for bi-directional associations. Bi-directional associations must be implemented as two uni-directional associations.

Navigability is shown on the class diagram by placing an arrowhead on the association which indicates the direction of association. An association on the class diagram which contains no arrowheads is assumed to be a bidirectional association.

Bi-directional associations not only require more references, but they are more costly to maintain. Specifically, when an bi-directional association is made between two objects, that requires maintaining two references (not just one). These associations must remain consistent with each other and when multiplicity is involved, that can increase the complexity considerably (See below). Because of the added complexity of bi-directional associations, they should be avoided if at all possible. When performing a design review, examine bi-directional associations for justification. The reason for having a bi-directional association is because objects on each side of the association have to be able to initiate messages to objects on the other side of the association. Often, the design can be examined and an alternate method of sending messages (possibly through another existing association) can be found; thus, eliminating the need for the bi-directional association.

The following illustrates the implementation of the Car and Driver classes with a bi-directional association. Note that the implementation must now ensure the integrity of the bidirectional association.

Figure 2: Class Diagram with bi-directional association.

Code Snippet 2:

public class Driver
{
private Car theCar;

public void setCar(Car aCar)
{
theCar = aCar;

if (theCar.getDriver()!=this)
{
theCar.setDriver(this);
}
}

public Car getCar()
{
return theCar;
}
}

public class Car
{
private Driver theDriver;

public void setDriver(Driver aDriver)
{
theDriver = aDriver;

if (theDriver.getCar()!=this)
{
theDriver.setCar(this);
}
}

public Driver getDriver()
{
return theDriver;
}
}

Multiplicity

The simplest form of multiplicity to implement is One-To-One. The examples above show the implementation of both uni-directional and bi-directional one-to-one associations.

Multiplicity becomes more difficult when implementing one-to-many and many-to-many associations. The complexity becomes even greater if those associations are bi-directional (again, another reason to avoid bi-directional associations).

Take the example of the CarManager objects which manages several car objects. The class diagram shows that there is a one-to-many relationship between the CarManager and the Cars that it manages.

Figure 3: CarManager and Car -- Class Diagram

The question is, who maintains the references to all of the car objects? If that functionality is added to the CarManager class the added responsibilities will reduce the cohesion of that Class. To best solution is to introduce another object which claims the responsibility of maintaining the car object references. These types of objects are called Collection objects and almost all object-oriented languages provide some collection classes.

Collection classes do not typically appear on the class diagram. Their use is implied through the definition of multiplicity. Whenever there is a to-many relationship, the to-many portion will be implemented using a collection object. The developer must make the decision of which collection class to use.

The following table shows the types of standard collection classes available in most object-oriented languages:

Collection NameProperties
SetAn unordered set of elements. All elements are unique.
BagAn unordered set of elements. May include duplicate entries.
StackAn ordered Last-In First-Out (LIFO) list.
QueueAn ordered First-In First-Out (FIFO) list.
Linked ListAn ordered list optimized for insertion and deletion. May be doubly linked.
RingA variant on the linked list where the end of the list points to the beginning of the list.
List/ArrayAn ordered list optimized for random access.
Map or DictionaryAn unordered set of elements where access to each element is provided through a key.
TreeAn ordered set of elements whose structure is represented using a Tree.

Figure 4 shows an object model for the class diagram shown in figure 3. You'll notice that the CarManager object has a reference to an ArrayList object and the ArrayList object is responsible for maintaining the list of cars. Code snippet 3 shows a partial implementation of the CarManager Class. The CarManager delegates the management of the car references to the ArrayList object. In this specific case, the navigability between the CarManager and Car classes is uni-directional; the Car objects need not maintain a reference to the CarManager object.

Figure 4: Object model of classes shown in Figure 3.

Code Snippet 3:

import java.util.*;

public class CarManager
{
private ArrayList theCars = new ArrayList();

public void addCar(String make, String model, String serialNumber)
{
theCars.add(new Car(make, model, serialNumber));
}

public Car getCar(int index)
{
return (Car)theCars.get(index);
}

// ...
}

Bi-directional Many-to-Many relationships

The most difficult and complex relationships to implement are bi-directional many-to-many relationships. There are two main forms for implementing these relationships:
  • Implement a One-To-Many relationship in both directions using collection objects
  • Implement the association using an association object.
Figure 5 shows a class diagram. The following sections will show the implementation of the class model shown in figure 4 using each of the above methods

Figure 5: Bi-directional many-to-many relationship.

Using collection objects

This section will show the implementation of the enrollment of students in courses using standard collection classes. In this case, each student can enroll in more than one course. Therefore, each student object maintains a reference to a collection object which maintains references to the courses within which the student is enrolled. Similarly, each course has multiple students enrolled. Therefore, each course object maintains a reference to a collection object which maintains references to the students which are enrolled within it. Figure 6 attempts to show the object model. Because of the number of associations being maintained (each object maintains multiple references to other objects through collection objects) the object structure is very complex and it is difficult to maintain. Even in this simple case of three students and two courses the object structure is complicated.

There is also a problem with this design. When the student completes the course, where is the grade stored? The benefit of this structure is that information about the relationships is easy to obtain. For example, if you wish to obtain a list of students enrolled in a specific course, simply ask the course object for its list of enrolled students. If you want to obtain a list of all of the courses that a student is enrolled in, simply ask the student object for its list of enrollments. Code snippet 4 shows a partial implementation of this model

Figure 6: Bi-directional many-to-many relationship using collections.

Code Snippet 4:

public class Course
{
private ArrayList theStudents = new ArrayList();

public void enrollStudent(Student aStudent)
{
if (!theStudents.contains(aStudent))
{
theStudents.add(aStudent);
}
if (!aStudent.isEnrolled(this))
{
aStudent.enrollIn(this);
}
}

public boolean hasStudentEnrolled(Student aStudent)
{
return theStudents.contains(aStudent);
}

public List getEnrolledStudents()
{
return Collections.unmodifiableList(theStudents);
}
// ...
}

public class Student
{
private ArrayList enrolledCourses = new ArrayList();

public void enrollIn(Course aCourse)
{
if (!enrolledCourses.contains(aCourse))
{
enrolledCourses.add(aCourse);
}
if (!aCourse.hasStudentEnrolled(aStudent))
{
aCourse.enrollStudent(this);
}
}

public boolean isEnrolled(Course aCourse)
{
return enrolledCourses.contains(aCourse);
}

public List getEnrolledCourses()
{
return Collections.unmodifiableList(enrolledCourses);
}
}

Using association objects

Association objects are similar to association tables in a relational database. Rather than each of the Student and Course objects attempting to maintain one-to-many relationships, an object is created which maintains references to each side of the many-to-many list. Figure 7 illustrates the class model for this implementation and Figure 8 illustrates the object model. Code snippet 5 shows a partial implementation of the Assocation class Enrollment.

Figure 7: Association class managing student enrollment in courses

Figure 8: The object model for the classes defined in figure 5

Code Snippet 5:

public class Enrollment
{
private Student theStudent;
private Course theCourse;

private Grade finalGrade;

public Enrollment(Student aStudent, Course aCourse)
{
theStudent = aStudent;
theCourse = aCourse;
}

public Course getCourse()
{
return theCourse;
}

public Student getStudent()
{
return theStudent;
}

public void assignGrade(Grade aGrade)
{
finalGrade = aGrade;
}
}