Refactoring to Adaptive Object Modeling: Type-Object
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 Type-Object methodology of Adaptive Object Modeling. Type-Object methodology maintains that the exact type(s), attributes and operations of the shared object data are determined only at runtime through the entity object's entity-type based on the meta-data used to build the instance object.
BackGround
Using a Type-Object design method is an interesting way to extend the configuration possibilities of simple class object methodology. It gives a way to define a class and it's attributes and operations dynamically, or at runtime, from a database, configuration file or user interface. Virtually any meta-data source can define the entity-type, attributes and operations for a given AOM class structure.
Here we see the exact Adaptive Object Modeling UML for the Type-Object class, based on the PLoPD3 Johnson and Woolf model. Notice that the Entity object accepts and has specific attributes and operations of the EntityType object, while the EntityType object adapts any available attributes and methods, giving complete flexibility to the entity object to add or subtract these pieces as the environment demands.
How to use the code
We start this refactoring example with the classes ExecutiveJob
and EmployeeJob
, which both have a simple enough class structure,
initialized with a basic constructor. For this example, we need to make these
objects more flexible and sensitive to our ever-changing business needs. To
accomplish this we need to define dynamically the attributes and operational
methods or business rules and relationships associated with this class, as well
(and most importantly for this example) we need to dynamically define it's class
type. For this example we will only show the conversion to the type-object pattern.
public class ExecutiveJob { private string _attribute1; private string _attribute2; public ExecutiveJob() { } ...attributes accessors and methods } public class EmployeeJob { private string _attribute1; private string _attribute2; public EmployeeJob() { } ...attributes accessors and methods }
The first step to the AOM model is to define the Type of the object
that we wish to add metadata to. This class holds all possible attributes for
an entity object, and defines the operational methods as well. For this example
both the attributes and operational method instances are omitted, since they
are outside the scope of the example. The EntityType
object in
this example acts as a dynamic template for the class type. We have two EntityType
objects ExecutiveJobEntityType
and EmployeeJobEntityType
,
which serve to define the actual limits of the class Entity definition. (In
later articles we will see the significance of this class and how it helps to
define how oue class can dynamically be set to interact within the system.)
public class EntityType { private string _typeName; private Type _type; 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;} } }
We now can create our JobEntity
class, which accepts EntityType,
and is a meta class containing it's specific attribute and operations in it's
EntityType realtionship at runtime. Notice that the class now has no attributes
or methods. We will allow the meta-data at runtime to define these for us through
the EntityType
(which is outside the scope of this article). We
associate our entity type class with our entity class in this fashion, giving
our entity flexibility to define it's class type from run time meta-data. The
EntityType class will hold all the attributes and methods for the class, allowing
different functional types to be described in an ad-hoc fashion. Below this
class is the implementation meta-class ExecutiveJobEntityType
and
EmployeeJobEntityType
, which will contain only the necessary attributes
and operations for the class type we want, and helps to define our class.
NOTE: Remember, reflection is used heavily as we will see in the factory class, and is the key to dynamic representation of the different type variations of the class.
public class JobEntity { private EntityType _entityType; public JobEntity(EntityType entityType) { _entityType = entityType; Console.WriteLine("..." + this.GetType().Name + " initialized with EntityType " + _entityType.Name); } public EntityType EntityTypeOf { get{return _entityType;} } } public class ExecutiveJobEntityType : EntityType { public ExecutiveJobEntityType(string name, Type type) : base(name,type) { Console.WriteLine("..." + this.GetType().Name + " initialized."); } } public class EmployeeJobEntityType : EntityType { public EmployeeJobEntityType(string name, Type type) : base(name,type) { Console.WriteLine("..." + this.GetType().Name + " initialized."); } }
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 and is considered the Interpreter model
of the AOM pattern. 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 is the meta-data for the entitytype. This is important because we will use this data to build and define the class and which class type to associate with it. We retrieve from our meta-data in the config file the name which we will refer to the class, and the full type name. (In a later article we will also retrieve the attributes and operations for the entity type.)
string name = Convert.ToString(hash["name"]); //the name of this attribute if(name == null || name == string.Empty) throw new FactoryException("No entity name specified."); //get the entity type string strEntityType = Convert.ToString(hash["type"]); if(strEntityType == null || strEntityType == string.Empty) throw new FactoryException("No Entity Type specified for [type]"); //get the type for a strongly typed parameter Type entityTypeOf = Type.GetType(strEntityType); if(entityTypeOf == null) throw new FactoryException("Could not load class Type for type " + strEntityType + " for entity " + name);
The meta-data from the config file appears thus:
<entityTypes>
<entityType name="ExecutiveJobEntityType" type="TypeObject.ImplClasses.ExecutiveJobEntityType" />
<entityType name="EmployeeJobEntityType" type="TypeObject.ImplClasses.EmployeeJobEntityType" />
</entityTypes>
Next we check to see if the hashtable that will hold our EntityType classes exists and contains the current data. If not we will use .NET's class activation methods to instantiate a new class of the correct type and pass in the appropriate parameters, in this case the name and type.
if(_entityTypes == null) _entityTypes = new Hashtable(); EntityType entityType = (EntityType)Activator.CreateInstance ( entityTypeOf,new object[]{name,entityTypeOf}); if(!_entityTypes.Contains(name)) _entityTypes.Add(name,entityType);
Next we are going to load the actual Entity class. We have three items of data per class, name, class type and entity type.
//the name of this attribute if(name == null || name == string.Empty) throw new FactoryException("No entity name specified."); //get the entity type string strEntityClassType = Convert.ToString(hash["type"]); if(strEntityClassType == null || strEntityClassType == string.Empty) throw new FactoryException("No Entity Class specified for " + strEntityClassType); //get the type for a strongly typed parameter Type entityClassType = Type.GetType(strEntityClassType); if(entityClassType == null) throw new FactoryException("Could not load class Type for type " + strEntityClassType + " for entity " + name); //get the entity type string strEntityType = Convert.ToString(hash["entityType"]); if(strEntityType == null || strEntityType == string.Empty) throw new FactoryException("No Entity Type specified for " + strEntityType);
The meta-data from the config file appears thus:
<entities>
<entity name="ExecutiveJob"
type="TypeObject.ImplClasses.JobEntity"
entityType="ExecutiveJobEntityType" />
<entity name="EmployeeJob"
type="TypeObject.ImplClasses.JobEntity"
entityType="EmployeeJobEntityType" />
</entities>
Next we see we check to see if the hashtable that will hold our Entity classes exists and contains the current data. If not we will use .NET's class activation methods to instantiate a new class of the correct type and pass in the appropriate parameters, in this case the entity type.
if(_entities == null) _entities = new Hashtable(); JobEntity entityClass = (JobEntity)Activator.CreateInstance( entityClassType,new object[]{_entityTypes[strEntityType]}); if(!_entities.Contains(name)) _entities.Add(name,entityClass);
Notice we only need the name and actual full type name of the class object and the entity type as a parameter to build this class. This is because the attributes and operational methods will be dynamically loaded to this class type at runtime and are contained in other parts of the config file (out of scope for this article). The attributes and parameters are associated with the EntityType, not the class, so they are decoupled from the class at compile time.
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 the proper EntityType object has been loaded and yielded expected results.
What have we accomplished? Well by looking at this article alone, it seems
little. But remember, this is just the first step. We started with two fairly
static classes ExecutiveJob
and EmployeeJob
, which
we have now migrated to the AOM pattern. From here we will begin to add Properties,
Associations, Strategies (or operational methods), and RuleObjects
(business function objects which define where and how we deal with the code
flow). All these further innovations will help define our adaptive object model
further and make it into a working schema.
Expanding the Example
How can we expand this example to functional code? We might expand the different ways we input and build object with meta-data, either by using a dynamically configurable data source like a database, or by allowing meta-data to stream in from a client or user interface. The first step is to define the constraints of the application, then to see how many different items in code you can provide meta-data for. Your application meta-data can be from settings changed by a power user, or by any client that you want to be allowed to configure a piece of the application.
Points of Interest
This is the first 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.
Related Articles
Other articles in this series include:
- Type-Object
- Properties
- Strategies
- Entity-Relationships and Accountability/Cardinality
- Ghost Loading, Data Mapping...
- Interpreter methodologies
- Discussions on Practical Project usefulness of design.
- 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.