65.9K
CodeProject is changing. Read more.
Home

StealthObjectFactory

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.91/5 (7 votes)

Mar 30, 2006

CPOL

5 min read

viewsIcon

23370

downloadIcon

207

A brief introduction to StealthObjectFactory, simple OOP scripting framework for .NET Framework 2.0

Introduction

StealthObjectFactory is a simple scripting framework. It enables you to add scripting capabilities to your application easily, yet preserve object-oriented design along the way.

Background

The term stealth came from the scripting idea (more explanation after this). The term object factory came from factory pattern. When both are combined, it produces the capability to perform factory pattern that can produce/instantiate an object whose type is unknown at runtime via scripting. I host this project at GotDotNet. I created this when I was working on one of my projects, which requires charting feature, but the type of chart to be developed isn't decided yet. Therefore, I decided to create this simple helper project to inject scripting feature. Actually it can also be achieved by factory pattern via assembly loading at runtime, however scripting has the advantage of ease of deployment and no need to compile.

Using the Code

In order to use StealthObjectFactory, only two simple steps are required

  1. Define StealthObject template
  2. Write the script code to be "injected" into the StealthObject

There are two ways to use StealthObjectFactory, inline code and external code. Inline code means both the StealthObject template and the script code is defined in App.config. On the other hand, external code means that the StealthObject template is defined in App.config but the script code is defined in an external file. Internal code is recommended when the StealthObject will have only one implementation, but it tends to change a lot and you want to solve it by using script. External code has the advantage of letting the client choose which implementation is to be executed in StealthObject, because the script code resides in an external file and there could be more than one implementation that can be chosen at run-time.

Internal Code

This section is defined in App.config. It contains a template of StealthObject to be produced. For example, let's say we have a class, MyBusinessLogic, that implements basic business logic and makes some part of logic abstract (those which tend to change a lot).

namespace MyNamespace
{
    public abstract class MyBusinessLogic
    {
        public int CalculateSomeCommonLogic(int param1, int param2)
        { return param1 * param2; }
 
	   // This will change a lot since the logic is not fixed yet
            public abstract int CalculateSomeNewLogic(int param1, int param2); 
    }
}

Let's say in the CalculateSomeNewLogic method, we want to calculate phytagoras value of those two integer parameters. Below will be the StealthObject template along with the script code:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="StealthConfiguration" 
	type="StealthObjectFactory.Configuration.StealthConfigurationSectionHandler, 
	StealthObjectFactory" />
  </configSections>
 
  <StealthConfiguration>
    <StealthObject name="SO1" returnClass="QuickStart.MyExtendedBusinessLogic">
      <Using>
        <add namespace="System" />
      </Using>
      
      <Namespace name="QuickStart">
        <Class name="MyExtendedBusinessLogic" extends="QuickStart.MyBusinessLogic">
          <Method override="true" name="CalculateSomeNewLogic" 
			returnType="System.Int32">
            <Parameters>
              <Parameter name="param1" type="System.Int32" />
              <Parameter name="param2" type="System.Int32" />
            </Parameters>
            <Code>
              <![CDATA[return (int)Math.Sqrt((param1 * param1) + 
					(param2 * param2));]]>
            </Code>
          </Method>
        </Class>
      </Namespace>
    </StealthObject>
  </StealthConfiguration> 
</configuration>

After defining the StealthObjectFactory template, we're ready to use it. Below is the full code used to call StealthObjectFactory:

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Text;
using StealthObjectFactory;
 
namespace QuickStart
{
    public abstract class MyBusinessLogic
    {
        public int CalculateSomeCommonLogic(int param1, int param2)
        { return param1 * param2; }
 
         // This will change a lot since the logic is not fixed yet
         public abstract int CalculateSomeNewLogic(int param1, int param2); 
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            StealthObject sObj = StealthConfiguration.Load();
 
            MyBusinessLogic instanceObj = sObj.GetObject() as MyBusinessLogic;
            instanceObj.CalculateSomeNewLogic(1,1);
        }
    }
}

Code Explanation

The first class in the namespace is the abstract class that we want to extend via scripting. Currently StealthObjectFactory only supports scripting via extending the existing class. As a matter of fact, it is possible to use it without extending any class, but the return type will be completely unknown at runtime and the only way to use it is by reflection (which is not that easy). Next is the main program. The first line basically orders StealthObjectFactory to load the configuration from App.config file. The second line is to get the default object (based on StealthObject template defined in App.config combined with the script code) and cast it right away. And that’s it! The next line is just using the object as usual.

Just for your information, as you can see in the example above, when extending the existing class from StealthObject, we should override the abstract method from the abstract class). Complying with the original purpose of this project, which is to simplify things, StealthObjectFactory will automatically override all required methods if you don't specify one. This way you won't have to worry about any compile error regarding unimplemented abstract methods from the super class.

External Code

Another way to use StealthObjectFactory is external code. Contrary to internal code, the script code (which resides in the CDATA section is code node), we can “extract” the script code to be defined in one or more external files.

For the sake of explanation, let’s just use the previous example. In this case, let’s assume that the complexity has been increased that we want to have several implementations at the same time and let the user choose which implementation is to be used. Below is the StealthObject template in App.config (which is actually quite similar to the Internal Code one).

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="StealthConfiguration" 
	type="StealthObjectFactory.Configuration.StealthConfigurationSectionHandler,
	StealthObjectFactory" />
  </configSections>
 
  <StealthConfiguration>
    <StealthObject name="SO1" returnClass="QuickStart.MyExtendedBusinessLogic">
      <Using>
        <add namespace="System" />
      </Using>
      <Namespace name="QuickStart">
        <Class name="MyExtendedBusinessLogic" extends="QuickStart.MyBusinessLogic">
          <Method override="true" name="CalculateSomeNewLogic" 
		returnType="System.Int32" variable="MyCustomLogic">
            <Parameters>
              <Parameter name="param1" type="System.Int32" />
              <Parameter name="param2" type="System.Int32" />
            </Parameters>
          </Method>
        </Class>
      </Namespace>
    </StealthObject>
  </StealthConfiguration> 
</configuration>

You might notice that there is a new attribute in the Method node, which is variable. This is to indicate that the content (code) will be replaced by a variable called MyCustomLogic. Maybe you're asking, where would this variable called MyCustomLogic came from. Well, earlier in this section, I've mentioned about putting the code in an external file. This external file would be an XML file with variable(s) inside, which will be loaded later by StealthObjectFactory and injected into the StealthObject template. Below are two examples of external XML code files.

Dummy Logic (Code 1)
<?xml version="1.0" encoding="utf-8"?>
<StealthVariable>
  <Variable name="MyCustomLogic">
    <![CDATA[return 0;]]>
  </Variable>
</StealthVariable>
Multiplication Logic (Code 2)
<?xml version="1.0" encoding="utf-8"?>
<StealthVariable>
  <Variable name="MyCustomLogic">
    <![CDATA[
      // Do some heavy math calculation
      return param1 * param2;
    ]]>
  </Variable>
</StealthVariable>

The code required to use this new stuff is as simple as the previous one. Here is the code (with the assumption that it will load the variable database from an XML file called Var1.xml. If you happen to have two external codes as in the example above, you would have two XML files (e.g. Var1.xml and Var2.xml). You can dynamically choose which variable database to use, by setting the Variable property of the StealthObject.

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Text;
using StealthObjectFactory;
 
namespace QuickStart
{
    public abstract class MyBusinessLogic
    {
        public int CalculateSomeCommonLogic(int param1, int param2)
        { return param1 * param2; }
 
	// This will change a lot since the logic is not fixed yet     
	public abstract int CalculateSomeNewLogic(int param1, int param2); 
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            StealthObject sObj = StealthConfiguration.Load();
            sObj.Variable = StealthVariable.LoadFromXml("Var1.xml");
 
            MyBusinessLogic instanceObj = sObj.GetObject() as MyBusinessLogic;
            instanceObj.CalculateSomeNewLogic(1,1);
 
            Console.ReadLine();
        }
    }
}

Points of Interest

During the development of this project, I learnt a lot about Reflection and CodeDom. I also happened to come across some problems regarding void variable type. If you see the source code, you will notice that I forced void variable type to be interpreted as void instead of System.Void, because for some reason the compiler won't accept it. This will be the special case contrary to the other variable type where I would interpret it using its full type name (i.e. int will be interpreted as System.Int32). I tried to find other solution, and I came across one possible solution by using code compile unit (instead of using my automatically generated code) that I suspect could improve the performance, but it would degrade the flexibility because it can't compile custom code. Anyway, I've posted this project at GotDotNet. You can help with the development if you happen to like this stuff.

History

  • 30th March, 2006: Initial post