I Love Stackoverflow
I Love Stackoverflow

Reputation: 6868

How to call different version of override method based on condition?

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

Answers (2)

NightOwl888
NightOwl888

Reputation: 56869

This is a good candidate for the strategy design pattern.

Define a family of algorithms, encapsulate each one, and make them interchangeable.

Interfaces

public interface IOperation
{
    Output Process(Input input);
    bool AppliesTo(string operation);
}

public interface IOperationStrategy
{
    Output Process(string operation, Input input);
}

Operations

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();
    }
}

Strategy

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);
    }
}

Usage

// 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.

Additional Examples

Upvotes: 2

Wheels73
Wheels73

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

Related Questions