Reputation: 6707
I am writing a storage solution for a workflow hierarchy.
To simplify the picture I have 2 types of objects, a Workflow and a WorkflowStep.
Even though WorkflowStep comes hierarchically under the Workflow, the Workflow does not aggregate WorkflowStep because we view these objects as just data containers.
So this leaves me with the following classes:
public class Workflow : Node {
public string UID;
public string parentID;
}
public class WorkflowStep : Node {
public string UID;
public string parentID;
}
public class WorkflowEngine {
public void Store(Workflow wf) {
}
public void Store(WorkflowStep wfs) {
}
}
The reasoning for not aggregating WorkflowStep inside Workflow (even though logically that fits) is that these objects are purely viewed as data containers and they may be subject to changes later on and we want to keep the storage of these objects decoupled from the objects themselves.
The other alternative of course would be to do something like this:
public class Workflow : Node {
public List<WorkflowStep> steps;
public string UID;
public string parentUID;
public void Store() { }
}
public class WorkflowStep : Node {
public string UID;
public string parentID;
public void Store() { }
}
What are the pros and cons of either approach? Is there any literature that talks about both the designs?
Upvotes: 0
Views: 342
Reputation: 19217
Even though Workflow
and WorkflowStep
are both data containers but keeping these aside from hierarchical measures doesn't solve your decoupling issue.
It is more logical to keep WorkflowStep
on hierarchy of Workflow
and to get along with decoupling you must introduce IoC
in this case.
The Beauty of IoC
is that changing the definitions of WorkflowStep
which is a list in Workflow
class will be just transparent where you will only be considering to register your types on your IoC
container.
Let me put you on an example with Ninject
IoC
container framework.
Define interfaces and implement your data containers accordingly:
public interface IWorkflow {
string UID { get; set; }
string parentID { get; set; }
IList<IWorkflowStep> Steps { get; set; }
}
public interface IWorkflowStep {
string UID { get; set; }
string parentID { get; set; }
}
public class Workflow : IWorkflow, Node {
public string UID { get; set; };
public string parentID { get; set; };
public IList<IWorkflowStep> Steps { get; set; }
}
public class WorkflowStep : IWorkflowStep, Node {
public string UID { get; set; };
public string parentID { get; set; };
}
And now, the Ninject module be:
public class WorkflowModule : NinjectModule
{
#region Overrides of NinjectModule
public override void Load()
{
Bind<IWorkflow>().To<Workflow>();
Bind<IWorkflowStep>().To<WorkflowStep>();
Bind<IList>().To<List>();
}
#endregion
}
This is the single place where you bind your interfaces with concrete classes. And rest of the world, you just ask for an instance of defined interface.
To resolve your type, you need to create a Ninject Kernel
which is an IKernel
type and a concrete implementation of StandardKernel
by loading your defined module.
Which is something like,
var kernel = new StandardKernel(new WorkflowModule());
Now, all you have to do is resolve your desired interface, like:
IWorkflow workflow = kernel.Get<IWorkflow>();
IWorkflowStep workflowStep = kernel.Get<IWorkflowStep>();
The beauty here is, you don't need to worry about your concrete implementation and which is very tightly coupled within your system. Its just the interface you will be dealing with and rest are the worries of your IoC
container implementation.
As you are more worried about the implementation of WorkflowStep
to be changed and not coupling with Workflow
. I guess, this is where IoC
comes to play.
Please be noted that, you can use any IoC
container framework like Unity, Spring.NET, StructureMap and etc. I used Ninject because I am comfortable with it.
Upvotes: 1