Object Design

At this point, we've gathered the key requirements for our application from the use cases. Based on these use cases, we can create an object-oriented design. There are a variety of techniques used in object-oriented design (you may have heard of CRC cards, decomposition, in addition to others), and in this chapter we'll use a form of decomposition: identifying the "nouns" in the use cases, and then narrowing down which of these are actual business objects.

Initial Design

The first step in the process, then, is to assemble a list of the nouns in the use case write-ups. By using a bit of judgment, we can eliminate a few nouns that are obviously not objects, but we'll still end up with a good- sized list of potential business objects or entities as shown in Table 6-1.

Table 6-1: Potential Entities Discovered in the Initial Design

Project manager

Project

Project number

Project name

Start date

End date

Administrator

List of projects

Employee

Resource

Employee name

Employee ID

Supervisor

List of assignments

Role

List of roles

Assignment

Date assigned

List of resources

List of assigned resources

 

Using our understanding of the business domain (and probably through further discussion with business users and our fellow designers), we can narrow down the options. Some of these aren't objects, but rather data elements, or security roles. These include the following:

  • Project manager

  • Administrator

  • Supervisor

Tip  

This implies that there's already an object to deal with a user 's role, and indeed there is: It's the BusinessPrincipal object in the CSLA .NET Framework. These security roles should not be confused with the role a resource (person) plays on a project ”they're two very different concepts.

Pulling out these nouns, along with those that are likely to be just data fields (such as project name and employee ID), we come up with a smaller list of likely business objects as shown in Table 6-2.

Table 6-2: Refined List of Possible Entities in the Design

Object

Description

Project

An object representing a project in the system

Resource

An object representing a resource (person) in the system

Employee

An object representing an employee in the company

List of projects

A list of the projects in the system

List of resources

A list of the resources in the system

List of assigned resources

A list of the resources assigned to a specific project

List of assignments

A list of the projects to which a resource is assigned

List of roles

A list of the roles a resource (person) might fill on a project

Role

A role that a resource (person) might fill on a project

At this point, we can start creating a basic class diagram in UML. In this diagram, we'll include our potential business objects, along with the data elements that we think belong to each. The diagram should also include relationships between the entities in the diagram. For the most part, these relationships can be inferred from the use-case descriptions ”for instance, we can infer that a "List of projects" will likely contain Project objects; and that a Project object will likely contain a "List of assigned resources," which in turn will likely contain Resource objects.

Note that I use the word likely here, rather than will . We're still very much in a fluid design stage here, so nothing is yet certain. We have a list of potential objects, and we're inferring a list of potential relationships.

As we create the diagram, we'll assign some rather more formal names to our potential objects. Table 6-3 shows this list.

Table 6-3: Potential Objects and Their Associated Class Names

Potential Object

Class Name

Project

Project

Resource

Resource

Employee

Employee

List of projects

ProjectList

List of resources

ResourceList

List of assigned resources

ProjectResources

List of assignments

ResourceAssignments

List of roles

RoleList

Role

Role

And the resulting diagram might then appear something like Figure 6-1.

image from book
Figure 6-1: Possible class diagram for the ProjectTracker application

When we look at this diagram, we see some indication that we have more work to do. There are several issues that we should look for and address, including duplicate objects, trivial objects, objects that have overly complex relationships in the diagram, and places where we can optimize for performance.

Revising the Design

The following list indicates some of the things we need to address:

  • Resource is-a Employee , but Resource adds nothing to Employee , so the two can probably be merged into one class.

  • The RoleList and Role classes aren't used by any other classes. Obviously, they're required by our use cases, but it isn't clear which class should use them in the diagram as it stands.

  • The relationship between Project , ProjectResources , Resource , and ResourceAssignments is very complex. In fact, it's a loop of containment, which is always a danger sign.

  • The use cases for ProjectList and ResourceList indicate that they're primarily used for selection of objects, not for editing all the projects or resources in the system. Actually loading all the Project or Resource objects just so that the user can make a simple selection is expensive performancewise, so this design should be reviewed.

In the early stages of any object design process there will be duplicate objects, or potential objects that end up being mere data fields in other objects. Usually, a great deal of debate will ensue during the design phase as all the people involved in the design process thrash out which objects are real, which are duplicates, and which should be just data fields. This is healthy and important, though obviously some judgment must be exercised to avoid analysis paralysis , whereby the design stalls entirely due to the debate.

Let's discuss this in a bit more detail.

Duplicate Objects

First, we can identify duplicate objects ”like Resource and Employee ”that have basically the same data and relationships. Let's eliminate Employee in favor of Resource , since that's the term used most often in our use-case descriptions (and thus, presumably, most used by our end users).

In most scenarios, our end users will have numerous terms for some of their concepts. It's our job, as part of the analysis process, to identify when multiple terms really refer to the same concepts (objects) and to clarify and abstract the appropriate meaning.

Trivial Objects

The Role object may not be required either. Fundamentally, a Role is just a string value, which means that we can use the NameValueList class from the CSLA .NET Framework to create a RoleList object that maintains a list of the values.

Tip  

This is based on the use cases we assembled earlier. If we intuitively feel that this is overly simplistic or unrealistic , then we should revisit the use cases and our users to make sure that we didn't miss something. For the purposes of this book, we'll assume that the use cases are accurate, and that the Role field really is a simple string value.

Like the process of removing duplicates, the process of finding and removing trivial objects is as much an art as it is a science. It can be the cause of plenty of healthy debate!

Overly Complex Relationships

Although it's certainly true that large and complex applications often have complex relationships between classes and objects, those complex relationships should always be carefully reviewed.

As a general rule, if we have relationship lines crossing each other or wrapping around each other in our UML diagram, we should review those relationships to see if they need to be so difficult. Sometimes, it's just the way things have to be, but often it's a sign that our design needs some work. Though relying on the aesthetics of our diagram may sound a bit odd, it's a good rule of thumb.

In our case, we have a pretty complex relationship between Project , ProjectResources , Resource , and ResourceAssignments . What we have, in fact, is a circular containment relationship, where all these objects contain the other objects in an endless chain. In a situation like this, we should always be looking for relationships that should be using , instead of containing . What we'll often find is that we're missing a class in our diagram ”one that doesn't necessarily flow directly from the use cases, but is required to make the object model workable .

The specific problem we're trying to deal with is that when we load an object from the database, it will typically also load any child objects it contains ”containment relationships will be followed in order to do the data loading. If we have an endless loop of containment relationships, that poses a rather obvious problem! We need some way to short-circuit the process, and the best way to do this is to introduce a using relationship into the mix. Typically, we won't follow a using relationship as we load objects.

In our case, what's missing is a class that actually represents the assignment of a resource to a project. There's data described in the use cases that we don't have in our object model, such as the role of a resource on a particular project, or the date that the resource was assigned to a project. We can't keep these data fields in the Project , because a Project will have many resources filling many different roles at different times. Similarly, we can't keep these data fields in the Resource , because a Resource may be assigned to many projects at different times and in different roles.

Adding an Assignment Class

What we're discovering here is the need for another class in which to store this data: Assignment . If we redraw our UML to include this class as shown in Figure 6-2, things are a bit different.

image from book
Figure 6-2: Revised class diagram for the ProjectTracker application

With this change, we've replaced the containment relationships with using relationships, whereby the new Assignment class uses both the Project and Resource classes. The Assignment class now also contains our missing data fields: the role and date when the resource was assigned to a project.

However, we're still not done. The Assignment class itself just became overly complex, because it's used within two different contexts: from our list of resources assigned to a project, and from the list of projects to which a resource is assigned. This is typically problematic . Having a single object as a child of two different collections makes for very complicated implementation and testing, and should be avoided where possible.

Looking at the diagram, we're also unable to tell when the Assignment class is using Project , and when it's using Resource . Based on our knowledge of the use cases and our overall design, we know that when a Project contains a list of Assignment objects, the Assignment will use a Resource to get data about that resource. Likewise, when a Resource contains a list of Assignment objects, the Assignment will use a Project to get at project data. But that's not clear from the diagram, and that's not good.

There are two possible solutions. We can combine the list objects into a single list of assignments, or we can have two different Assignment objects. To resolve this, we need to think about what we want to accomplish when approaching the object from Project , and from a Resource .

Assignment from a Project Perspective

When we list the resources assigned to a project, we'll want to include some useful information, some of which will come from the Resource itself. However, it's highly inefficient to load an entire Resource object just to get at its ID and Name properties. It might seem as though the answer to this is to include these fields as part of the other object ”in other words, we could include the Resource 's ID and Name fields in Assignment ”but the problem with that approach is that the business logic associated with those fields exists in the object that owns them.

The logic governing the ID and Name data for a resource is in the Resource class. If we allow users to edit and manipulate those fields in the Assignment object, we would need to replicate that business logic ”and that's unacceptable.

An alternative is to " borrow " the data values, making them read-only fields on other objects. In our Assignment object, we can borrow the Resource 's ID and Name fields, exposing them as read-only properties. We don't need to replicate the business logic governing the editing and manipulation of these fields, because we don't allow the fields to be altered . If the user wants to alter or edit the fields, they can do so through the Resource object that owns those fields.

Tip  

The exception here is field-level security. If our application uses role-based security to restrict who can view certain data elements, we'll have to replicate that business logic anywhere the data fields are made publicly available. If we borrow a data field for display purposes, we'll also have to replicate the business logic that checks to see if the field should be visible. In our particular example, we don't have such field-level security requirements, so that's not an issue here.

Borrowing fields is important for performance, and in most applications it doesn't require replication of business logic across classes. Although not ideal from an object-oriented design perspective, it's a compromise that's often necessary to make our applications work in the real world. Using it here would mean that the Assignment object needs to look something like what's shown in Figure 6-3.

Assignment

ˆ’ Date assigned

ˆ’ Role

+ResourceID (read-only)()

+ResourceName (read-only)()


Figure 6-3: Assignment class with borrowed data fields from Resource
Tip  

UML doesn't have any provision for indicating that a property method should be read-only, so I'm including it here as a notation.

When an Assignment is used from a Project perspective, we would borrow the Resource 's ID and Name values, so that it's quickly and easily apparent which Resource has been assigned to the Project .

Assignment from a Resource Perspective

Similarly, when we list the projects to which a Resource is assigned, we'll want to list some information about the project. This means that the Assignment object would need to look something like what's shown in Figure 6-4.

Assignment

ˆ’ Date assigned

ˆ’ Role

+ProjectID (read-only)()

+ProjectName (read-only)()


Figure 6-4: Assignment class with borrowed data fields from Project

Again, we're borrowing fields from another object ”a Project , in this case ” for display purposes within our Assignment object.

A Combined Assignment Class

At this stage, we could merge these two Assignment classes (from the Project and Resource perspectives) into a single class as shown in Figure 6-5.

Assignment

ˆ’ Date assigned

ˆ’ Rate

+ProjectID (read-only)()

+ProjectName (read-only)()

+ResourceID (read-only)()

+ResourceName (read-only)()


Figure 6-5: Assignment class with combined set of borrowed fields

The drawback to this approach, however, is that we'd have to populate the fields in this object even when they weren't needed. For instance, when accessing this object from a project, we'd still populate the ProjectID and ProjectName property values ”a total waste, because we already have a Project object!

If it doesn't make sense to consolidate these two types of Assignment , then we can't consolidate the list objects. Instead, we need to create two different types of Assignment object. Both represent an assignment of some kind, but each will have some different information.

This is a classic inheritance scenario, since both of our objects will have some data and behaviors in common. The end result is illustrated in Figure 6-6.

image from book
Figure 6-6: Using inheritance to manage Assignment data

Our two new classes each inherit from Assignment , so they contain the core data that makes up an assignment. However, each of them provides read-only data as appropriate to support our application's requirements. The result of this design work is shown in Figure 6-7.

image from book
Figure 6-7: Revised class diagram for the ProjectTracker application

Notice that we've also added a GetResource() method to ProjectResource , and a GetProject() method to ResourceAssignment . These methods imply a using relationship between these classes and the classes from which they're borrowing data, which, as shown in Figure 6-8, resolves things nicely .

image from book
Figure 6-8: ProjectResource using Resource and ProjectAssignment using Project

Our Project object now contains a collection of ProjectResource objects, each of which is useful by itself since it contains the date the assignment was made, the role of the resource, and the resource's ID and Name values. If we need more information about the resource, we can use the GetResource() method to get a real Resource object that will contain all the details.

Likewise, our Resource object now contains a collection of ResourceAssignment objects. Each of these objects contains useful information, and if we need more details about the project itself, we can use the GetProject() method to get a real Project object with all the data.

We no longer have a circular containment relationship, and our new child objects should have good performance since they're borrowing the most commonly used fields for display purposes.

Optimizing for Performance

As part of our object design, we need to review things to ensure that we haven't created a model that will lead to poor performance. This isn't really a single step in the process, as much as something that should be done on a continual basis during the whole process. However, once we think the model is complete, we should always pause to review it for performance issues.

We've already done some performance optimization as the model has evolved. Specifically, we used a field-borrowing technique to take common display fields from Project and Resource and put them into the ResourceAssignment and ProjectResource classes. This saves us from having to load entire business objects just to display a couple of common data elements.

We do, however, have another performance issue in our model. Our ProjectList and ResourceList collection objects, as modeled , retrieve collections of Project and Resource business objects so that some of their data can be displayed in a list. Based on our use cases, the user then selects one of the objects and chooses to view, edit, or remove that object.

From a purely object-oriented perspective, it's attractive to think that we actually could just load a collection of Project objects and allow the user to pick the one he wants to edit. However, this could be very expensive, because it means loading all the data for every Project object, including each project's list of assigned resources, and so forth. As the user adds, edits, and removes Project objects, we would potentially have to maintain our collection in memory too.

Practical performance issues mean that we're better off creating a read-only collection that contains only the information needed to create the user interface. (This is one of the primary reasons why CSLA .NET includes the ReadOnlyCollectionBase class, which makes it very easy to create such objects.) We can create ProjectList and ResourceList collection objects that contain only the data to be displayed, in read-only format. Essentially, this is a variation on the field-borrowing concept: We're creating collection objects that consist entirely of borrowed data. None of this data is editable, and so we typically won't have to replicate any business logic as part of this process.

Tip  

As before, the exception is where we have business rules governing who can see specific data fields. In that case, we'll have to replace that field-level security business logic into our read-only collection objects.

We'll need two read-only collection objects, each with its own structure for the data as shown in Figure 6-9.

image from book
Figure 6-9: Read-only collection objects ProjectList and ResourceList

The ProjectInfo structure contains fields borrowed from the Project class, while ResourceInfo contains fields borrowed from Resource .

Reviewing the Design

The final step in the object design process is to compare our new class diagram with our use-case descriptions in order to ensure that everything described in each use case can be accomplished through the use of these objects. In doing so, we'll help to ensure that our object model covers all the user requirements. Our complete object model looks like Figure 6-10.

image from book
Figure 6-10: Final class diagram for the ProjectTracker application

If we review our use cases, we should find that we'll be able to use our objects to accomplish all of the tasks and processes described here.

  • Users can get a list of projects.

  • Users can add a project.

  • Users can edit a project.

  • Users can remove a project.

  • Users can get a list of resources.

  • Users can add a resource.

  • Users can edit a resource.

  • Users can remove a resource.

  • Users can assign a resource to a project (or vice versa).

  • When a resource is assigned to a project, users can specify the role the resource will play on the project.

At this point, we can consider our object model to be complete.



Expert C# Business Objects
Expert C# 2008 Business Objects
ISBN: 1430210192
EAN: 2147483647
Year: 2006
Pages: 111
Authors: Rockford Lhotka
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net