dcsimg
Pattern for sharing a process among multiple objects while tailoring the process.
1 posts in topic
Flat View  Flat View
TOPIC ACTIONS:
 

Posted By:   Bill_McDonald
Posted On:   Friday, January 14, 2005 02:27 PM

I’m looking for a pattern that will help me re-factor some code so that a general main process that is tailored by IF statements is re-designed so that the main process isn’t cluttered with non-main-process code/references. I am trying to re-factor code that has a lot of 'if' code. By this I mean that it has a general process that it follows, but it's tailored in various places in the process depending upon which kind of job we're trying to process. If this job type were stored in a variable called job_type, my code looks like: Process() { if ( (job_type='C' || job_type='X') && !IsValid(processing_date) ) throw ProcessingError; CreateQuery(job_type); if ( job_type='P' )    More>>

I’m looking for a pattern that will help me re-factor some code so that a general main process that is tailored by IF statements is re-designed so that the main process isn’t cluttered with non-main-process code/references.


I am trying to re-factor code that has a lot of 'if' code. By this I mean that it has a general process that it follows, but it's tailored in various places in the process depending upon which kind of job we're trying to process. If this job type were stored in a variable called job_type, my code looks like:


			
Process() {

if ( (job_type='C' || job_type='X') && !IsValid(processing_date) ) throw ProcessingError;

CreateQuery(job_type);

if ( job_type='P' )
CalculateExchangeRate();

OutputResults();
}

The point is that the general flow (CreateQuery, OutputResults) is used across all job types, but you can see that the way this process is 'tailored' is by putting IF statements with 'side-code' depending upon the job_type.



My approach is to encapsulate the things that are in IF statements into their own classes, instantiate the class I want depending on the job_type, and call Process(). I would make the IF statements into functions, put the main process in the superclass's Process method, and only redefine the functions (or override its empty definition in the Parent class) for those functions that pertain to the subclass they belong to (eg. overriding CalculateExchangeRate since it pertains to PJob only).



			
class Job
{
Process() {
ValidateDate(processing_date); // step 0.
CreateQuery(job_type); // step 1
CalculateExchangeRate(); // step 1.1
OutputResults(); // step 2
}
ValidateDate(date dt) { // empty definition }
CalculateExchangeRate() {// empty definition }
CreateQuery(char job_type) { // create query };
OutputResults() { // output results };
}

class CJob extends Job
{
ValidateDate(date dt) {
if (!IsValid(processing_date) ) throw ProcessingError;
}
}

class PJob extends Job
{
CalculateExchangeRate {
// Do exchange rate calculations....
}
}


The thing I don't like about this setup is:


1. The CalculateExchangeRate method is in the Job object when it really only pertains to a PJob process. Its presence in the main process (in class Job) clutters up the main process. If I'm analyzing the code, I want a view of the main process code that doesn't even refer to calculating exchange rates UNLESS I'm analyzing the PJob process.


2. ValidateDate has the same problem, although it seems less distractive when reading the main process. This type of data qualification however could be done by the CJob object itself when it is created.



Ideally, I'd like to write PJob so that it, "uses the general process, but between step 1 and 2, do PJob->CalculateExchangeRate().




///////////////////////////////////////////////////




My next approach was to make the steps in the main process their own objects.


			
class Step_ValidateDate extends Step {
Job m_job;
Step_ValidateDate(Job job) { m_job = job; }
Execute() { m_job->ValidateDate(); }
}

and insert this object into a process tree at a particular branch point.


			
class CJob extends Job
{
CJob() {
m_tree.Insert("0.1", new Step_ValidateDate(this));
// "0.1" puts it after Job step 0 and before Job step 1
}
}

The main process is initialized in the Job class and the tailored parts are inserted in the initialization (ctor) of the specific Job class (eg. CJob). The point of the constructors is to create a process tree which represents the entire process and then simply go through each node in the tree sequentially to execute the process; see Job::Process():


			
class Job {
Job() {
tree.insert( "1", new Step_CreateQuery(this) ); // step 1
tree.insert( "2", new Step_OutputResults(this) ); // step 2
}
Process() {
loop thru tree
tree[i]->Execute();
}
CreateQuery() { // create query and execute it };
OutputResults() { // output results };
Tree tree;
}

/****** Step Objects *****/
class Step_CreateQuery extends Step {
Job m_job;
Step_CreateQuery(Job job) { m_job = job; }
Execute() { m_job->CreateQuery(); }
}

class Step_OutputResults extends Step {
Job m_job;
Step_OutputResults(Job job) { m_job = job; }
Execute() { m_job->OutputResults(); }
}

/****** Specific Jobs *****/
class CJob extends Job {
CJob() {
tree.insert( "0.1", new Step_ValidateDate(this) );
}
ValidateDate() { // if invalid date throw exception }
}

class PJob extends Job {
PJob() {
tree.insert( "1.1", new Step_CalculateExchangeRate(this) );
}
CalculateExchangeRate() { // calculate exchange rate };
}

/****** More Steps *****/
class Step_ValidateDate extends Step {
Job m_job;
Step_ValidateDate(Job job) { m_job = job; }
Execute() { m_job->ValidateDate(); }
}

class Step_CalculateExchangeRate extends Step {
Job m_job;
Step_CalculateExchangeRate(Job job) { m_job = job; }
Execute() { m_job->CalculateExchangeRate(); }
}

I have a circular dependency problem in my code declarations (I'm actually doing this in C++), but I think its because I’m writing everything in a .hpp file at the moment.



The Step classes could actually be made into a template class.


When a particular Job is created, say CJob, the parent class's ctor (Job::Job()) builds the main process by inserting the main process step objects into the tree, and then the CJob ctor inserts the tailored steps into the tree at the positions indicated by the first "path" parameter (eg "1.1"). Then all we have to do is traverse the tree to execute the entire CJob Process: (Steps 0.1, 1, 1.1, 2).




///////////////////////////////////////



Another way is to simply override one of the main steps (copy its code into the local version) in the child and slip in the 'specific' code at the beginning or end of the implementation; essentially a code insert:


			
class PJob {
...
OutputResults() {
// copy code from Job::OutputResults here
CalculateExchangeRate();
}

CalculateExchangeRate() {
// Do exchange rate calculations....
}
}
...
}

This has its own problems. Aside from looking sloppy, what happens if you change the Job::OutputResults function? What if we want to put a new function after OutputResults in the main process, but then CalculateExchangeRate() needs to happen after this new function?



///////////////////////////////////////



Back to steps as objects...



If steps are now objects, there are other issues, like how do you pass parameters between steps? How do you get the results from one step into another?



I wanted to know if there was a design pattern that works better than my approach.



Essentially, you want to share a process but allow it to be tailored by subclasses without using overriding (eg. code insertion). Altering the process should cause minimal impact to child classes (ie. Adding a step 5 to the main process, or adding a step 2.1 for J, K and L jobs only).



Perhaps I’m thinking too much in terms of utilizing inheritance while not thinking of an aggregation (delegation) solution?


TVMIA,


Bill

   <<Less

Re: Pattern for sharing a process among multiple objects while tailoring the process.

Posted By:   Christopher_Koenigsberg  
Posted On:   Saturday, January 15, 2005 08:33 AM

Perhaps you are thinking of the "command" design pattern? (Gang of Four)
About | Sitemap | Contact