A Flaw in the Code Design


You may recall that the PayrollDatabase was filled with nothing but public static methods. This decision is no longer appropriate. How do we start using a real database in the code without breaking all the tests that use the static methods? We don't want to overwrite the PayrollDatabase class to use a real database. That would force all our existing unit tests to use the real database. It would be nice if PayrollDatabase were an interface so we could easily swap out different implementations. One implementation would store data in memory like it does now, so that our tests can continue to run quickly. Another implementation would store data in a real database.

To achieve this new design, we'll have to perform a few refactorings, running the unit tests after each step to make sure we're not breaking the code. First, we'll create an instance of the PayrollDatabase and store it in a static variable: instance. Then we'll go through each static method in PayrollDatabase and rename it to include the word static. Then we'll extract the method body into a new non-static method of the same name. (See Listing 37-1.)

Listing 37-1. Example refactorin

public class PayrollDatabase {   private static PayrollDatabase instance;   public static void AddEmployee_Static(Employee employee)   {     instance.AddEmployee(employee);   }   public void AddEmployee(Employee employee)   {     employees[employee.EmpId] = employee;   }

Now we need to find every call to PayrollDatabase.AddEmployee_Static() and replace it with PayrollDatabase.instance.AddEmployee(). Once they have all been changed, we can delete the static version of the method. The same has to be done with each static method, of course.

That leaves every database invocation going through the PayrollDatabase. instance variable. We would like PayrollDatabase to be an interface. So we need to find another home for that instance variable. Certainly, PayrollTest should hold such a variable, since it can then be used by all the tests. For the application, a good place is in each TRansaction derivative. The PayrollDatabase instance will have to be passed into the constructor and stored as an instance variable of each transaction. Rather than duplicate this code, let's simply put the PayrollDatabase instance in the TRansaction base class. transaction is an interface, so we'll have to convert it into an abstract class, as in Listing 37-2.

Listing 37-2. TRansaction.cs

public abstract class Transaction {   protected readonly PayrollDatabase database;   public Transaction(PayrollDatabase database)   {     this.database = database;   }   public abstract void Execute(); }

Now that nobody is using the PayrollDatabase.instance, we can delete it. Before we convert PayrollDatabase into an interface, we need a new implementation that extends PayrollDatabase. Since the current implementation stores everything in memory, we'll call the new class InMemoryPayrollDatabase (Listing 37-3) and use it wherever PayrollDatabase is instantiated. Finally, PayrollDatabase can be reduced to an interface (Listing 37-4) and we can begin work on the real database implementation.

Listing 37-3. InMemoryPayrollDatabase.cs

public class InMemoryPayrollDatabase : PayrollDatabase {   private static Hashtable employees = new Hashtable();   private static Hashtable unionMembers = new Hashtable();   public void AddEmployee(Employee employee)   {     employees[employee.EmpId] = employee;   }   // etc... }

Listing 37-4. PayrollDatabase.cs

public interface PayrollDatabase {   void AddEmployee(Employee employee);   Employee GetEmployee(int id);   void DeleteEmployee(int id);   void AddUnionMember(int id, Employee e);   Employee GetUnionMember(int id);   void RemoveUnionMember(int memberId);   ArrayList GetAllEmployeeIds(); }




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