Как вызвать другую версию метода переопределения в зависимости от условия?
У меня есть 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());
}
Очевидно, вам придется повозиться с тем, как используется входной объект (конструктор, как есть сейчас или, возможно, в интерфейсе)... и как вы создаете свой результат и выставляете ответ как свойство.
Надеюсь, это поможет.