Ho l'obbligo di avere un componente che verificherà l'input rispetto ad alcune regole in un ordine rigoroso. L'ordine delle regole è definito dall'azienda.
Per mantenere il codice di esempio semplice, proverò a definire un'analogia.
Diciamo che il business definisce l'ordine di alcuni valori int. Ti verrà data una sequenza di numeri interi e il requisito è quello di verificare se la sequenza contiene un numero dai numeri definiti dall'azienda. In caso affermativo, restituire il numero, se nessuno di questi numeri è contenuto in sequenza, restituire -1
. I controlli devono essere effettuati in ordine (questo è importante!).
Ad esempio:
Diciamo che il business definisce l'ordine dei numeri da controllare come {3, 7, 5}
.
Quando viene ricevuta la sequenza intera di input (esempio {4, 6, 7}
), dovremmo controllare nell'ordine definito se il numero esiste nella sequenza ricevuta. Quindi nel nostro esempio
- Controlliamo se il numero
3
esiste nella sequenza, chiaramente non - Quindi controlliamo se il numero
7
esiste nella sequenza, lo fa, quindi restituiamo 7
Come ho detto questa è un'analogia semplificata, le regole nell'applicazione reale sono molto più complicate, così come l'input.
La mia implementazione utilizza il pattern chain of rules
. C'è una catena di regole, in cui ogni regola sa qual è la prossima regola e amp; controllando l'input sulla sua logica, nel caso in cui non sia soddisfatto, passa l'input alla regola successiva.
Implementazione per l'analogia con i nostri numeri
public interface INumberRule
{
int GetNumber(IEnumerable<int> numbers);
}
INumberRule
interface definisce l'API per le nostre regole.
public sealed class ChainedRule : INumberRule
{
private INumberRule NextRule { get; }
private int Number { get; }
public ChainedRule(INumberRule nextRule, int number)
{
NextRule = nextRule;
Number = number;
}
public int GetNumber(IEnumerable<int> numbers)
{
if (numbers.Contains(Number))
return Number;
return NextRule.GetNumber(numbers);
}
}
ChainedRule
definisce lo scheletro della nostra regola. Tutte le regole saranno ChainedRule
, ad eccezione della regola terminazione della catena .
public sealed class MinusOneRule: INumberRule
{
private MinusOneRule()
{ }
public int GetNumber(IEnumerable<int> numbers) => -1;
public static MinusOneRule Instance { get; } = new MinusOneRule();
}
Il solo scopo di MinusOneRule
è di terminare la catena quando nessuna delle regole potrebbe soddisfare la loro logica contro l'input. Come descritto sopra, se nessuno dei numeri contenuti nella sequenza di input, dovremmo restituire -1
. MinusOneRule
fa proprio questo.
Definiamo factory che crea una catena di regole:
public interface INumberRuleFactory
{
INumberRule GetChainedRules();
}
public sealed class ChainedNumberRuleFactory : INumberRuleFactory
{
public INumberRule GetChainedRules()
{
var minusOneRule = MinusOneRule.Instance;
var sevenRule = new ChainedRule(minusOneRule, 7);
var fiveRule = new ChainedRule(sevenRule, 5);
var threeRule = new ChainedRule(fiveRule, 3);
return threeRule;
}
}
E infine l'utilizzo:
public sealed class NumberProvider2
{
private INumberRule NumberRules { get; }
public NumberProvider2(INumberRuleFactory numberRuleFactory)
{
NumberRules = numberRuleFactory.GetChainedRules();
}
public int GetNumber(IEnumerable<int> numbers) => NumberRules.GetNumber(numbers);
}
class Program
{
static async Task Main(string[] args)
{
IEnumerable<int> numbers = new[] {4, 6, 7};
var numberRulesFactory = new ChainedNumberRuleFactory();
var numberProvider2 = new NumberProvider2(numberRulesFactory);
WriteLine($"Number Provider v. 2: {numberProvider2.GetNumber(numbers)}");
ReadLine();
}
}
Mi piace questo approccio perché è robusto, comunica chiaramente le intenzioni e amp; importanza dell'ordine.
Il mio collega ha suggerito che potrebbe trattarsi di un codice eccessivo e invece potrebbe essere fatto usando una sequenza di regole. Questa sequenza è anche creata da una factory & la fabbrica è responsabile delle regole numeriche ordinate correttamente.
public interface IOrderedNumberSequenceFactory
{
IEnumerable<int> GetOrderedNumbers();
}
public sealed class OrderedNumberSequenceFactory : IOrderedNumberSequenceFactory
{
public IEnumerable<int> GetOrderedNumbers() => new[] {3, 7, 5};
}
E l'utilizzo:
public sealed class NumberProvider
{
private IEnumerable<int> OrderedNumbers { get; }
public NumberProvider(IOrderedNumberSequenceFactory orderedNumberLisProvider)
{
OrderedNumbers = orderedNumberLisProvider.GetOrderedNumbers();
}
public int GetNumber(IEnumerable<int> numbers)
{
foreach (int orderedNumber in OrderedNumbers)
{
if (numbers.Contains(orderedNumber))
return orderedNumber;
}
return -1;
}
}
class Program
{
static async Task Main(string[] args)
{
IEnumerable<int> numbers = new[] {4, 6, 7};
var rulesFactory = new OrderedNumberSequenceFactory();
var numberProvider = new NumberProvider(rulesFactory);
WriteLine($"Number Provider v. 1: {numberProvider.GetNumber(numbers)}");
}
}
Riesco chiaramente a vedere questo approccio è meno codice, meno componenti e più facile da leggere e amp; capire a prima vista. Tuttavia, ciò che non mi piace è che non sembra comunicare con forza l'importanza dell'ordine delle regole e dell'amplificazione; è più facile confondere l'ordine con questa implementazione rispetto a quello precedente.
L'argomento opposto era se il metodo factory che crea numeri ordinati fosse correttamente denominato (ad esempio GetOrderedNumbers()
), quindi comunica chiaramente che l'ordine è importante qui.
Qualche idea su questi approcci con pro / contro?