© Jennifer M. Kohnke
Everything which is in any way beautiful is beautiful in itself, and terminates in itself, not having praise as part of itself.
—Marcus Aurelius, circa A.D. 170
The following case study describes the first iteration in the development of a simple batch payroll system. You will find the user stories in this case study to be simplistic. For example, taxes are simply not mentioned. This is typical of an early iteration. It will provide only a very small part of the business value the customers need.
In this chapter, we do the kind of quick analysis and design session that often takes place at the start of a normal iteration. The customer has selected the stories for the iteration, and now we have to figure out how we are going to implement them. Such design sessions are short and cursory, just like this chapter. The UML diagrams you see here are no more than hasty sketches on a whiteboard. The real design work will take place in the next chapter, when we work through the unit tests and implementations.
Following are some notes we took while conversing with our customer about the stories that were selected for the first iteration.
• Some employees work by the hour. They are paid an hourly rate that is one of the fields in their employee record. They submit daily time cards that record the date and the number of hours worked. If they work more than 8 hours per day, they are paid 1.5 times their normal rate for those extra hours. They are paid every Friday.
• Some employees are paid a flat salary. They are paid on the last working day of the month. Their monthly salary is one of the fields in their employee record.
• Some of the salaried employees are also paid a commission based on their sales. They submit sales receipts that record the date and the amount of the sale. Their commission rate is a field in their employee record. They are paid every other Friday.
• Employees can select their method of payment. They may have their paychecks mailed to the postal address of their choice, have their paychecks held for pickup by the paymaster, or request that their paychecks be directly deposited into the bank account of their choice.
• Some employees belong to the union. Their employee record has a field for the weekly dues rate. Their dues must be deducted from their pay. Also, the union may assess service charges against individual union members from time to time. These service charges are submitted by the union on a weekly basis and must be deducted from the appropriate employee’s next pay amount.
• The payroll application will run once each working day and pay the appropriate employees on that day. The system will be told what date the employees are to be paid to, so it will generate payments for records from the last time the employee was paid up to the specified date.
We could begin by generating the database schema. Clearly, this problem calls for some kind of relational database, and the requirements give us a very good idea of what the tables and fields might be. It would be easy to design a workable schema and then start building some queries. However, this approach will generate an application for which the database is the central concern.
Databases are implementation details! Consideration of the database should be deferred as long as possible. Far too many applications were designed with the database in mind from the beginning and so are inextricably tied to those databases. Remember the definition of abstraction: “the amplification of the essential and the elimination of the irrelevant.” At this stage of the project, the database is irrelevant; it is merely a technique used for storing and accessing data, nothing more.
Instead of starting with the data of the system, let’s start by considering the behavior of the system. After all, it is the system’s behavior that we are being paid to create.
One way to capture and analyze the behavior of a system is to create use cases. As originally described by Jacobson, use cases are very similar to the notion of user stories in XP.1 A use case is like a user story that has been elaborated with a little more detail. Such elaboration is appropriate once the user story has been selected for implementation in the current iteration.
When we perform use case analysis, we look to the user stories and acceptance tests to find out the kinds of stimuli that the users of this system provide. Then we try to figure out how the system responds to those stimuli. For example, here are the user stories that our customer has chosen for the next iteration:
Let’s convert each of these user stories into an elaborated use case. We don’t need to go into too much detail: just enough to help us think through the design of the code that fulfills each story.
A new employee is added by the receipt of an AddEmp
transaction. This transaction contains the employee’s name, address, and assigned employee number. The transaction has three forms:
AddEmp <EmpID> "<name>" "<address>" H <hrly-rate>
AddEmp <EmpID> "<name>" "<address>" S <mtly-slry>
AddEmp <EmpID> "<name>" "<address>" C <mtly-slry> <com-rate>
The employee record is created with its fields assigned appropriately.
If the transaction structure is inappropriate, it is printed out in an error message, and no action is taken.
Use case 1 hints at an abstraction. The AddEmp
transaction has three forms, all of which share the <EmpID>
, <name>
, and <address>
fields. We can use the COMMAND pattern to create an AddEmployeeTransaction
abstract base class with three derivatives: AddHourlyEmployeeTransaction
, AddSalariedEmployeeTransaction
, and AddCommissionedEmployeeTransaction
(see Figure 26-1).
This structure conforms nicely to the Single-Responsibility Principle (SRP) by splitting each job into its own class. The alternative would be to put all these jobs into a single module. Although doing so might reduce the number of classes in the system and therefore make the system simpler, it would also concentrate all the transaction-processing code in one place, creating a large and potentially error-prone module.
Use case 1 specifically talks about an employee record, which implies some sort of database. Again. our predisposition to databases may tempt us into thinking about record layouts or the field structure in a relational database table, but we should resist these urges. What the use case is really asking us to do is create an employee. What is the object model of an employee? A better question might be: What do the three transactions create? In my view, they create three kinds of employee objects, mimicking the three kinds of AddEmp
transactions. Figure 26-2 shows a possible structure.
Use Case 2: Deleting an Employee
Employees are deleted when a DelEmp
transaction is received. The form of this transaction is as follows:
DelEmp <EmpID>
When this transaction is received, the appropriate employee record is deleted.
If the <EmpID>
field is not structured correctly or does not refer to a valid employee record, the transaction is printed with an error message, and no other action is taken.
Other than the obvious DeleteEmployeeTransaction
class, I’m not getting any particular insight from use case 2. Let’s move on.
On receipt of a TimeCard
transaction, the system will create a time card record and associate it with the appropriate employee record.
TimeCard <empid> <date> <hours>
The system will print an appropriate error message and take no further action.
The system will print an appropriate error message and take no further action.
This use case points out that some transactions apply only to certain kinds of employees, strengthening the idea that each kind should be represented by different classes. In this case, there is also an association implied between time cards and hourly employees. Figure 26-3 shows a possible static model for this association.
Use Case 4: Post a Sales Receipt
On receipt of the SalesReceipt
transaction, the system will create a new salesreceipt record and associate it with the appropriate commissioned employee.
SalesReceipt <EmpID> <date> <amount>
The system will print an appropriate error message and take no further action.
The system will print an appropriate error message and take no further action.
This use case is very similar to use case 3 and implies the structure shown in Figure 26-4.
Use Case 5: Post a Union Service Charge
On receipt of this transaction, the system will create a service-charge record and associate it with the appropriate union member.
ServiceCharge <memberID> <amount>
If the transaction is not well formed or if the <memberID>
does not refer to an existing union member, the transaction is printed with an appropriate error message.
This use case shows that union members are not accessed through employee IDs. The union maintains its own identification numbering scheme for union members. Thus, the system must be able to associate union members and employees. There are many ways to provide this kind of association, so to avoid being arbitrary, let’s defer this decision until later. Perhaps constraints from other parts of the system will force our hand one way or another.
One thing is certain. There is a direct association between union members and their service charges. Figure 26-5 shows a possible static model for this association.
Use Case 6: Changing Employee Details
Upon receipt of this transaction, the system will alter one of the details of the appropriate employee record. There are several possible variations to this transaction.
If the structure of the transaction is improper, <EmpID>
does not refer to a real employee, or <memberID>
already refers to a member, the system will print a suitable error and take no further action.
This use case is very revealing. It has told us all the employee aspects that must be changeable. The fact that we can change an employee from hourly to salaried means that the diagram in Figure 26-2 is certainly invalid. Instead, it would probably be more appropriate to use the STRATEGY pattern for calculating pay. The Employee
class could hold a strategy class named PaymentClassification
, as in Figure 26-6. This is an advantage because we can change the PaymentClassification
object without changing any other part of the Employee
object. When an hourly employee is changed to a salaried employee, the HourlyClassification
of the corresponding Employee
object is replaced with a SalariedClassification
object.
PaymentClassification
objects come in three varieties. The HourlyClassification
objects maintain the hourly rate and a list of TimeCard
objects. The Salaried-Classification
objects maintain the monthly salary figure. The Commissioned-Classification
objects maintain a monthly salary, a commission rate, and a list of SalesReceipt
objects.
The method of payment must also be changeable. Figure 26-6 implements this idea by using the STRATEGY pattern and deriving three kinds of PaymentMethod
classes. If the Employee
object contains a MailMethod
object, the corresponding employee will have paychecks mailed to the address recorded in the MailMethod
object. If the Employee
object contains a DirectMethod
object, the corresponding employee’s pay will be directly deposited into the bank account recorded in the DirectMethod
object. If the Employee
contains a HoldMethod
object, the corresponding employee’s paychecks will be sent to the paymaster to be held for pickup.
Finally, Figure 26-6 applies the NULL OBJECT pattern to union membership. Each Employee
object contains an Affiliation
object, which has two forms. If the Employee
contains a NoAffiliation
object, the corresponding employee’s pay is not adjusted by any organization other than the employer. However, if the Employee
object contains a UnionAffiliation
object, that employee must pay the dues and service charges that are recorded in that UnionAffiliation
object.
This use of these patterns makes this system conform well to the Open/Closed Principle (OCP). The Employee
class is closed against changes in payment method, payment classification, and union affiliation. New methods, classifications, and affiliations can be added to the system without affecting Employee
.
Figure 26-6 is becoming our core model, or architecture. It’s at the heart of everything that the payroll system does. There will be many other classes and designs in the payroll application, but they will all be secondary to this fundamental structure. Of course, this structure is not cast in stone. We will be modifying it along with everything else.
Use Case 7: Run the Payroll for Today
On receipt of the payday transaction, the system finds all those employees that should be paid on the specified date. The system then determines how much they are owed and pays them according to their selected payment method. An audit-trail report is printed showing the action taken for each employee.
Payday <date>
Although it is easy to understand the intent of this use case, it is not so simple to determine what impact it has on the static structure of Figure 26-6. We need to answer several questions.
First, how does the Employee
object know how to calculate its pay? Certainly, the system must tally up an hourly employee’s time cards and multiply by the hourly rate. Similarly, the system must tally up a commissioned employee’s sales receipts, multiply by the commission rate, and add the base salary. But where does this get done? The ideal place seems to be in the PaymentClassification
derivatives. These objects maintain the records needed to calculate pay, so they should probably have the methods for determining pay. Figure 26-7 shows a collaboration diagram that describes how this might work.
When asked to calculate pay, the Employee
object refers this request to its PaymentClassification
object. The algorithm used depends on the type of PaymentClassification
that the Employee
object contains. Figures 26-8 through 26-10 show the three possible scenarios.
So far, we have learned that a simple use case analysis can provide a wealth of information and insights into the design of a system. Figures 26-6 through 26-10 resulted from thinking about the use cases, that is, thinking about behavior.
To use the OCP effectively, we must hunt for abstractions and find those that underlie the application. Often, these abstractions are not stated or even alluded to by the requirements of the application or even the use cases. Requirements and use cases may be too steeped in details to express the generalities of the underlying abstractions.
Let’s look again at the requirements. We see statements like this: “Some employees work by the hour” and “Some employees are paid a flat salary” and “Some . . . employees are paid a commission.” This hints at the following generalization: All employees are paid, but they are paid by different schemes. The abstraction here is that all employees are paid. Our model of the PaymentClassification
in Figures 26-7 through 26-10 expresses this abstraction nicely. Thus, this abstraction has already been found among our user stories by doing a very simple use case analysis.
Looking for other abstractions, we find “They are paid every Friday,” “They are paid on the last working day of the month,” and “They are paid every other Friday.” This leads us to another generality: All employees are paid according to a schedule. The abstraction here is the notion of the schedule. It should be possible to ask an Employee
object whether a certain date is its payday. The use cases barely mention this. The requirements associate an employee’s schedule and payment classification. Specifically, hourly employees are paid weekly, salaried employees are paid monthly, and employees receiving commissions are paid biweekly; however, is this association essential? Might not the policy change one day, so that employees could select a particular schedule or employees belonging to different departments or different divisions could have different schedules? Might not schedule policy change independent of payment policy? Certainly, this seems likely.
If, as the requirements imply, we delegated the issue of schedule to the Payment-Classification
class, our class could not be closed against issues of change in schedule. When we changed payment policy, we would also have to test schedule; when we changed schedules, we would also have to test payment policy. Both OCP and SRP would be violated.
An association between schedule and payment policy could lead to bugs in which a change to a particular payment policy caused incorrect scheduling of certain employees. Bugs like this may make sense to programmers, but they strike fear in the hearts of managers and users. They fear, and rightly so, that if schedules can be broken by a change to payment policy, any change made anywhere might cause problems in any other unrelated part of the system. They fear that they cannot predict the effects of a change. When effects cannot be predicted, confidence is lost, and the program assumes the status of “dangerous and unstable” in the minds of its managers and users.
Despite the essential nature of the schedule abstraction, our use case analysis failed to give us any direct clues about its existence. To spot it required careful consideration of the requirements and an insight into the wiles of the user community. Overreliance on tools and procedures and underreliance on intelligence and experience are recipes for disaster.
Figures 26-11 and 26-12 show the static and dynamic models for the schedule abstraction. As you can see, we’ve used the STRATEGY pattern yet again. The Employee
class contains the abstract PaymentSchedule
class. The three varieties of PaymentSchedule
correspond to the three known schedules by which employees are paid.
Another generalization we can make from the requirements is that all employees receive their pay by some method. The abstraction is the PaymentMethod
class. Interestingly enough, this abstraction is already expressed in Figure 26-6.
The requirements imply that employees may have affiliations with a union; however, the union may not be the only organization that has a claim to some of an employee’s pay. Employees might want to make automatic contributions to certain charities or have their dues to professional associations paid automatically. The generalization therefore becomes that the employee may be affiliated with many organizations that should be automatically paid from the employee’s paycheck.
The corresponding abstraction is the Affiliation
class that is shown in Figure 26-6. That figure, however, does not show the Employee
containing more than one Affiliation
, and it shows the presence of a NoAffiliation
class. This design does not quite fit the abstraction we now think we need. Figures 26-13 and 26-14 show the static and dynamic models that represent the Affiliation
abstraction.
The list of Affiliation
objects has obviated the need to use the NULL OBJECT pattern for unaffiliated employees. Now, the list of affiliations for an employee who has no affiliation will simply be empty.
This is a good start on a design. By elaborating the user stories into use cases and hunting through those use cases for abstractions, we’ve created a shape for the system. An archicture is burgeoning. Note, however, that this architecture has been created by looking at only the first few user stories. We did not do a comprehensive review of every requirement in the system. Nor did we demand that every user story and use case be perfect. We also did not do an exhaustive design of the system, complete with class and sequence diagrams for every jot and title that we could think of.
Thinking about design is important. Thinking about design in small, incremental steps is critical. Doing too much is worse than doing too little. In this chapter, the amount we did was just about right. It feels unfinished, but it’s enough for us to understand and make progress with.
[Jacobson92] Ivar Jacobson, Object-Oriented Software Engineering: A Use Case Driven Approach, Addison-Wesley, 1992.
3.141.8.247