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


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


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


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


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


Figure 27-6. Dynamic model for DeleteEmployee TRansaction


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


Figure 27-8. Dynamic model for posting a TimeCard


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


Figure 27-10. Dynamic model for SalesReceiptTransaction


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


Figure 27-12. Dynamic model for ServiceChargeTransaction


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


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


Figure 27-15. Dynamic model for ChangeNameTransaction


Figure 27-16. Dynamic model for ChangeAddressTransaction


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


Figure 27-18. Dynamic model of ChangeHourlyTransaction


Figure 27-19. Dynamic model of ChangeSalariedTransaction


Figure 27-20. Dynamic Model of ChangeCommissionedTransaction


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


Figure 27-22. Dynamic model of ChangeDirectTransaction


Figure 27-23. Dynamic model of ChangeMailTransaction


Figure 27-24. Dynamic model of ChangeHoldTransaction


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


Figure 27-26. Dynamic model of ChangeMemberTransaction


Figure 27-27. Dynamic model of ChangeUnaffiliatedTransaction


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]

[1] I could use the VISITOR pattern to solve this problem, but that would probably be way overengineered.

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


Figure 27-29. Dynamic model for PaydayTransaction


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


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."


Figure 27-32. Dynamic model scenario: Posting payment


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.

[2] OK, the customer is me.

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]

[3] And so Bob talks to himself yet again. Go to www.google.com/groups and look up "Schizophrenic Robert Martin."

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);     }   } }




Agile Principles, Patterns, and Practices in C#
Agile Principles, Patterns, and Practices in C#
ISBN: 0131857258
EAN: 2147483647
Year: 2006
Pages: 272

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