Reputation: 6868
I have 1 class library which has some code to perform some operation.For instance it will perform 2 operation like:
- Add
- Multiply
In future may be I can have few more operation like division for eg.
Above are just an example because i have some long running operation with each of this Add,Multiply operation.
So idea of this library is to receive input and then execute long running code against those inputs.
This is what I am thinking:
public class Input
{
//input properties
}
public interface IOperations
{
public abstract void Process(Input obj);
}
public class Add : IOperations
{
Input obj;
public Add(Input obj)
{
this.obj = obj;
}
public override void Process(Input obj)
{
//Add method implementation
}
}
public class Multiply : IOperations
{
Input obj;
public Multiply(Input obj)
{
this.obj = obj;
}
public override void Process(Input obj)
{
//Multiply method implementation
}
}
Now suppose if I want to perform Add operation or multiply operation then how I will call respective methods based on below type:
string type;
if(type=="Add")
//perform add operation
else if(type=="Multiply")
//perform multiply operation
But I am not getting proper way to design code structure for above requirement.
Note : Reason for creating IOperations
as Interface
is for dependency injection
Upvotes: 1
Views: 667
Reputation: 56869
This is a good candidate for the strategy design pattern.
Define a family of algorithms, encapsulate each one, and make them interchangeable.
public interface IOperation
{
Output Process(Input input);
bool AppliesTo(string operation);
}
public interface IOperationStrategy
{
Output Process(string operation, Input input);
}
public class Add : IOperation
{
public bool AppliesTo(string operation)
{
return nameof(Add).Equals(operation, StringComparison.OrdinalIgnoreCase);
}
public Output Process(Input input)
{
// Implementation
return new Output();
}
}
public class Multiply : IOperation
{
public bool AppliesTo(string operation)
{
return nameof(Multiply).Equals(operation, StringComparison.OrdinalIgnoreCase);
}
public Output Process(Input input)
{
// Implementation
return new Output();
}
}
public class OperationStrategy : IOperationStrategy
{
private readonly IOperation[] operations;
public OperationStrategy(params IOperation[] operations)
{
if (operations == null)
throw new ArgumentNullException(nameof(operations));
this.operations = operations;
}
public Output Process(string operation, Input input)
{
var op = operations.FirstOrDefault(o => o.AppliesTo(operation));
if (op == null)
throw new InvalidOperationException($"{operation} not registered.");
return op.Process(input);
}
}
// I am showing this in code, but you would normally
// do this with your DI container in your composition
// root, and the instance would be created by injecting
// it somewhere.
var strategy = new OperationStrategy(
new Add(), // Inject any dependencies for operation here
new Multiply()); // Inject any dependencies for operation here
// And then once it is injected, you would simply do this.
// Note that it would not be appropriate to use an Enum for
// the operation, because the compiled Enum would have to
// remain in sync with the runtime operation values (not possible).
// That said, the data type doesn't necessarily need to be string.
var input = new Input { Value1 = 2, Value2 = 3 };
var output = strategy.Process("add", input);
// output.Value is 5
var output = strategy.Process("multiply", input);
// output.Value is 6
One of the advantages of using this pattern over a factory design is that the design doesn't need to change to add or remove operations. In a factory design, you have a switch case statement hard coded into it that needs to change every time you add an operation.
Of course, there really are no constraints on how your inputs and outputs are setup provided you use the same types for each IOperation
. I am only showing it this way, since it is sensible to get the output as a return value of the Process
method, but the implementation you use might be different.
Upvotes: 2
Reputation: 2890
Ok, as discussed
Define your classes for the input and result.
public class Input
{
}
public class Result
{
}
Define your interface
public interface IOperations
{
Result Process(Input obj);
}
Define a factory to return the required implementation
public class MathFactory
{
public IOperations GetOperatorByType(string type)
{
switch (type)
{
case "Add":
return new Add(new Input());
case "Multiply":
return new Multiply(new Input());
}
throw new Exception("Unknown type.");
}
}
Define your concrete implementations
public class Add : IOperations
{
Input obj;
public Add(Input obj)
{
this.obj = obj;
}
public Result Process(Input obj)
{
//Perform Add here
return new Result();
}
}
public class Multiply : IOperations
{
Input obj;
public Multiply(Input obj)
{
this.obj = obj;
}
public Result Process(Input obj)
{
//Perform multiply here
return new Result();
}
}
Finally invoke from code.
private void button1_Click(object sender, EventArgs e)
{
var mathFactory = new MathFactory();
var operation = mathFactory.GetOperatorByType("Add");
var result = operation.Process(new Input());
}
Obviously you will have to tinker with how the input object is used (constructor as is now or perhaps on the interface)... and how you construct your result and expose the answer as a property.
Hope that helps.
Upvotes: 2