DOC

Refactoring to the Template Method Design Pattern

By Ryan Kelly,2014-05-13 08:32
7 views 0
n this article we'll take a simple piece of procedural code and see how maintaining it in an unthinking way gradually reduces the readability of code and scatters our rules and logic to different locations. We'll then see how we can change the code so that the quality improves rather than degrades. Along the way we'll learn a thing or two about refactoring, inheritance and design patterns.

    By Refactoring to established Design Patterns your maintenance efforts will improve rather than degrade

    the quality of the code.

    Download RefactorDemo_src.zip - 8.69 KB

    Introduction

    This is part one of a two part series of articles on Refactoring and Design Patterns.

    ? Part 1 - Refactoring to the Template Method Design Pattern

    ? Part 2 - Refactoring to Dependency Injection

    In this article we'll take a simple piece of procedural code and see how maintaining it in an unthinking

    way gradually reduces the readability of code and scatters our rules and logic to different locations.

    We'll then see how we can change the code so that the quality improves rather than degrades. Along the

    way we'll learn a thing or two about refactoring, inheritance and design patterns.

    Background

    Refactoring is the process of making a succession of small well defined changes to code so as to improve

    it. There are numerous well understood and documented code issues, along with changes that can be

    made to fix them. These are changes that many programmers make on a day to day basis without even

    thinking about them. An awareness of refactoring techniques guides us towards writing better code from

    the start, and puts us on more solid ground whenever we do need to make changes.

    Design Patterns are documented solutions to common problems that arise again and again in the design

    and implementation of software systems. An understanding of Design Patterns will help you produce

    better software more quickly. Design Patterns also serve as a common language allowing developers to

    discuss design alternatives with greater accuracy and shared understanding.

    The Design Pattern that we'll be looking at in this article is the Template Method pattern. This Design Pattern is discussed further in the following article, which also looks at an alternative to the pattern: Template Method Design Pattern vs. Functional Programming - By Tuomas Hietanen

    The initial code

    Let's start with a simple class called SecurityManager. It provides a method for creating a new user in a database. For the sake of simplicity I'm not getting into any Database code here. I'll just pop a message to the Console in places where database work is supposed to happen.

    As you can see the method does everything that's needed, from encrypting the users password (in this case it simply reverses it), to inserting the user details into the Database, to creating entries in the Audit trail.

     class SecurityManager

     {

     public void CreateUser(string username, string realName, string password)

     {

     //Encrypt the Password

     char[] array = password.ToCharArray();

     Array.Reverse(array);

     string encryptedPassword = new string(array);

     // Insert any required database details about the user

     Console.Write(String.Format("Default Behaviour\n

     Inserting Details for User ({0}, {1}, {2})\n\n",

     username, realName, encryptedPassword));

     //Insert audit trail entries about the creation of the user

     Console.Write(String.Format("Default Behaviour\n

     AuditTrail Create User ({0})\n\n", username));

     }

     }

    Even at first glance, this code has problems. It does too much in one routine. Routines should do one specific thing. In this case creating a user is clearly a three-step process, but this one routine is doing it all. If we never need to change this code it wouldn't be the end of the world to leave it as is. On the other hand if any changes or special conditions need to be implemented then things are likely to get worse

    unless we take some action. We'll return to the steps we can take shortly, For now let's see what happens if we try and add features to the code as it stands.

    Our SecurityManager code has been released into production. It's working fine, however we've been told that it will soon be rolled out to an office in France where the rules for creating a new user are a little bit different.

    In France passwords are not encrypted. They are stored in plain text. This is not good, and they're working on changing it, but our app needs to handle this situation until the changes are made. We'd also like to make it easy to switch on encryption whenever the French Office are ready to use it. The simple quick fix is an 'if' statement that only encrypts password when the country is not France. It wouldn't be a huge deal to remove the 'if' statement when the time comes to enable encryption.

    We also discover that as we release the code to other countries there are other special cases. There are three different versions of the Database Schema, table and column names can vary and in some cases we need to insert data into extra tables, so the SQL for creating a new user will be different from country to country. The quick and dirty fix is a 'switch' statement to create the user in the correct way for each version of the schema.

    Our revised CreateUser function is shown below, complete with quick fix hacks. Note that COUNTRY and SCHEMA_VERSION are now checked and they affect how a user is created. In our Demo these are Constants, they could come from a Config File or from any number of sources.

    The important thing to note is that our simple 3 step routine for creating a user just got a lot more complicated. If we keep going on this path each time a new variation arises it could quickly become unmanagable. This isn't good. Complicated routines are exactly where subtle bugs like to hide.

     public void CreateUser(string username, string realName, string password)

     {

     //Encrypt the Password

     string encryptedPassword = password;

     if (COUNTRY != "France")

     {

     char[] array = password.ToCharArray();

     Array.Reverse(array);

     encryptedPassword = new string(array);

     }

     // Insert any required database details about the user

     switch(SCHEMA_VERSION)

     {

     case 2:

     Console.Write(String.Format("Schema 2 Behaviour\n

     Inserting Details for User ({0}, {1},

    {2})\n\n",

     username, realName, encryptedPassword));

     break;

     case 3:

     Console.Write(String.Format("Schema 3 Behaviour\n

     Inserting Details for User ({0}, {1},

    {2})\n\n",

     username, realName, encryptedPassword));

     break;

     default:

     Console.Write(String.Format("Default Behaviour\n

     Inserting Details for User ({0}, {1},

    {2})\n\n",

     username, realName, encryptedPassword));

     break;

     }

     //Insert audit trail entries about the creation of the user

     Console.Write(String.Format("Default Behaviour\n

     AuditTrail Create User ({0})\n\n", username));

     }

    Every time you are forced to change code you have an opportunity. You can use a quick fix which gets the job done but degrades the quality of the code, or you can refactor the code that's already there so that the overal quality of the code is actually improved by the change. Let's forget about the changes we need to make, and start by fixing the code that we started with.

    Break the algorithm into meaningful steps

    We know that the CreateUser method is doing three things, so we should extract each of those things into their own routines.

    In the world of refactoring there's a name for this...Extract Method. There are even detailed instructions on how to do it. Better still there are a number of Refactoring Tools that will make the changes for you, you just point them at the code that you want to extract and they do all the work.

    Here's how our Security Manager looks now that we've refactored it a little but. Note our CreateUser method is still a three step process, but now the three steps are delegated to their own

    routines.

     class SecurityManager

     {

     public void CreateUser(string username, string realName, string password)

     {

     string encryptedPassword = GetEncryptedPassword(password);

     InsertUserDetails(username, realName, encryptedPassword);

     AuditTrailCreateUser(username);

     }

     private string GetEncryptedPassword(string password)

     {

     //Encrypt the Password

     char[] array = password.ToCharArray();

     Array.Reverse(array);

     return new string(array);

     }

     private void InsertUserDetails(string username, string realName, string encryptedPassword)

     {

     // Insert any required database details about the user

     Console.Write(String.Format("Default Behaviour\n

     Inserting Details for User ({0}, {1}, {2})\n\n",

     username, realName, encryptedPassword));

     }

     private void AuditTrailCreateUser(string username)

     {

     //Insert audit trail entries about the creation of the user

     Console.Write(String.Format("Default Behaviour\n

     AuditTrail Create User ({0})\n\n", username));

     }

     }

    With the code broken into methods that each do one thing, let's see if we can add our special cases to the

    code as it stands. We could of course go into the functions like GetEncryptedPassword or InsertUserDetails and use 'if' statements and 'switch' statements like we did in the original code.

    It's marginally better to do this now in smaller routines with one specific task each, however it still leaves us with logic about special cases scattered throughout our code. I don't like it. In refactoring lingo...It Smells.

    Another approach is to use inheritance so that we can override specific parts of the algorithm for creating a user.

    Extracting a Hierarchy

    We can use inheritance when we have a class that broadly does what we want, but where we'd like to modify some of its behaviour, perhaps in response to a special case scenario. Sounds perfect. The only public method of the class is CreateUser. It fairly accurately captures the general algorithm for creating a user. It calls sub routines to encrypt the password, Insert the user details, and audit trail the event.

    It might be useful to some future programmer to override the CreateUser method and use a completely different algorithm, but for now let's assume that it works, and that changes will only apply to the sub steps like encrypting passwords.

    To allow for inheritance we need to modify our SecurityManager class to expose those sub methods that we think could be overridden with different behaviour. We don't want people calling these methods directly, to do so would enable someone to insert user records without inserting the necessary AuditTrail records.

    We need to change these methods from Private to Protected. This will let classes that inherit from SecurityManager see the methods, but won't allow other classes in the system call them directly. We also want our sub class to be able to override the behaviour of the three protected methods. To achieve this we mark the routines as virtual.

    We know that the password encryption can vary, and so can the Insertion of User Data. So far there is no suggestion that the way we update the Audit Trail can vary. We take the decision that the only methods that we will allow to be overridden are EncryptPassword and InsertUserDetails. We leave AuditTrailCreateUser private and non-virtual. AuditTrailing isn't something that programmers should be screwing around with. We don't want someone overriding the method so that no audit trail is kept. If we hadn't broken the CreateUser method into it's three sub routines we wouldn't have the option to selectively open up part of the algorithm for modification. It would have been all or nothing. Our refactoring is aleady paying dividends.

    What we've actually done is modify our code to match a well known Design Pattern called the Template Method Design Pattern. If you haven't paid much attention to Design Patterns, it's time to start. Virtually

    every design problem that you encounter has been solved and documented by someone else. Design Patterns are the language we use to communicate those solutions.

    Apart from providing ready made solutions to common problems, Design Patterns can actually reprogram the way you think about software design and programming. Once you grasp a pattern like the Template Method you will never again write code like the initial code in this article. Here's our revised SecurityManager code, now containing two protected virtual methods. Apart from these changes the code is identical to the previous version.

     class SecurityManager

     {

     public void CreateUser(string username, string realName, string password)

     {

     string encryptedPassword = GetEncryptedPassword(password);

     InsertUserDetails(username, realName, encryptedPassword);

     AuditTrailCreateUser(username);

     }

     protected virtual string GetEncryptedPassword(string password)

     {

     //Encrypt the Password

     char[] array = password.ToCharArray();

     Array.Reverse(array);

     return new string(array);

     }

     protected virtual void InsertUserDetails(string username, string realName, string encryptedPassword)

     {

     // Insert any required database details about the user

     Console.Write(String.Format("Default Behaviour\n

     Inserting Details for User ({0}, {1}, {2})\n\n",

     username, realName, encryptedPassword));

     }

     private void AuditTrailCreateUser(string username)

     {

     //Insert audit trail entries about the creation of the user

     Console.Write(String.Format("Default Behaviour\n

     AuditTrail Create User ({0})\n\n", username));

     }

     }

    Creating a Subclass

    The base class makes inheritance possible, but it's the derived class that actually makes it happen. Let's take the simplest example we can think of. We'd like the process of creating a user to work exactly like it did previously, but we don't want the password to be encrypted at all, this will meet the needs of the French office.

    We create a new class called NoEncryptionSecurityManager. It Inherits from Security Manager. We override the GetEncryptedPassword method so that instead of performing encryption we simply return whatever password is passed in.

     class NoEncryptionSecurityManager: SecurityManager

     {

     protected override string GetEncryptedPassword(string password)

     {

     return password;

     }

     }

    Note that we don't mention the other methods at all. This is the key to the Template Method Design Pattern. Our new class behaves just like its parent except for the behaviour that it specifically overrides. If you want to visually grasp what's happening here, imagine that at runtime this new version of GetEncryptedPassword is cut and pasted into the base class, replacing the original GetEncryptedPassword method.

    We don't always have to completely re-implement the method when we override it. Sometimes we're happy with what it's doing, but we'd like to add an extra step. The next example overrides GetEncryptedPassword, but it calls the original implementation in the Base Class using the 'base' identifier. It then adds the extra step of converting the result to Upper Case.

     class UpperCasePasswordSecurityManager: SecurityManager

     {

     protected override string GetEncryptedPassword(string password)

     {

     return base.GetEncryptedPassword(password).ToUpper();

     }

     }

    We're only overriding one method in these examples. The rest of the class works exactly as implemented in the original SecurityManager class. We can override the behaviour of multiple methods just as easily. The following class overrides the GetEncryptedPassword method to do no encryption, and overrides the InsertUserDetails method to perform some additional steps.

     class SchemaTwoSecurityManager: SecurityManager

     {

     protected override string GetEncryptedPassword(string password)

     {

     return password;

     }

     protected override void InsertUserDetails(string username, string realName, string encryptedPassword)

     {

     // SchemaTwo Inserts the Data in the same way as the Default method

     base.InsertUserDetails(username, realName, encryptedPassword);

     // But adds some New Steps

     InsertDetailsForABCSystem();

     GrantPermissionsForXYZ();

     }

     private void InsertDetailsForABCSystem()

     {

     Console.Write("Schema Two Behaviour\n

     Inserting Details into ABC System\n\n");

     }

     private void GrantPermissionsForXYZ()

     {

     Console.Write("Schema Two Behaviour\n

     Grant Permissions For XYZ\n\n");

     }

     }

    We can try out this modified functionality by creating an instance of our new class

     SecurityManager securityManager = new SchemaTwoSecurityManager();

     securityManager.CreateUser("daltonr", "Richard Dalton", "GuessThis");

    The console output is as follows:

Using Subclasses

    Instantiating and using subclasses seems to be an area that causes some confusion for those learning about inheritance. The problem seems revolve around keeping track of which version of a method will be executed.

    In our example we have a base class called SecurityManager. So, we can write some code that uses this class.

     static void DoStuffWithSecurityManager(SecurityManager securityManager)

     {

     securityManager.CreateUser("daltonr", "Richard Dalton", "GuessThis");

     }

    OK, a pretty useless routine, but you get the idea, the method accepts a SecurityManager Object and calls the only public method that it has, CreateUser. The key thing to note is that any class that inherits from SecurityManager can be passed into this routine, and it will work.

     SecurityManager securityManager = new SecurityManager();

     DoStuffWithSecurityManager(securityManager);

Report this document

For any questions or suggestions please email
cust-service@docsford.com