Refactoring to State/Strategy
 
Published: 15 May 2007
Abstract
This article will take a class that was implemented and refactor it to the state/strategy pattern.
by Brian Mains
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 21895/ 39

Introduction

Sometimes it is more costly to implement an object as it is. Because of that, it is easier to refactor the design into an alternative approach. It can be more efficient and easier to maintain and I am going to illustrate this with a class I originally created that needs to be refactored to a better design.

Parameter Checking Example

The class that will be refactored is the ParameterCheck class. This class defines parameters using reflection.

Listing 1

using System;
using System.Reflection;
 
namespace Nucleo.Reflection
{
  public class ParameterCheck
  {
    private object _argument = null;
    private ParameterCheckType _type;
 
    private enum ParameterCheckType
    {
      Any, Type, NotNull, Null
    }
 
    private ParameterCheck(ParameterCheckType type)
    {
      _type = type;
    }
 
    private ParameterCheck(ParameterCheckType type, object argument): this(type)
    {
      _argument = argument;
    }
 
    public static ParameterCheck Any()
    {
      return new ParameterCheck(ParameterCheckType.Any);
    }
 
    public static ParameterCheck IsOfType(Type type)
    {
      return new ParameterCheck(ParameterCheckType.Type, type);
    }
 
    public static ParameterCheck IsNotNull()
    {
      return new ParameterCheck(ParameterCheckType.NotNull);
    }
 
    public static ParameterCheck IsNull()
    {
      return new ParameterCheck(ParameterCheckType.Null);
    }
 
    internal static bool ProcessIsOfType(object source, Type type)
    {
      if (source != null)
        return source.GetType().IsAssignableFrom(type);
      else
        return false;
    }
 
    internal static bool ProcessIsNotNull(object source)
    {
      return (source != null);
    }
 
    internal static bool ProcessIsNull(object source)
    {
      return (source == null);
    }
 
    internal static bool ProcessObject(object source, ParameterCheck check)
    {
      if (check._type == ParameterCheckType.Any)
        return true;
      else if (check._type == ParameterCheckType.NotNull)
        return ProcessIsNotNull(source);
      else if (check._type == ParameterCheckType.Null)
        return ProcessIsNull(source);
      else if (check._type == ParameterCheckType.Type)
        return ProcessIsOfType(source, (Type)check._argument);
      else
        return true;
    }
  }
}

Now, this class is not exactly terrible in size at the moment.  However, as we add more parameter checks, it will quickly grow in size.  Because of this, a better way to represent this object is to use the state/strategy pattern.

Introduction to State/Strategy

Using the example of the class in Listing 1, the state/strategy pattern will move each parameter check into its own object, instead of creating a separate method for it.  The key to implementing the state/strategy pattern is to have a base class (ParameterCheck) that the objects in the pattern will derive from.  Below is a diagram of what this relationship will look like.

Figure 1

Each object will override the IsValid method in ParameterCheck to provide a more specific validation check.  The State/Strategy pattern works in this way, using inheritance to provide a means to add unlimited number of derived classes.  Both the State and Strategy pattern works like this; however, state is often used for representing a state object and strategy is often used for an algorithm of sorts.  Compare the following ParameterCheck definition over the previous code sample above.

Listing 2

public abstract class ParameterCheck
{
  public abstract bool IsValid(object data, object argument);
}

That is quite a difference.  ParameterCheck is now providing an interface that all derived types must implement and so most of the actual processing is now present in the concrete implementations TypeParameterCheck, NullableParameterCheck, etc.  To see what it looks like, the following is the definition of TypeParameterCheck.

Listing 3

public class TypeParameterCheck: ParameterCheck
{
  public override bool IsValid(object data, object argument)
  {
    if (!argument is Type)
      throw new ArgumentException("The argument type is not valid");
 
    return (data.GetType().IsAssignableFrom((Type)argument));
  }
}

The following is the definition of NullableParameterCheck, which takes a boolean value stating whether to ensure that a value is null or to ensure it is not null, based on the value of the parameter.

Listing 4

public class NullableParameterCheck: ParameterCheck
{
  public override bool IsValid(object data, object argument)
  {
    bool mustBeNulls;
 
    if (argument == null)
      mustBeNulls = false;
    else
      argument = (bool)argument;
 
    if (mustBeNulls)
      return (data == null);
    else
      return (data != null);
  }
}

Any number of derivatives can be added.  For instance, assuming that we wanted to add the ability to validate based on a list of values, we can do.

Listing 5

public class ValuesRangeParameterCheck: ParameterCheck
{
  public override bool IsValid(object data, object argument)
  {
    if (!argument is object[])
      throw new ArgumentException("The argument must be an array");
 
    List < object > values = new List < object > ((object[])argument);
    return values.Contains(data);
  }
}

And the list could go on, based on whatever requirements we may have.  Inheritance allows the ability to create N number of parameter checks without us having to have a definite list.  However, there has to be a means to expose each one, so at some point they need to be defined.  A common way is to create a custom configuration section, which can have an array of parameter check class references.  This is a similar concept to the membership element, which has a list of providers that it contains.  An alternative to this is to use a Factory method, which can take a string, enum, or some other object that uniquely references the correct parameter check.

Conclusion

The state/strategy pattern is an effective way to reduce redundancy and increase the simplicity of the object design.  It makes it easier to expand upon the feature much easier as well.



User Comments

No comments posted yet.






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-29 4:31:21 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search