Как вызвать другую версию метода переопределения в зависимости от условия?

У меня есть 1 библиотека классов, которая имеет некоторый код для выполнения какой-либо операции. Например, она будет выполнять 2 операции, такие как:

- Add  
- Multiply

В будущем, возможно, у меня будет еще несколько операций, таких как деление, например.

Выше приведен только пример, потому что у меня есть длительная операция с каждой из этих операций добавления, умножения.

Таким образом, идея этой библиотеки состоит в том, чтобы получать входные данные и затем выполнять долго выполняющийся код для этих входных данных.

Вот о чем я думаю:

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

Теперь предположим, что если я захочу выполнить операцию добавления или операцию умножения, то как я буду вызывать соответствующие методы на основе типа ниже:

string type;
if(type=="Add")
   //perform add operation 
else if(type=="Multiply")
  //perform multiply operation 

Но я не получаю правильный способ разработки структуры кода для вышеуказанного требования.

Примечание: причина создания IOperations как Interface для dependency injection

2 ответа

Решение

Это хороший кандидат для разработки стратегии.

Определите семейство алгоритмов, инкапсулируйте каждый и сделайте их взаимозаменяемыми.

Интерфейсы

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

Одним из преимуществ использования этого шаблона по сравнению с заводским дизайном является то, что дизайн не нуждается в изменении для добавления или удаления операций. В фабричном дизайне у вас есть жестко запрограммированный оператор регистра переключателя, который нужно менять каждый раз, когда вы добавляете операцию.

Конечно, на самом деле нет никаких ограничений на настройку ваших входов и выходов, если вы используете одни и те же типы для каждого IOperation, Я только показываю это таким образом, так как разумно получить выходные данные в качестве возвращаемого значения Process метод, но реализация, которую вы используете, может отличаться.

Дополнительные примеры

Хорошо, как обсуждалось

Определите ваши классы для ввода и результата.

public class Input
{
}

public class Result
{
}

Определите ваш интерфейс

public interface IOperations
{
    Result Process(Input obj);
}

Определите фабрику для возврата требуемой реализации

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

Определите ваши конкретные реализации

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

Наконец, вызвать из кода.

 private void button1_Click(object sender, EventArgs e)
 {
     var mathFactory = new MathFactory();
     var operation = mathFactory.GetOperatorByType("Add");
     var result = operation.Process(new Input());
}

Очевидно, вам придется повозиться с тем, как используется входной объект (конструктор, как есть сейчас или, возможно, в интерфейсе)... и как вы создаете свой результат и выставляете ответ как свойство.

Надеюсь, это поможет.

Другие вопросы по тегам