27. The Payroll Case Study: Implementation

image

© Jennifer M. Kohnke

It’s long past time we started writing the code that supports and verifies the designs we’ve been spinning. I’ll be creating that code in very small, incremental steps, but I’ll show it to you only at convenient points in the text. Don’t let the fact that you see only fully formed snapshots of code mislead you into thinking that I wrote it in that form. In fact, between each batch of code you see, there will have been dozens of edits, compiles, and test cases, each one making a tiny, evolutionary change in the code.

You’ll also see quite a bit of UML. Think of this UML as a quick diagram that I sketch on a whiteboard to show you, my pair partner, what I have in mind. UML makes a convenient medium for us to communicate by.

Transactions

We begin by thinking about the transactions that represent the use cases. Figure 27-1 shows that we represent transactions as an interface named Transaction, which has a method named Execute(). This is, of course, the COMMAND pattern. The implementation of the Transaction class is shown in Listing 27-1.

Figure 27-1. Transaction interface

image


Listing 27-1. Transaction.cs

namespace Payroll
{
  public interface Transaction
  {
    void Execute();
  }
}


Adding Employees

Figure 27-2 shows a potential structure for the transactions that add employees. Note that it is within these transactions that the employees’ payment schedule is associated with their payment classification. This is appropriate, since the transactions are contrivances instead of part of the core model. Thus, for example, the core model is unaware that hourly employess are paid weekly. The association between payment classificaton and payment schedule is merely part of one of the peripheral contrivances and can be changed at any time. For example, we could easily add a transaction that allows us to change employee schedules.

Figure 27-2. Static model of AddEmployeeTransaction

image

This decision conforms nicely to OCP and SRP. It is the responsibility of the transactions, not the core model, to specify the association between payment type and payment schedule. What’s more, that association can be changed without changing the core model.

Note, too, that the default payment method is to hold the paycheck with the paymaster. If an employee wants a different payment method, it must be changed with the appropriate ChgEmp transaction.

As usual, we begin writing code by writing tests first. The test case in Listing 27-2 shows that the AddSalariedTransaction is working correctly. The code to follow will make that test case pass.


Listing 27-2. PayrollTest.TestAddSalariedEmployee

[Test]
public void TestAddSalariedEmployee()
{
  int empId = 1;
  AddSalariedEmployee t =
    new AddSalariedEmployee(empId, "Bob", "Home", 1000.00);
  t.Execute();

  Employee e = PayrollDatabase.GetEmployee(empId);
  Assert.AreEqual("Bob", e.Name);

  PaymentClassification pc = e.Classification;
  Assert.IsTrue(pc is SalariedClassification);
  SalariedClassification sc = pc as SalariedClassification;

  Assert.AreEqual(1000.00, sc.Salary, .001);
  PaymentSchedule ps = e.Schedule;
  Assert.IsTrue(ps is MonthlySchedule);

  PaymentMethod pm = e.Method;
  Assert.IsTrue(pm is HoldMethod);
}


The payroll database

The AddEmployeeTransaction class uses a class called PayrollDatabase. For the moment, this class maintains all the existing Employee objects in a Hashtable that is keyed by empID. The class also maintains a Hashtable that maps union memberIDs to empIDs. We’ll figure out how to make the contents persistent later. The structure for this class appears in Figure 27-3. PayrollDatabase is an example of the FACADE pattern.

Figure 27-3. Static structure of PayrollDatabase

image

Listing 27-3 shows a rudimentary implementation of the PayrollDatabase. This implementation is meant to help us with our initial test cases. It does not yet contain the hash table that maps member IDs to Employee instances.


Listing 27-3. PayrollDatabase.cs

using System.Collections;

namespace Payroll
{
  public class PayrollDatabase
  {
    private static Hashtable employees = new Hashtable();

    public static void AddEmployee(int id, Employee employee)
    {
      employees[id] = employee;
    }

    public static Employee GetEmployee(int id)
    {
      return employees[id] as Employee;
    }
  }
}


In general, I consider database implementations to be details. Decisions about those details should be deferred as long as possible. Whether this particular database will be implemented with a relational database management system (RDBMS), or flat files, or an object-oriented database management system (OODBMS), is irrelevant at this point. Right now, I’m simply interested in creating the API that will provide database services to the rest of the application. I’ll find appropriate implementations for the database later.

Deferring details about the database is an uncommon but very rewarding practice. Database decisions can usually wait until we have much more knowledge about the software and its needs. By waiting, we avoid the problem of putting too much infrastructure into the database. Rather, we implement only enough database facility for the current needs of the application.

Using Template Method to add employees

Figure 27-4 shows the dynamic model for adding an employee. Note that the AddEmployeeTransaction object sends messages to itself in order to get the appropriate PaymentClassification and PaymentSchedule objects. These messages are implemented in the derivatives of the AddEmployeeTransaction class. This is an application of the TEMPLATE METHOD pattern.

Figure 27-4. Dynamic model for adding an employee

image

Listing 27-4 shows the implementation of the TEMPLATE METHOD pattern in the AddEmployeeTransaction class. This class implements the Execute() method to call two pure virtual functions that will be implemented by derivatives. These functions, MakeSchedule() and MakeClassification(), return the PaymentSchedule and PaymentClassification objects that the newly created Employee needs. The Execute() method then binds these objects to the Employee and saves the Employee in the PayrollDatabase.

Two things are of particular interest here. First, when the TEMPLATE METHOD pattern is applied, as it is here, for the sole purpose of creating objects, it goes by the name FACTORY METHOD. Second, it is conventional for the creation methods in the FACTORY METHOD pattern to be named MakeXXX(). I realized both of these issues while I was writing the code, and that is why the method names differ between the code and the diagram.

Should I have gone back and changed the diagram? I didn’t see the need in this case. I don’t intend for that diagram to be used as a reference by anyone else. Indeed, if this were a real project, that diagram would have been drawn on a whiteboard and would probably now be on the verge of being erased.


Listing 27-4. AddEmployeeTransaction.cs

namespace Payroll
{
  public abstract class AddEmployeeTransaction : Transaction
  {
    private readonly int empid;
    private readonly string name;
    private readonly string address;

    public AddEmployeeTransaction(int empid,
      string name, string address)
    {
      this.empid = empid;
      this.name = name;
      this.address = address;
    }

    protected abstract
      PaymentClassification MakeClassification();
    protected abstract
      PaymentSchedule MakeSchedule();

    public void Execute()
    {
      PaymentClassification pc = MakeClassification();
      PaymentSchedule ps = MakeSchedule();
      PaymentMethod pm = new HoldMethod();

      Employee e = new Employee(empid, name, address);
      e.Classification = pc;
      e.Schedule = ps;
      e.Method = pm;
      PayrollDatabase.AddEmployee(empid, e);
    }
  }
}


Listing 27-5 shows the implementation of the AddSalariedEmployee class. This class derives from AddEmployeeTransaction and implements the MakeSchedule() and MakeClassification() methods to pass back the appropriate objects to AddEmployeeTransaction.Execute().


Listing 27-5. AddSalariedEmployee.cs

namespace Payroll
{
  public class AddSalariedEmployee : AddEmployeeTransaction
  {
    private readonly double salary;

    public AddSalariedEmployee(int id, string name,
      string address, double salary)
      : base(id, name, address)
    {
      this.salary = salary;
    }

    protected override
      PaymentClassification MakeClassification()
    {
      return new SalariedClassification(salary);
    }

    protected override PaymentSchedule MakeSchedule()
    {
      return new MonthlySchedule();
    }
  }
}


The AddHourlyEmployee and AddCommissionedEmployee are left as exercises for you. Remember to write your test cases first.

Deleting Employees

Figures 27-5 and 27-6 present the static and dynamic models for the transactions that delete employees. Listing 27-6 shows the test case for deleting an employee. Listing 27-7 shows the implementation of DeleteEmployeeTransaction. This is a very typical implementation of the COMMAND pattern. The constructor stores the data that the Execute() method eventually operates on.

Figure 27-5. Static model for DeleteEmployee transaction

image

Figure 27-6. Dynamic model for DeleteEmployee transaction

image


Listing 27-6. PayrollTest.DeleteEmployee

[Test]
public void DeleteEmployee()
{
  int empId = 4;
  AddCommissionedEmployee t =
    new AddCommissionedEmployee(
    empId, "Bill", "Home", 2500, 3.2);
  t.Execute();

  Employee e = PayrollDatabase.GetEmployee(empId);
  Assert.IsNotNull(e);

  DeleteEmployeeTransaction dt =
    new DeleteEmployeeTransaction(empId);
  dt.Execute();

  e = PayrollDatabase.GetEmployee(empId);
  Assert.IsNull(e);
}



Listing 27-7. DeleteEmployeeTransaction.cs

namespace Payroll
{
  public class DeleteEmployeeTransaction : Transaction
  {
    private readonly int id;

    public DeleteEmployeeTransaction(int id)
    {
      this.id = id;
    }

    public void Execute()
    {
      PayrollDatabase.DeleteEmployee(id);
    }
  }
}


By now, you have noticed that the PayrollDatabase provides static access to its fields. In effect, PayrollDatabase.employees is a global variable. For decades, textbooks and teachers have been discouraging the use of global variables, with good reason. Still, global variables are not intrinsically evil or harmful. This particular situation is an ideal choice for a global variable. There will ever be only one instance of the PayrollDatabase methods and variables, and it needs to be known by a wide audience.

You might think that this could be better accomplished by using the SINGLETON or MONOSTATE patterns. It is true that these would serve the purpose. However, they do so by using global variables themselves. A SINGLETON or a MONOSTATE is, by definition, a global entity. In this case, I felt that a SINGLETON or a MONOSTATE would smell of needless complexity. It’s easier to simply keep the database global.

Time Cards, Sales Receipts, and Service Charges

Figure 27-7 shows the static structure for the transaction that posts time cards to employees. Figure 27-8 shows the dynamic model. The basic idea is that the transaction gets the Employee object from the PayrollDatabase, asks the Employee for its PaymentClassification object, and then creates and adds a TimeCard object to that PaymentClassification.

Figure 27-7. Static structure of TimeCardTransaction

image

Figure 27-8. Dynamic model for posting a TimeCard

image

Note that we cannot add TimeCard objects to general PaymentClassification objects; we can add them only to HourlyClassification objects. This implies that we must downcast the PaymentClassification object received from the Employee object to an HourlyClassification object. This is a good use for the as operator in C# (see Listing 27-10).

Listing 27-8 shows one of the test cases that verifies that time cards can be added to hourly employees. This test code simply creates an hourly employee and adds it to the database. Then it creates a TimeCardTransaction, invokes Execute(), and checks whether the employee’s HourlyClassification contains the appropriate TimeCard.


Listing 27-8. PayrollTest.TestTimeCardTransaction

[Test]
public void TestTimeCardTransaction()
{
  int empId = 5;
  AddHourlyEmployee t =
    new AddHourlyEmployee(empId, "Bill", "Home", 15.25);
  t.Execute();
  TimeCardTransaction tct =
    new TimeCardTransaction(
      new DateTime(2005, 7, 31), 8.0, empId);
  tct.Execute();

  Employee e = PayrollDatabase.GetEmployee(empId);
  Assert.IsNotNull(e);

  PaymentClassification pc = e.Classification;
  Assert.IsTrue(pc is HourlyClassification);
  HourlyClassification hc = pc as HourlyClassification;

  TimeCard tc = hc.GetTimeCard(new DateTime(2005, 7, 31));
  Assert.IsNotNull(tc);
  Assert.AreEqual(8.0, tc.Hours);
}


Listing 27-9 shows the implementation of the TimeCard class. There’s not much to this class right now. It’s simply a data class.


Listing 27-9. TimeCard.cs

using System;

namespace Payroll
{
  public class TimeCard
  {
    private readonly DateTime date;
    private readonly double hours;

    public TimeCard(DateTime date, double hours)
    {
      this.date = date;
      this.hours = hours;
    }

    public double Hours
    {
      get { return hours; }
    }

    public DateTime Date
    {
      get { return date; }
    }
  }
}


Listing 27-10 shows the implementation of the TimeCardTransaction class. Note the use of InvalidOperationExceptions. This is not particularly good long-term practice but suffices this early in development. After we get some idea of what the exceptions ought to be, we can come back and create meaningful exception classes.


Listing 27-10. TimeCardTransaction.cs

using System;

namespace Payroll
{
  public class TimeCardTransaction : Transaction
  {
    private readonly DateTime date;
    private readonly double hours;
    private readonly int empId;

    public TimeCardTransaction(
      DateTime date, double hours, int empId)
    {
      this.date = date;
      this.hours = hours;
      this.empId = empId;
    }

    public void Execute()
    {
      Employee e = PayrollDatabase.GetEmployee(empId);

      if (e != null)
      {
        HourlyClassification hc =
          e.Classification as HourlyClassification;

        if (hc != null)
          hc.AddTimeCard(new TimeCard(date, hours));
        else
          throw new InvalidOperationException(
            "Tried to add timecard to" +
              "non-hourly employee");
      }

        else
          throw new InvalidOperationException(
            "No such employee.");
    }
  }
}


Figures 27-9 and 27-10 show a similar design for the transaction that posts sales receipts to a commissioned employee. I’ve left the implementation of these classes as an exercise.

Figure 27-9. Static model for SalesReceiptTransaction

image

Figure 27-10. Dynamic model for SalesReceiptTransaction

image

Figures 27-11 and 27-12 show the design for the transaction that posts service charges to union members. These designs point out a mismatch between the transaction model and the core model that we have created. Our core Employee object can be affiliated with many different organizations, but the transaction model assumes that any affiliation must be a union affiliation. Thus, the transaction model provides no way to identify a particular kind of affiliation. Instead, it simply assumes that if we are posting a service charge, the employee has a union affiliation.

Figure 27-11. Static model for ServiceChargeTransaction

image

Figure 27-12. Dynamic model for ServiceChargeTransaction

image

The dynamic model addresses this dilemma by searching the set of Affiliation objects contained by the Employee object for a UnionAffiliation object. The model then adds the ServiceCharge object to that UnionAffiliation.

Listing 27-11 shows the test case for the ServiceChargeTransaction. It simply creates an hourly employee, adds a UnionAffiliation to it, makes sure that the appropriate member ID is registered with the PayrollDatabase, creates and executes a ServiceChargeTransaction, and, finally, makes sure that the appropriate ServiceCharge was indeed added to Employee’s UnionAffiliation.


Listing 27-11. PayrollTest.AddServiceCharge

[Test]
public void AddServiceCharge()
{
  int empId = 2;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.25);
  t.Execute();
  Employee e = PayrollDatabase.GetEmployee(empId);
  Assert.IsNotNull(e);
  UnionAffiliation af = new UnionAffiliation();
  e.Affiliation = af;
  int memberId = 86; // Maxwell Smart
  PayrollDatabase.AddUnionMember(memberId, e);
  ServiceChargeTransaction sct =
    new ServiceChargeTransaction(
    memberId, new DateTime(2005, 8, 8), 12.95);
  sct.Execute();
  ServiceCharge sc =
    af.GetServiceCharge(new DateTime(2005, 8, 8));
  Assert.IsNotNull(sc);
  Assert.AreEqual(12.95, sc.Amount, .001);
}


When I drew the UML in Figure 27-12, I thought that replacing NoAffiliation with a list of affiliations was a better design. I thought it was more flexible and less complex. After all, I could add new affiliations any time I wanted, and I didn’t have to create the NoAffiliation class. However, when writing the test case in Listing 27-11, I realized that setting the Affiliation property on Employee was better than calling AddAffiliation. After all, the requirements do not ask that an employee have more than one Affiliation, so there is no need to use a cast to select from potentially many kinds. Doing so would be more complex than necessary.

This is an example of why doing too much UML without verifying it in code can be dangerous. The code can tell you things about your design that the UML cannot. Here, I was putting structures into the UML that weren’t needed. Maybe one day they’d come in handy, but they have to be maintained between now and then. The cost of that maintenance may not be worth the benefit.

In this case, even though the cost of maintaining the downcast is relatively slight, I’m not going to use it; it’s much simpler to implement without a list of Affiliation objects. So I’ll keep the NULL OBJECT pattern in place with the NoAffiliation class.

Listing 27-12 shows the implementation of the ServiceChargeTransaction. It is indeed much simpler without the loop looking for UnionAffiliation objects. It simply gets the Employee from the database, downcasts its Affillation to a UnionAffilliation, and adds the ServiceCharge to it.


Listing 27-12. ServiceChargeTransaction.cs

using System;

namespace Payroll
{
  public class ServiceChargeTransaction : Transaction
  {
    private readonly int memberId;
    private readonly DateTime time;
    private readonly double charge;

    public ServiceChargeTransaction(
      int id, DateTime time, double charge)
    {
      this.memberId = id;
      this.time = time;
      this.charge = charge;
    }

    public void Execute()
    {
      Employee e = PayrollDatabase.GetUnionMember(memberId);

      if (e != null)
      {
        UnionAffiliation ua = null;
        if(e.Affiliation is UnionAffiliation)
          ua = e.Affiliation as UnionAffiliation;

        if (ua != null)
          ua.AddServiceCharge(
            new ServiceCharge(time, charge));
        else
          throw new InvalidOperationException(
            "Tries to add service charge to union"
            + "member without a union affiliation");
      }
      else
        throw new InvalidOperationException(
          "No such union member.");
    }
  }
}


Changing Employees

Figure 27-13 show the static structure for the transactions that change the attributes of an employee. This structure is easily derived from use case 6. All the transactions take an EmpID argument, so we can create a top-level base class called Change-EmployeeTransaction. Below this base class are the classes that change single attributes, such as ChangeNameTransaction and ChangeAddressTransaction. The transactions that change classifications have a commonality of purpose in that they all modify the same field of the Employee object. Thus, they can be grouped together under an abstract base, ChangeClassificationTransaction. The same is true of the transactions that change the payment and the affiliations. This can be seen by the structure of Change-MethodTransaction and ChangeAffiliationTransaction.

Figure 27-13. Static model for ChangeEmployeeTransaction

image

image

image

Figure 27-14 shows the dynamic model for all the change transactions. Again, we see the TEMPLATE METHOD pattern in use. In every case, the Employee object corresponding to the EmpID must be retrieved from the PayrollDatabase. Thus, the Execute function of ChangeEmployeeTransaction implements this behavior and then sends the Change message to itself. This method will be declared as virtual and implemented in the derivatives, as shown in Figures 27-15 and 27-16.

Figure 27-14. Dynamic model for ChangeEmployeeTransaction

image

Figure 27-15. Dynamic model for ChangeNameTransaction

image

Figure 27-16. Dynamic model for ChangeAddressTransaction

image

Listing 27-13 shows the test case for the ChangeNameTransaction. This simple test case uses the AddHourlyEmployee transaction to create an hourly employee named Bill. It then creates and executes a ChangeNameTransaction that should change the employee’s name to Bob. Finally, it fetches the Employee instance from the Payroll-Database and verifies that the name has been changed.


Listing 27-13. PayrollTest.TestChangeNameTransaction()

[Test]
public void TestChangeNameTransaction()
{
  int empId = 2;
  AddHourlyEmployee t =
    new AddHourlyEmployee(empId, "Bill", "Home", 15.25);
  t.Execute();
  ChangeNameTransaction cnt =
    new ChangeNameTransaction(empId, "Bob");
  cnt.Execute();
  Employee e = PayrollDatabase.GetEmployee(empId);
  Assert.IsNotNull(e);
  Assert.AreEqual("Bob", e.Name);
}


Listing 27-14 shows the implementation of the abstract base class ChangeEmployeeTransaction. The structure of the TEMPLATE METHOD pattern is clearly in evidence. The Execute() method simply reads the appropriate Employee instance from the PayrollDatabase and, if successful, invokes the abstract Change() method.


Listing 27-14. ChangeEmployeeTransaction.cs

using System;

namespace Payroll
{
  public abstract class ChangeEmployeeTransaction : Transaction
  {
    private readonly int empId;

    public ChangeEmployeeTransaction(int empId)
    {
      this.empId = empId;
    }

    public void Execute()
    {
      Employee e = PayrollDatabase.GetEmployee(empId);

      if(e != null)
        Change(e);
      else
        throw new InvalidOperationException(
          "No such employee.");
    }

    protected abstract void Change(Employee e);
  }
}


Listing 27-15 shows the implementation of the ChangeNameTransaction. The second half of the TEMPLATE METHOD can easily be seen. The Change() method is implemented to change the name of the Employee argument. The structure of the ChangeAddressTransaction is very similar and is left as an exercise.


Listing 27-15. ChangeNameTransaction.cs

namespace Payroll
{
  public class ChangeNameTransaction :
    ChangeEmployeeTransaction
  {
    private readonly string newName;

    public ChangeNameTransaction(int id, string newName)
    : base(id)
    {
      this.newName = newName;
    }

    protected override void Change(Employee e)
    {
      e.Name = newName;
    }
  }
}


Changing Classification

Figure 27-17 shows how the hierarchy beneath Change-ClassificationTransaction is envisioned. The TEMPLATE METHOD pattern is used yet again. All these transactions must create a new PaymentClassification object and then hand it to the Employee object. This is accomplished by sending the GetClassification message to itself. This abstract method is implemented in each of the classes derived from ChangeClassificationTransaction, as shown in Figures 27-18 through Figure 27-20.

Figure 27-17. Dynamic model of ChangeClassificationTransaction

image

Figure 27-18. Dynamic model of ChangeHourlyTransaction

image

Figure 27-19. Dynamic model of ChangeSalariedTransaction

image

Figure 27-20. Dynamic Model of ChangeCommissionedTransaction

image

Listing 27-16 shows the test case for the ChangeHourlyTransaction. The test case uses an AddCommissionedEmployee transaction to create a commissioned employee and then creates a ChangeHourlyTransaction and executes it. The transaction fetches the changed employee and verifies that its PaymentClassification is an Hourly-Classification with the appropriate hourly rate and that its PaymentSchedule is a WeeklySchedule.


Listing 27-16. PayrollTest.TestChangeHourlyTransaction()

[Test]
public void TestChangeHourlyTransaction()
{
  int empId = 3;
  AddCommissionedEmployee t =
    new AddCommissionedEmployee(
    empId, "Lance", "Home", 2500, 3.2);
  t.Execute();
  ChangeHourlyTransaction cht =
    new ChangeHourlyTransaction(empId, 27.52);
  cht.Execute();
  Employee e = PayrollDatabase.GetEmployee(empId);
  Assert.IsNotNull(e);
  PaymentClassification pc = e.Classification;
  Assert.IsNotNull(pc);
  Assert.IsTrue(pc is HourlyClassification);
  HourlyClassification hc = pc as HourlyClassification;
  Assert.AreEqual(27.52, hc.HourlyRate, .001);
  PaymentSchedule ps = e.Schedule;
  Assert.IsTrue(ps is WeeklySchedule);
}


Listing 27-17 shows the implementation of the abstract base class ChangeClassificationTransaction. Once again, the TEMPLATE METHOD pattern is easy to pick out. The Change() method invokes the two abstract getters for the, Classification and Schedule properties and uses the values from these properties to set the classification and schedule of the Employee.


Listing 27-17. ChangeClassificationTransaction.cs

namespace Payroll
{
  public abstract class ChangeClassificationTransaction
    : ChangeEmployeeTransaction
  {
    public ChangeClassificationTransaction(int id)
      : base (id)
    {}

    protected override void Change(Employee e)
    {
      e.Classification = Classification;
      e.Schedule = Schedule;
    }

    protected abstract
      PaymentClassification Classification { get; }
    protected abstract PaymentSchedule Schedule { get; }
  }
}


The decision to use properties instead of get functions was made as the code was being written. Again, we see the tension between the diagrams and the code.

Listing 27-18 shows the implementation of the ChangeHourlyTransaction class. This class completes the TEMPLATE METHOD pattern by implementing the getters for the Classification and Schedule properties that it inherited from Change-ClassificationTransaction. The class implements the Classification getter to return a newly created HourlyClassification and implements the Schedule getter to return a newly created WeeklySchedule.


Listing 27-18. ChangeHourlyTransaction.cs

namespace Payroll
{
  public class ChangeHourlyTransaction
    : ChangeClassificationTransaction
  {
    private readonly double hourlyRate;

    public ChangeHourlyTransaction(int id, double hourlyRate)
      : base(id)
    {
      this.hourlyRate = hourlyRate;
    }

    protected override PaymentClassification Classification
    {
      get { return new HourlyClassification(hourlyRate); }
    }

    protected override PaymentSchedule Schedule
    {
      get { return new WeeklySchedule(); }
    }
  }
}


As always, the ChangeSalariedTransaction and ChangeCommissionedTransaction are left as an exercise.

A similar mechanism is used for the implementation of ChangeMethod-Transaction. The abstract Method property is used to select the proper derivative of PaymentMethod, which is then handed to the Employee object (see Figures 27-21 through 27-24).

Figure 27-21. Dynamic model of ChangeMethodTransaction

image

Figure 27-22. Dynamic model of ChangeDirectTransaction

image

Figure 27-23. Dynamic model of ChangeMailTransaction

image

Figure 27-24. Dynamic model of ChangeHoldTransaction

image

The implementation of these classes turned out to be straightforward and unsurprising. They too are left as an exercise.

Figure 27-25 shows the implementation of the ChangeAffiliationTransaction. Once again, we use the TEMPLATE METHOD pattern to select the Affiliation derivative that should be handed to the Employee object. (See Figures 27-26 through 27-28).

Figure 27-25. Dynamic model of ChangeAffiliationTransaction

image

Figure 27-26. Dynamic model of ChangeMemberTransaction

image

Figure 27-27. Dynamic model of ChangeUnaffiliatedTransaction

image

What Was I Smoking?

I got quite a surprise when I went to implement this design. Look closely at the dynamic diagrams for the affiliation transactions. Can you spot the problem?

As always, I began the implementation by writing the test case for ChangeMemberTransaction. You can see this test case in Listing 27-19. The test case starts out straightforward enough. It creates an hourly employee named Bill and then creates and executes a ChangeMemberTransaction to put Bill in the union. Then it checks to see that Bill has a UnionAffiliation bound to him and that the UnionAffiliation has the right dues rate.


Listing 27-19. PayrollTest.ChangeUnionMember()

[Test]
public void ChangeUnionMember()
{
  int empId = 8;
  AddHourlyEmployee t =
    new AddHourlyEmployee(empId, "Bill", "Home", 15.25);
  t.Execute();
  int memberId = 7743;
  ChangeMemberTransaction cmt =
    new ChangeMemberTransaction(empId, memberId, 99.42);
  cmt.Execute();
  Employee e = PayrollDatabase.GetEmployee(empId);
  Assert.IsNotNull(e);

  Affiliation affiliation = e.Affiliation;
  Assert.IsNotNull(affiliation);
  Assert.IsTrue(affiliation is UnionAffiliation);
  UnionAffiliation uf = affiliation as UnionAffiliation;
  Assert.AreEqual(99.42, uf.Dues, .001);
  Employee member =PayrollDatabase.GetUnionMember(memberId);
  Assert.IsNotNull(member);
  Assert.AreEqual(e, member);
}


The surprise is hidden in the last few lines of the test case. Those lines make sure that the PayrollDatabase has recorded Bill’s membership in the union. Nothing in the existing UML diagrams makes sure that this happens. The UML is concerned only with the appropriate Affiliation derivative being bound to the Employee. I didn’t notice the deficit at all. Did you?

I merrily coded the transactions as per the diagrams and then watched the unit test fail. Once the failure occurred, it was obvious what I had neglected. What was not obvious was the solution to the problem. How do I get the membership to be recorded by ChangeMemberTransaction but erased by ChangeUnaffiliatedTransaction?

The answer was to add to ChangeAffiliationTransaction another abstract method, named RecordMembership(Employee). This function is implemented in ChangeMemberTransaction to bind the memberId to the Employee instance. In the ChangeUnaffiliatedTransaction, it is implemented to erase the membership record.

Listing 27-20 shows the resulting implementation of the abstract base class ChangeAffiliationTransaction. Again, the use of the TEMPLATE METHOD pattern is obvious.


Listing 27-20. ChangeAffiliationTransaction.cs

namespace Payroll
{
  public abstract class ChangeAffiliationTransaction :
       ChangeEmployeeTransaction
  {
    public ChangeAffiliationTransaction(int empId)
      : base(empId)
    {}

    protected override void Change(Employee e)
    {
      RecordMembership(e);
      Affiliation affiliation = Affiliation;
      e.Affiliation = affiliation;
    }

    protected abstract Affiliation Affiliation { get; }
    protected abstract void RecordMembership(Employee e);
  }
}


Listing 27-21 shows the implementation of ChangeMemberTransaction. This is not particularly complicated or interesting. On the other hand, the implementation of ChangeUnaffiliatedTransaction in Listing 27-22 is a bit more substantial. The RecordMembership function has to decide whether the current employee is a union member. If so, it gets the memberId from the UnionAffiliation and erases the membership record.


Listing 27-21. ChangeMemberTransaction.cs

namespace Payroll
{
  public class ChangeMemberTransaction :
ChangeAffiliationTransaction
  {
    private readonly int memberId;
    private readonly double dues;

    public ChangeMemberTransaction(
      int empId, int memberId, double dues)
      : base(empId)
    {
      this.memberId = memberId;
      this.dues = dues;
    }

    protected override Affiliation Affiliation
    {
      get { return new UnionAffiliation(memberId, dues); }
    }

    protected override void RecordMembership(Employee e)
    {
      PayrollDatabase.AddUnionMember(memberId, e);
    }
  }
}



Listing 27-22. ChangeUnaffiliatedTransaction.cs

namespace Payroll
{
  public class ChangeUnaffiliatedTransaction
   : ChangeAffiliationTransaction
  {}
    public ChangeUnaffiliatedTransaction(int empId)
      : base(empId)
    {}

    protected override Affiliation Affiliation
    {
      get { return new NoAffiliation(); }
    }

    protected override void RecordMembership(Employee e)

    {
      Affiliation affiliation = e.Affiliation;
      if(affiliation is UnionAffiliation)
      {
        UnionAffiliation unionAffiliation =
          affiliation as UnionAffiliation;
        int memberId = unionAffiliation.MemberId;
        PayrollDatabase.RemoveUnionMember(memberId);
      }
    }
  }
}


I can’t say that I’m very pleased with this design. It bothers me that the ChangeUnaffiliatedTransaction must know about UnionAffiliation. I could solve this by putting RecordMembership and EraseMembership abstract methods in the Affiliation class. However, this would force UnionAffiliation and NoAffiliation to know about the PayrollDatabase. And I’m not very happy about that, either.1

Still, the implementation as it stands is pretty simple and violates OCP only slightly. The nice thing is that very few modules in the system know about ChangeUnaffiliatedTransaction, so its extra dependencies aren’t doing very much harm.

Paying Employees

Finally, it is time to consider the transaction that is at the root of this application: the transaction that instructs the system to pay the appropriate employees. Figure 27-28 shows the static structure of the PaydayTransaction class. Figure 27-29 and Figure 27-30 describe the dynamic behavior.

Figure 27-28. Static model of PaydayTransaction

image

Figure 27-29. Dynamic model for PaydayTransaction

image

Figure 27-30. Dynamic model scenario: “Payday is not today.”

image

image

The dynamic models express a great deal of polymorphic behavior. The algorithm used by the CalculatePay message depends on the kind of PaymentClassification that the Employee object contains. The algorithm used to determine whether a date is a payday depends on the kind of PaymentSchedule that the Employee contains. The algorithm used to send the payment to the Employee depends on the type of the PaymentMethod object. This high degree of abstraction allows the algorithms to be closed against the addition of new kinds of payment classifications, schedules, affiliations, or payment methods.

The algorithms depicted in Figure 27-31 and Figure 27-32 introduce the concept of posting. After the correct pay amount has been calculated and sent to the Employee, the payment is posted; that is, the records involved in the payment are updated. Thus, we can define the CalculatePay method as calculating the pay from the last posting until the specified date.

Figure 27-31. Dynamic model scenario: “Payday is today.”

image

Figure 27-32. Dynamic model scenario: Posting payment

image

Developers and business decisions

Where did this notion of posting come from? It certainly wasn’t mentioned in the user stories or the use cases. As it happens, I cooked it up as a way to solve a problem that I perceived. I was concerned that the Payday method might be called multiple times with the same date or with a date in the same pay period, so I wanted to make sure that the employee was not paid more than once. I did this on my own initiative, without asking my customer. It just seemed the right thing to do.

In effect, I made a business decision, deciding that multiple runs of the payroll program should produce different results. I should have asked my customer or project manager about this, since they might have very different ideas.

In checking with the customer,2 I find that the idea of posting goes against his intent. The customer wants to be able to run the payroll system and then review the paychecks. If any of them are wrong, the customer wants to correct the payroll information and run the payroll program again. The customer tells me that I should never consider time cards or sales receipts for dates outside the current pay period.

So, we have to ditch the posting scheme. It seemed like a good idea at the time, but it was not what the customer wanted.

Paying Salaried Employees

The two test cases in Listing 27-23 test whether a salaried employee is being paid appropriately. The first test case makes sure that the employee is paid on the last day of the month. The second test case makes sure that the employee is not paid if it is not the last day of the month.


Listing 27-23. PayrollTest.PaySingleSalariedEmployee et al.

[Test]
public void PaySingleSalariedEmployee()
{
  int empId = 1;
  AddSalariedEmployee t = new AddSalariedEmployee(
    empId, "Bob", "Home", 1000.00);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 30);
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  Paycheck pc = pt.GetPaycheck(empId);
  Assert.IsNotNull(pc);
  Assert.AreEqual(payDate, pc.PayDate);
  Assert.AreEqual(1000.00, pc.GrossPay, .001);
  Assert.AreEqual("Hold", pc.GetField("Disposition"));
  Assert.AreEqual(0.0, pc.Deductions, .001);
  Assert.AreEqual(1000.00, pc.NetPay, .001);
}

[Test]
public void PaySingleSalariedEmployeeOnWrongDate()
{
  int empId = 1;
  AddSalariedEmployee t = new AddSalariedEmployee(
    empId, "Bob", "Home", 1000.00);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 29);

  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  Paycheck pc = pt.GetPaycheck(empId);
  Assert.IsNull(pc);
}


Listing 27-24 shows the Execute() function of PaydayTransaction. It iterates through all the Employee objects in the database, asking each employee if the date on this transaction is its pay date. If so, it creates a new paycheck for the employee and tells the employee to fill in its fields.


Listing 27-24. PaydayTransaction.Execute()

public void Execute()
{
  ArrayList empIds = PayrollDatabase.GetAllEmployeeIds();

  foreach(int empId in empIds)
  {
    Employee employee = PayrollDatabase.GetEmployee(empId);
    if (employee.IsPayDate(payDate)) {
      Paycheck pc = new Paycheck(payDate);
      paychecks[empId] = pc;
      employee.Payday(pc);
    }
  }
}


Listing 27-25 shows MonthlySchedule.cs. Note that it implements IsPayDate to return true only if the argument date is the last day of the month.


Listing 27-25. MonthlySchedule.cs

using System;

namespace Payroll
{
  public class MonthlySchedule : PaymentSchedule
  {
    private bool IsLastDayOfMonth(DateTime date)
    {
      int m1 = date.Month;
      int m2 = date.AddDays(1).Month;
      return (m1 != m2);
    }

    public bool IsPayDate(DateTime payDate)
    {
      return IsLastDayOfMonth(payDate);
    }
  }
}


Listing 27-26 shows the implementation of Employee.PayDay(). This function is the generic algorithm for calculating and dispatching payment for all employees. Notice the rampant use of the STRATEGY pattern. All detailed calculations are deferred to the contained strategy classes: classification, affiliation, and method.


Listing 27-26. Employee.Paysay()

public void Payday(Paycheck paycheck)
{
  double grossPay = classification.CalculatePay(paycheck);
  double deductions =
    affiliation.CalculateDeductions(paycheck);
  double netPay = grossPay - deductions;
  paycheck.GrossPay = grossPay;
  paycheck.Deductions = deductions;
  paycheck.NetPay = netPay;
  method.Pay(paycheck);
}


Paying Hourly Employees

Getting the hourly employees paid is a good example of the incrementalism of test-first design. I started with very trivial test cases and worked my way up to increasingly complex ones. I’ll show the test cases first, and then show the production code that resulted from them.

Listing 27-27 shows the simplest case. We add an hourly employee to the database and then pay that employee. Since there aren’t any time cards, we expect the paycheck to have a zero value. The utility function ValidateHourlyPaycheck represents a refactoring that happened later. At first, that code was simply buried inside the test function. This test case passed after returning true from WeeklySchedule.IsPayDate().


Listing 27-27. PayrollTest.TestPaySingleHourlyEmployeeNoTimeCards()

[Test]
public void PayingSingleHourlyEmployeeNoTimeCards()
{
  int empId = 2;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.25);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 9);
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  ValidateHourlyPaycheck(pt, empId, payDate, 0.0);
}

private void ValidateHourlyPaycheck(PaydayTransaction pt,
  int empid, DateTime payDate, double pay)
{
  Paycheck pc = pt.GetPaycheck(empid);
  Assert.IsNotNull(pc);
  Assert.AreEqual(payDate, pc.PayDate);
  Assert.AreEqual(pay, pc.GrossPay, .001);
  Assert.AreEqual("Hold", pc.GetField("Disposition"));
  Assert.AreEqual(0.0, pc.Deductions, .001);
  Assert.AreEqual(pay, pc.NetPay, .001);
}


Listing 27-28 shows two test cases. The first tests whether we can pay an employee after adding a single time card. The second tests whether we can pay overtime for a card that has more than 8 hours on it. Of course, I didn’t write these two test cases at the same time. Instead, I wrote the first one and got it working, and then I wrote the second one.


Listing 27-28. PayrollTest.PaySingleHourlyEmployee...()

[Test]
public void PaySingleHourlyEmployeeOneTimeCard()
{
  int empId = 2;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.25);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 9); // Friday

  TimeCardTransaction tc =
    new TimeCardTransaction(payDate, 2.0, empId);
  tc.Execute();
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  ValidateHourlyPaycheck(pt, empId, payDate, 30.5);
}

[Test]
public void PaySingleHourlyEmployeeOvertimeOneTimeCard()
{
  int empId = 2;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.25);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 9); // Friday

  TimeCardTransaction tc =
    new TimeCardTransaction(payDate, 9.0, empId);
  tc.Execute();
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  ValidateHourlyPaycheck(pt, empId, payDate,
    (8 + 1.5)*15.25);
}


Getting the first test case working was a matter of changing HourlyClassification. CalculatePay to loop through the time cards for the employee, add up the hours, and multiply by the pay rate. Getting the second test working forced me to change the function to calculate straight and overtime hours.

The test case in Listing 27-29 makes sure that we don’t pay hourly employees unless the PaydayTransaction is constructed with a Friday.


Listing 27-29. PayrollTest.PaySingleHourlyEmployeeOnWrongDate()

[Test]
public void PaySingleHourlyEmployeeOnWrongDate()
{
  int empId = 2;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.25);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 8); // Thursday

  TimeCardTransaction tc =
    new TimeCardTransaction(payDate, 9.0, empId);
  tc.Execute();
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();

  Paycheck pc = pt.GetPaycheck(empId);
  Assert.IsNull(pc);
}


Listing 27-30 is a test case that makes sure that we can calculate the pay for an employee who has more than one time card.


Listing 27-30. PayrollTest.PaySingleHourlyEmployeeTwoTimeCards()

[Test]
public void PaySingleHourlyEmployeeTwoTimeCards()
{
  int empId = 2;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.25);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 9); // Friday

  TimeCardTransaction tc =
    new TimeCardTransaction(payDate, 2.0, empId);
  tc.Execute();
  TimeCardTransaction tc2 =
    new TimeCardTransaction(payDate.AddDays(-1), 5.0,empId);
  tc2.Execute();
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  ValidateHourlyPaycheck(pt, empId, payDate, 7*15.25);
}


Finally, the test case in Listing 27-31 proves that we will pay an employee only for time cards in the current pay period. Time cards from other pay periods are ignored.


Listing 27-31. PayrollTest.Test...WithTimeCardsSpanningTwoPayPeriods()

[Test]
public void
TestPaySingleHourlyEmployeeWithTimeCardsSpanningTwoPayPeriods()
{
  int empId = 2;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.25);
  t.Execute();
  DateTime payDate = new DateTime(2001, 11, 9); // Friday
  DateTime dateInPreviousPayPeriod =
    new DateTime(2001, 11, 2);

  TimeCardTransaction tc =
    new TimeCardTransaction(payDate, 2.0, empId);
  tc.Execute();
  TimeCardTransaction tc2 = new TimeCardTransaction(
    dateInPreviousPayPeriod, 5.0, empId);
  tc2.Execute();
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  ValidateHourlyPaycheck(pt, empId, payDate, 2*15.25);
}


The code that makes all this work was grown incrementally, one test case at a time. The structure you see in the code that follows evolved from test case to test case. Listing 27-32 shows the appropriate fragments of HourlyClassification.cs. We simply loop through the time cards. For each time card, we check whether if it is in the pay period. If so, we calculate the pay it represents.


Listing 27-32. HourlyClassification.cs (fragment)

public double CalculatePay(Paycheck paycheck)
{
  double totalPay = 0.0;
  foreach(TimeCard timeCard in timeCards.Values)
  {
    if(IsInPayPeriod(timeCard, paycheck.PayDate))
      totalPay += CalculatePayForTimeCard(timeCard);
  }
  return totalPay;
}

private bool IsInPayPeriod(TimeCard card,
                               DateTime payPeriod)
{
  DateTime payPeriodEndDate = payPeriod;
  DateTime payPeriodStartDate = payPeriod.AddDays(-5);

  return card.Date <= payPeriodEndDate &&
    card.Date >= payPeriodStartDate;
}

private double CalculatePayForTimeCard(TimeCard card)
{
  double overtimeHours = Math.Max(0.0, card.Hours - 8);
  double normalHours = card.Hours - overtimeHours;
  return hourlyRate * normalHours +
    hourlyRate * 1.5 * overtimeHours;
}


Listing 27-33 shows that the WeeklySchedule pays only on Fridays.


Listing 27-33. WeeklySchedule.IsPayDate()

public bool IsPayDate(DateTime payDate)
{
  return payDate.DayOfWeek == DayOfWeek.Friday;
}


Calculating the pay for commissioned employees is left as an exercise. There shouldn’t be any big surprises.

Pay periods: A design problem

Now it’s time to implement the union dues and service charges. I’m contemplating a test case that will add a salaried employee, convert it into a union member, and then pay the employee and ensure that the dues were subtracted from the pay. The coding is shown in Listing 27-34.


Listing 27-34. PayrollTest.SalariedUnionMemberDues()

[Test]
public void SalariedUnionMemberDues()
{
  int empId = 1;
  AddSalariedEmployee t = new AddSalariedEmployee(
    empId, "Bob", "Home", 1000.00);
  t.Execute();
  int memberId = 7734;
  ChangeMemberTransaction cmt =
    new ChangeMemberTransaction(empId, memberId, 9.42);
  cmt.Execute();
  DateTime payDate = new DateTime(2001, 11, 30);
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  Paycheck pc = pt.GetPaycheck(empId);
  Assert.IsNotNull(pc);
  Assert.AreEqual(payDate, pc.PayDate);
  Assert.AreEqual(1000.0, pc.GrossPay, .001);
  Assert.AreEqual("Hold", pc.GetField("Disposition"));
  Assert.AreEqual(???, pc.Deductions, .001);
  Assert.AreEqual(1000.0 - ???, pc.NetPay, .001);
}


Note the ??? in the last two lines of the test case. What should I put there? The user stories tell me that union dues are weekly, but salaried employees are paid monthly. How many weeks are in each month? Should I simply multiply the dues by 4? That’s not very accurate. I’ll ask the customer what he wants.3

The customer tells me that union dues are accrued every Friday. So what I need to do is count the number of Fridays in the pay period and multiply by the weekly dues. There are five Fridays in November 2001, the month the test case is written for. So I can modify the test case appropriately.

Counting the Fridays in a pay period implies that I need to know what the starting and ending dates of the pay period are. I have done this calculation before in the function IsInPayPeriod in Listing 27-32. (You probably wrote a similar one for the CommissionedClassification.) This function is used by the CalculatePay function of the HourlyClassification object to ensure that time cards only from the pay period are tallied. Now it seems that the UnionAffiliation object must call this function, too.

But wait! What is this function doing in the HourlyClassification class? We’ve already determined that the association between the payment schedule and the payment classification is accidental. The function that determines the pay period ought to be in the PaymentSchedule class, not in the PaymentClassification class!

It is interesting that our UML diagrams didn’t help us catch this problem. The problem surfaced only when I started thinking about the test cases for UnionAffiliation. This is yet another example of how necessary coding feedback is to any design. Diagrams can be useful, but reliance on them without feedback from the code is risky business.

So, how do we get the pay period out of the PaymentSchedule hierarchy and into the PaymentClassification and Affiliation hierarchies? These hierarchies do not know anything about each other. I have an idea about this. We could put the pay period dates into the Paycheck object. Right now, the Paycheck simply has the end date of the pay period. We ought to be able to get the start date in there too.

Listing 27-35 shows the change made to PaydayTransaction.Execute(). Note that when the Paycheck is created, it is passed both the start and end dates of the pay period. Note also that it is the PaymentSchedule that calculates both. The changes to Paycheck should be obvious.


Listing 27-35. PaydayTransaction.Execute()

public void Execute()
{
  ArrayList empIds = PayrollDatabase.GetAllEmployeeIds();

  foreach(int empId in empIds)
  {
    Employee employee = PayrollDatabase.GetEmployee(empId);

    if (employee.IsPayDate(payDate))
    {
      DateTime startDate =
        employee.GetPayPeriodStartDate(payDate);
      Paycheck pc = new Paycheck(startDate, payDate);
      paychecks[empId] = pc;
      employee.Payday(pc);
    }
  }
}


The two functions in HourlyClassification and CommissionedClassification that determined whether TimeCards and SalesReceipts were within the pay period have been merged and moved into the base class PaymentClassification. See Listing 27-36.


Listing 27-36. PaymentClassification.IsInPayPeriod(...)

public bool IsInPayPeriod(DateTime theDate, Paycheck paycheck)
{
  DateTime payPeriodEndDate = paycheck.PayPeriodEndDate;
  DateTime payPeriodStartDate = paycheck.PayPeriodStartDate;
  return (theDate >= payPeriodStartDate)
    && (theDate <= payPeriodEndDate);
}


Now we are ready to calculate the employee’s union dues in UnionAffilliation. CalculateDeductions. The code in Listing 27-37 shows how this is done. The two dates that define the pay period are extracted from the paycheck and are passed to a utility function that counts the number of Fridays between them. This number is then multiplied by the weekly dues rate to calculate the dues for the pay period.


Listing 27-37. UnionAffiliation.CalculateDeductions(...)

public double CalculateDeductions(Paycheck paycheck)
{
  double totalDues = 0;

  int fridays = NumberOfFridaysInPayPeriod(
    paycheck.PayPeriodStartDate, paycheck.PayPeriodEndDate);
  totalDues = dues * fridays;
  return totalDues;
}

private int NumberOfFridaysInPayPeriod(
  DateTime payPeriodStart, DateTime payPeriodEnd)
{
  int fridays = 0;
  for (DateTime day = payPeriodStart;
    day <= payPeriodEnd; day.AddDays(1))

  {
    if (day.DayOfWeek == DayOfWeek.Friday)
      fridays++;
  }
  return fridays;
}


The last two test cases have to do with union service charges. The first test case, shown in Listing 27-38, makes sure that we deduct service charges appropriately.


Listing 27-38. PayrollTest.HourlyUnionMemberServiceCharge()

[Test]
public void HourlyUnionMemberServiceCharge()
{
  int empId = 1;
  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.24);
  t.Execute();
  int memberId = 7734;
  ChangeMemberTransaction cmt =
    new ChangeMemberTransaction(empId, memberId, 9.42);
  cmt.Execute();
  DateTime payDate = new DateTime(2001, 11, 9);
  ServiceChargeTransaction sct =
    new ServiceChargeTransaction(memberId, payDate, 19.42);
  sct.Execute();
  TimeCardTransaction tct =
    new TimeCardTransaction(payDate, 8.0, empId);
  tct.Execute();
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  Paycheck pc = pt.GetPaycheck(empId);
  Assert.IsNotNull(pc);
  Assert.AreEqual(payDate, pc.PayPeriodEndDate);
  Assert.AreEqual(8*15.24, pc.GrossPay, .001);
  Assert.AreEqual("Hold", pc.GetField("Disposition"));
  Assert.AreEqual(9.42 + 19.42, pc.Deductions, .001);
  Assert.AreEqual((8*15.24)-(9.42 + 19.42),pc.NetPay, .001);
}


The second test case, which posed something of a problem for me, is shown it in Listing 27-39. This test case makes sure that service charges dated outside the current pay period are not deducted.


Listing 27-39. PayrollTest.ServiceChargesSpanningMultiplePayPeriods()

[Test]
public void ServiceChargesSpanningMultiplePayPeriods()
{
  int empId = 1;

  AddHourlyEmployee t = new AddHourlyEmployee(
    empId, "Bill", "Home", 15.24);
  t.Execute();
  int memberId = 7734;
  ChangeMemberTransaction cmt =
    new ChangeMemberTransaction(empId, memberId, 9.42);
  cmt.Execute();
  DateTime payDate = new DateTime(2001, 11, 9);
  DateTime earlyDate =
    new DateTime(2001, 11, 2); // previous Friday
  DateTime lateDate =
    new DateTime(2001, 11, 16); // next Friday
  ServiceChargeTransaction sct =
    new ServiceChargeTransaction(memberId, payDate, 19.42);
  sct.Execute();
  ServiceChargeTransaction sctEarly =
    new ServiceChargeTransaction(memberId,earlyDate,100.00);
  sctEarly.Execute();
  ServiceChargeTransaction sctLate =
    new ServiceChargeTransaction(memberId,lateDate,200.00);
  sctLate.Execute();
  TimeCardTransaction tct =
    new TimeCardTransaction(payDate, 8.0, empId);
  tct.Execute();
  PaydayTransaction pt = new PaydayTransaction(payDate);
  pt.Execute();
  Paycheck pc = pt.GetPaycheck(empId);
  Assert.IsNotNull(pc);
  Assert.AreEqual(payDate, pc.PayPeriodEndDate);
  Assert.AreEqual(8*15.24, pc.GrossPay, .001);
  Assert.AreEqual("Hold", pc.GetField("Disposition"));
  Assert.AreEqual(9.42 + 19.42, pc.Deductions, .001);
  Assert.AreEqual((8*15.24) - (9.42 + 19.42),
    pc.NetPay, .001);
}


To implement this, I wanted UnionAffiliation::CalculateDeductions to call IsInPayPeriod. Unfortunately, we just put IsInPayPeriod in the PaymentClassification class. (See Listing 27-36.) It was convenient to put it there while it was the derivatives of PaymentClassification that needed to call it. But now other classes need it as well. So I moved the function into a DateUtil class. After all, the function is simply determining whether a given date is between two other given dates. (See Listing 27-40.)


Listing 27-40. DateUtil.cs

using System;

namespace Payroll
{
  public class DateUtil

  {
    public static bool IsInPayPeriod(
      DateTime theDate, DateTime startDate, DateTime endDate)
    {
      return (theDate >= startDate) && (theDate <= endDate);
    }
  }
}


So now, finally, we can finish the UnionAffiliation::CalculateDeductions function. I leave that as an exercise for you.

Listing 27-41 shows the implementation of the Employee class.


Listing 27-41. Employee.cs

using System;

namespace Payroll
{
  public class Employee
  {
    private readonly int empid;
    private string name;
    private readonly string address;
    private PaymentClassification classification;
    private PaymentSchedule schedule;
    private PaymentMethod method;
    private Affiliation affiliation = new NoAffiliation();

    public Employee(int empid, string name, string address)
    {
      this.empid = empid;
      this.name = name;
      this.address = address;
    }

    public string Name
    {
      get { return name; }
      set { name = value; }
    }

    public string Address
    {
      get { return address; }
    }

    public PaymentClassification Classification
    {
      get { return classification; }
      set { classification = value; }
    }

    public PaymentSchedule Schedule
    {
      get { return schedule; }
      set { schedule = value; }
    }

    public PaymentMethod Method
    {
      get { return method; }
      set { method = value; }
    }

    public Affiliation Affiliation
    {
      get { return affiliation; }
      set { affiliation = value; }
    }

    public bool IsPayDate(DateTime date)
    {
      return schedule.IsPayDate(date);
    }

    public void Payday(Paycheck paycheck)
    {
      double grossPay = classification.CalculatePay(paycheck);
      double deductions =
        affiliation.CalculateDeductions(paycheck);
      double netPay = grossPay - deductions;
      paycheck.GrossPay = grossPay;
      paycheck.Deductions = deductions;
      paycheck.NetPay = netPay;
      method.Pay(paycheck);
    }

    public DateTime GetPayPeriodStartDate(DateTime date)
    {
      return schedule.GetPayPeriodStartDate(date);
    }
  }
}


Main Program

The main payroll program can now be expressed as a loop that parses transactions from an input source and then executes them. Figures 27-33 and 27-34 describe the statics and dynamics of the main program. The concept is simple: The PayrollApplication sits in a loop, alternately requesting transactions from the TransactionSource and then telling those Transaction objects to Execute. Note that this is different from the diagram in Figure 27-1 and represents a shift in our thinking to a more abstract mechanism.

Figure 27-33. Static model for the main program

image

Figure 27-34. Dynamic model for the main program

image

TransactionSource is an interface that we can implement in several ways. The static diagram shows the derivative named TextParserTransactionSource, which reads an incoming text stream and parses out the transactions as described in the use cases. This object then creates the appropriate Transaction objects and sends them along to the PayrollApplication.

The separation of interface from implementation in the TransactionSource allows the source of the transactions to vary. For example, we could easily interface the PayrollApplication to a GUITransactionSource or a RemoteTransaction-Source.

The Database

Now that most of the application has been analyzed, designed, and implemented, we can consider the role of the database. The class PayrollDatabase clearly encapsulates something involving persistence. The objects contained within the PayrollDatabase must live longer than any particular run of the application. How should this be implemented? Clearly, the transient mechanism used by the test cases is not sufficient for the real system. We have several options.

We could implement PayrollDatabase by using an object-oriented database management system (OODBMS). This would allow the objects to reside within the permanent storage of the database. As designers, we would have little more work to do, since the OODBMS would not add much new to our design. One of the great benefits of OODBMS products is that they have little or no impact on the object model of the applications. As far as the design is concerned, the database barely exists.4

Another option would be to use simple flat text files to record the data. On initialization, the PayrollDatabase object could read that file and build the necessary objects in memory. At the end of the program, the PayrollDatabase object could write a new version of the text file. Certainly, this option would not suffice for a company with hundreds of thousands of employees or one that wanted real-time concurrent access to its payroll database. However, it might suffice for a smaller company, and it could certainly be used as a mechanism for testing the rest of the application classes without investing in a big database system.

Still another option would be to incorporate a relational database management system (RDBMS) into the PayrollDatabase object. The implementation of the PayrollDatabase object would then make the appropriate queries to the RDMBS to temporarily create the necessary objects in memory.

The point is that any of these mechanisms would work. Our application has been designed in such a way that it does not know or care what the underlying implementation of the database is. As far as the application is concerned, the database is simply mechanisms for managing storage.

Databases should usually not be considered as a major factor of the design and implementation. As we have shown here, they can be left for last and handled as a detail.5 By doing so, we leave open a number of interesting options for implementing the needed persistence and creating mechanisms to test the rest of the application. We also do not tie ourselves to any particular database technology or product. We have the freedom to choose the database we need, based on the rest of the design, and we maintain the freedom to change or replace that database product in the future as needed.

Conclusion

In roughly 32 diagrams in Chapters 26 and 27, we have documented the design and implementation of the payroll application. The design uses a large amount of abstraction and polymorphism. The result is that large portions of the design are closed against changes of payroll policy. For example, the application could be changed to deal with employees who were paid quarterly, based on a normal salary and a bonus schedule. This change would require addition to the design, but little of the existing design and code would change.

During this design process, we rarely considered whether we were performing analysis, design, or implementation. Instead, we concentrated on issues of clarity and dependency management. We tried to find the underlying abstractions wherever possible. The result is that we have a good design for a payroll application, and we have a core of classes that are germane to the problem domain as a whole.

About This Chapter

The diagrams in this chapter are derived from the Booch diagrams in the corresponding chapter of my 1995 book.6 Those diagrams were created in 1994. As I created them, I also wrote some of the code that implemented them, to make sure that the diagrams made sense. However, I did not write anywhere near the amount of code presented here. Therefore, the diagrams did not benefit from significant feedback from the code and tests. This lack of feedback shows.

This chapter appears in my 2002 book.7 I wrote the code for that chapter in C++ in the order presented here. In every case, test cases were written before production code. In many cases, those tests were created incrementally, evolving as the production code also evolved. The production code was written to comply with the diagrams, so long as that made sense. In several cases, it did not make sense, and so I changed the design of the code.

One of the first places that this happened was when I decided against multiple Affiliation instances in the Employee object. Another was when I found that I had not considered recording the employee’s membership in the union in the ChangeMember-Transaction.

This is normal. When you design without feedback, you will necessarily make errors. It was the feedback imposed by the tests cases and running code that found these errors for us.

This chapter was translated from C++ into C# by my coauthor, Micah Martin. Special attention was paid to C# conventions and styles, so that the code would not look too much like C#++. (You can find the final version of this code at www.objectmentor.com/PPP/payroll.net.zip.) The diagrams were left unchanged, except that we replaced composition relationships with associations.

Bibliography

[Jacobson92] Ivar Jacobson, Object-Oriented Software Engineering: A Use Case Driven Approach, Addison-Wesley, 1992.

[Martin1995] Designing Object-Oriented C++ Aplications Using the Booch Method, Prentice Hall, 1995.

[Martin2002] Agile Software Development: Principles, Patterns, and Practices, Prentice Hall, 2002.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.104.238