Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Refactoring to Adaptive Object Modeling: Strategy Pattern

0.00/5 (No votes)
20 Jun 2005 5  
Strategy methodology maintains that the operations and business rules or code flow of a class should be held as a collection of properties, as opposed to single method calls, which can be changed at runtime.

Introduction

Adaptive Object Modeling is a relatively unrealized concept in object oriented design that has found some roots in advanced development processes. It allows that applications are in continual flux (or their requirements are) from adjustments to business rules, environments and user requirements. Its precepts are that using metadata for business objects, rules and process flows, instead of hard coded, static method code, makes the application more flexible to user/client needs. The AOM design model also requires that certain domain 'facts' are maintained, i.e. that there are controlled interpretations of the effects of the 'immediate' changes from user-generated metadata.

Adaptive solutions offer us a way to make applications more configurable (regarding workflow) to the end users. It is not a 99% solution and, to my knowledge there is no end-to-end solution that meets all needs, simply because those needs are more directly defined by the business requirements. However, since business requirements change frequently, writing immutable code flow logic leaves very short lives for code. Even the online industry is feeling the need to configure dynamically: if you are a subscriber to most web portals like Yahoo! or MSN you have a user configurable home page where you can decide which items of interest you would like to display on your page.

Most portal technologies are indirectly pointing us to a more adaptive model, simply because they are configurable by end-users or power-users. I see adaptive technologies like Adaptive Object Modeling in the same light as a web portal: some functionality is needed to be coded by developers, like database and business logic, but where and when this logic is implemented should be (following business trends) decided upon by business experts, not developers. Some adaptive models incorporate the non-meta-data into tier models tightly coupled with tier meta-data, which is one way to do it, but I prefer to let the EUD (end user developer) have the hand in how business data is handled, and simply allow the AOM framework to give the actual implementation of when and where back to business experts (which most developers don't have the bandwidth to be).

To better understand and practice the techniques of AOM design, I am writing this and several other articles that explain the usage and conversion (or refactoring effort) from a static code model to AOM design.

This article will deal with Strategy methodology of Adaptive Object Modeling. Strategy methodology maintains that the operations and business rules or code flow of a class should be held as a collection of properties, as opposed to single method calls, which can changed at runtime. A Strategy pattern is a set of algorithms. So a Strategy pattern as it relates to AOM is a group of different strategies that can be dynamically added as business rules to an entity at runtime.

Background

Using a Strategy design method is an interesting way to extend the configuration possibilities of simple class object methodology. It gives a way to define a class' operations and rules dynamically, or at runtime, from a database, configuration file or user interface. Virtually any meta-data source can define the operations for a given AOM class structure. The Strategy design method uses, in this example, interface contracts and reflection to help define the limits of the operational calls.

Here we see the exact Adaptive Object Modeling UML for the Strategy class pattern based on the Design Patterns GOF95 model. Notice that the Entity object accepts and has specific operations (or strategies) associated with it. The strategy deals with actual business rule and operational rule implementations.

How to use the code

We start this refactoring example where we left off from our other example article Properties. We still have the same entity and entity types, and now we would like to dynamically add some operational methods or Strategies. To accomplish this we need to create a class to hold our dynamic interface contracts that are loaded during runtime. This takes the form of a simple container (or entity-attribute type) object that holds the interfaces or contracts for speaking to different strategies. This is loaded with metadata at runtime for the specific object operation that we wish to implement. Notice that we have added a collection object OperationsCollection, which contains the shared operations between the entity and the entity type:

public interface IOperation
{
    void OperationMethod(object[] parameters);
}

//Collection of attributes


public class OperationsCollection
{
    public void Add(IOperation obj)
    {
        //.....addition code to collection

    
    }

}

Here we see that the EntityType object from our last example has an added parameter of type OperationsCollection. This operations collection will allow the Entity object to have dynamically loaded meta-data driven business rules associated with it at runtime. Now we can store any given business rule we like within our class objects, without recompile:

public abstract class EntityType
{                    
    private string _typeName;
    private Type _type;
    private EntityAttributeCollection _attributes = 
                         new EntityAttributeCollection();
    private OperationsCollection _operations = 
                         new OperationsCollection();

    public EntityType(string name, Type type)
    {
        _typeName = name;            
        _type = type;    
    }   
            
    public string Name
    {
        get{return _typeName;}
        set{_typeName = value;}                    
    }        
            
    public Type TypeOf
    {
        get{return _type;}
        set{_type = value;}                    
    }
            
    public EntityAttributeCollection Attributes
    {
        get{return _attributes;}
        set{_attributes = value;}                    
    }

    public OperationsCollection Operations
    {
        get{return _operations;}
        set{_operations = value;}                    
    }
            
}

We have also created functional method objects inheriting from IOperation, which define the exact method operations we will allow to be adapted to our entity object. Having the interface allows us to tie that interface to any class method we wish, as long as that class implements IOperation.

Note: The operational methods could be contained in other assemblies, the namespace encapsulation becomes unimportant, because you can add attributes or methods via the metadata, without any concern to its source at compile time. Also the operation method interface IOperation could be changed to dynamically define strongly type parameters as well, but this will be saved for either another article or your own invention. Remember, reflection is used heavily as we will see in the factory class, and is the key to dynamic representation of contractual interfaces.

public class JobOperation1 : IOperation
{
    void IOperation.OperationMethod(object[] parameters)
    {
        Console.WriteLine("..." + this.GetType().Name + 
                        ".OperationMethod method called.");
        foreach(object obj in parameters)
            Console.WriteLine("... parameter Type:" + 
                          obj.GetType() + " Value:" + obj);  
    }
}          

public class JobOperation2 : IOperation
{
    void IOperation.OperationMethod(object[] parameters)
    {
        Console.WriteLine("..." + this.GetType().Name + 
                      ".OperationMethod method called.");
        foreach(object obj in parameters)
            Console.WriteLine("... parameter Type:" + 
                       obj.GetType() + " Value:" + obj);  
    }
}

We are now ready to focus on the AOMFactory class, which is a static factory implementation loosely based on Deyan Petrov's DataSourceFactory. The factory is where we will actually load our runtime data for this example. Our example code will use an XML config file for the actual meta-data, but in actual project code, as I stated above a variety of sources are available, not in the least, the client or user interface. Remember, this factory class is only for this example, and while I would recommend a factory or builder pattern for this piece, it is up to you the developer to find the suitable method for loading your classes from meta-data.

The first piece of data we get after this revision to our Properties example is the meta-data for the operation. We will use this data to build and define all the possible types of operational interfaces we can load to our entity types, making them available to specific entities. The class with the methods we wish to add inherits from the IOperation interface, providing access to its methods directly.

string name = Convert.ToString(hash["name"]);
//the name of this attribute

if(name == null || name == string.Empty)
    throw new FactoryException("No operation name specified.");

//get the attribute type

string strOperationType = Convert.ToString(hash["type"]);
if(strOperationType == null || strOperationType == string.Empty)
    throw new FactoryException("No Type specified for operation " + name);
//get the type for a strongly typed parameter

Type operationType = Type.GetType(strOperationType);
if(operationType == null)
    throw new FactoryException("Could not load class Type for type " + 
                         strOperationType + " for operation " + name);

Here we make sure the class implements the interface, and creates an instance of the interface from the class type that holds the wanted operational methods. As we said above this gives a lot of flexibility to the business flow, and allows different assemblies to provide new data on the fly.

Type interfaceType = 
   operationType.GetInterface(typeof(IOperation).FullName);
if(interfaceType == null)
    throw new FactoryException("No interface of type IOperation " + 
                                   "exists for operation " + name);    
         
IOperation operation = 
            (IOperation)Activator.CreateInstance(operationType);

if(_entityOperations == null)
    _entityOperations = new Hashtable();

if(!_entityOperations.Contains(operation))
    _entityOperations.Add(name,operation);

The config file defines the different operations by name and their implementation classes full type name. As we said above, the actual operational namespace can be internal or external. The meta-data from the config file appears thus:

 <entityOperations>
        <entityOperation name="JobOperation1" 
            type="Strategy.ImplClasses.JobOperation1" />
        <entityOperation name="JobOperation2"             
            type="Strategy.ImplClasses.JobOperation2" />
 </entityOperations>

Next we check to see if the hashtable that will hold our Strategy interfaces exists and contains the current data. If not we will add the new operation to the entityType object.

EntityType entityType = (EntityType)Activator.CreateInstance(entityTypeOf,
                                    new object[]{name,entityTypeOf});
foreach(string attribute in attributes)
     ....
foreach(string operation in operations)
  if(!entityType.Operations.Contains((IOperation)_entityOperations[operation]))
    entityType.Operations.Add((IOperation)_entityOperations[operation]);

The meta-data from the config file appears as below. Notice the operations XML node. This is where we put all the possible operation types by name for each EntityType instance. The operation names are comma delimited. This is how we define which strategy and business rule relationships we will associate with the entity instance.

    <entityTypes>
        <entityType name="ExecutiveJobEntityType" 
            type="Strategy.ImplClasses.ExecutiveJobEntityType" 
            attributes="Attribute1,Attribute3"
            operations="JobOperation1,JobOperation2" />
        <entityType name="EmployeeJobEntityType" 
            type="Strategy.ImplClasses.EmployeeJobEntityType" 
            attributes="Attribute2,Attribute4" 
            operations="JobOperation2" />
    </entityTypes>

We now are at a point where we can test our code.

Here we see that the entity is first established from the entity type and its operations have been called.

Expanding the example

How can we expand this example to functional code? We need to establish how the operational methods influence the program flow, and define our entity relationships and entitytype relationships if applicable. These items we will cover in the next article.

Points of interest

This is the third installment in the series I am writing on real world adaptive object modeling. All examples and the bulk of this article are taken from my professional experience as an architect. The examples given are templates only, and the designer must keep in mind that they are the ones who must decide where different patterns, if any, may be best used in their code.

Deciding to perform a refactoring effort from existing code to a pattern or enhanced design model must be weighed on the necessity and the need of the code itself. Patterns and Adaptive models are only design templates, helpers to accommodate better overall design. I must stress that making an effort to use advanced design methodologies will strengthen your overall design ability, but like your basic coding skills, it is something that is to be learnt and cultivated.

If this or any other in this series on adaptive object modeling design is helpful to you or you have questions or comments, please e-mail me at: chris.lasater@gmail.com.

Related Articles

Other articles in this series include:

  1. Type Object
  2. Properties
  3. Strategies
  4. Entity-Relationships and Accountability/Cardinality.
  5. Ghost Loading, Data Mapping and more...
  6. Interpreter methodologies (coming soon).
  7. Discussions on Practical Project usefulness of design.
  8. Adaptive Object Modeling: Final discussions and MVC.

History

This is the first revision and is the third installment in a series.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here