LasaterConsulting.com

Design Patterns
Type-Object | Properties | Strategy | Entity Relationship | Interpreter | Ghost Loading, Data Mapping... | Project Usage | AOM UI

Refactoring to Adaptive Object Modeling: Strategy

 

Introduction

Adaptive Object Modeling is a relatively new 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. It's 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.

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 a 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's 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 EntityTypes, 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 we have added also 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 also have created functional method objects inheriting from IOperation, which define 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 concern to it's 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 now are 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 actually will 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 as needed to specific entities. The class with the methods we wish to add inherits from the IOperation interface, providing access to it's 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 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 see 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 it's 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 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 need of the code itself. Patterns and Adaptive Models are only design templates, helpers to accommodate better overall design. I might stress that making the effort to use advanced design methodologies will strengthen your overall design ability, but like your basic coding skills, it is something learned and cultivated.

If this or any other in this series on adaptive object modeling design is helpful 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...
  6. Interpreter methodologies
  7. Discussions on Practical Project usefulness of design.
  8. Discussions on the AOM framework, UI, and Advanced Concepts

History

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

Credits

Thanks to: Joe Yoder, Ralph Johnson, for their various articles on the AOM pattern, Martin Fowler for his interest in my articles and his work on the Accountability pattern, and Doga Oztuzun, for his insight and sharing of his relevant project data. Also to anyone else I have forgotten.

| Make a Donation to Help Build/Maintain our site! |©2004 Lasater Consulting

1