Metodo factory con nome corretto vs catena di regole

0

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

  1. Controlliamo se il numero 3 esiste nella sequenza, chiaramente non
  2. 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?

    
posta Michael 18.07.2018 - 08:23
fonte

1 risposta

4

Gli elenchi collegati sono obsoleti.

Ciò che qui in pratica è un elenco collegato di passaggi di convalida.

Gli elenchi collegati sono una lezione interessante sulle strutture dati se stai imparando a conoscere i tipi di riferimento. E ai tempi di C / C ++, le liste collegate fornivano anche una preziosa caratteristica: una collezione la cui dimensione non ha bisogno di essere definita in anticipo (al contrario degli array).

Tuttavia, nel C # di oggi, non vedo più alcun vantaggio per gli elenchi collegati, poiché l'avvento di List<T> . Le liste forniscono le stesse funzionalità (raccolta ordinata, possono crescere organicamente) e non richiedono di sviluppare la colla che tiene insieme la raccolta.

There is still an interesting discussion to be had about List versus LinkedList (mostly in the comments to the accepted answer), but that's a different discussion. You're using a manually built linked list, not a LinkedList.

Note that when I say linked lists are outdated, I mean manually crafted linked lists like yours. As mentioned in the comments here, the benefit of linked lists is that you can insert items in the middle of the list in O(1) time.
However, that can already be achieved by using the LinkedList class; there's still no reason to manually craft classes to act as linked list nodes.

La tua soluzione.

I like this approach because it is robust, it clearly communicates intention & importance of the order.

Mi oppongo fermamente alla richiesta di comunicare l'importanza dell'ordine. Il tuo codice ha invertito l'ordine delle regole, iniziando dalla priorità più bassa e creando la massima priorità.

Per quanto riguarda la leggibilità del codice, il tuo codice è esattamente opposto a quello che ti aspetteresti: la regola della priorità più alta (in basso) è una clausola di uscita precoce e sarebbe istintivamente trovata al parte superiore del codice.

Il tuo codice dovrebbe essere una generalizzazione di un modello simile a questo:

if(value == first_number) 
    return first_number;

if(value == second_number) 
    return second_number;

return default_number;

Questo è solo un semplice esempio di pseudocodice, ma questo schema di ritorni anticipati è incredibilmente comune da incontrare nel codice. E in tutti i casi comuni, la priorità più alta si trova in alto, non in basso.

Penso anche che tu stia confondendo "l'intenzione di comunicare chiaramente" con "fa ciò che avevo già deciso che dovrebbe fare". Di per sé, senza leggere la tua spiegazione (che è ciò che faccio sempre per testare la leggibilità del codice), non è chiaro cosa tu abbia cercato di ottenere. L'essenza della tua soluzione è nascosta in un gran numero di codici strutturali placcati.
Tongue-in-cheek, "robusto" di un uomo è il "sovrastrutturato" dell'altro uomo.

Se possibile, la tua intenzione può essere riassunta in due piccoli frammenti:

if (numbers.Contains(Number))
            return Number;

var threeRule = ...
var fiveRule = ...
var sevenRule = ...
var minusOneRule = ...;

Tutto il resto è solo vestirsi. Come punto interessante, ho invertito l'ordine delle tue variabili rule . Perché senza fare affidamento sulla nozione di creare una struttura concatenata, l'ordine invertito di variabili induce un lettore errante ad assumere che l'ordine delle regole sia il contrario (prima il primo).

La soluzione del tuo collega.

However, what I don't like is it doesn't seem to communicate strongly the importance of rule order

Non sono d'accordo. Il codice del tuo collega ha più senso; i numeri di convalida sono descritti in ordine decrescente di priorità, che è quello che ti aspetteresti istintivamente.

Ho anche apprezzato molto che il suo codice separasse i valori previsti dal valore predefinito . -1 non è uguale a 3 , 7 o 5 . È funzionalmente diverso. Se l'applicazione deve supportare numeri negativi nella versione successiva, è necessario un modo per modificare il valore predefinito senza influire sul resto del codice.

& it is easier to mess order with this implementation than with the previous one.

Come?

Se sei responsabile di scambiare l'ordine dei numeri nella lista dei tuoi colleghi, sei altrettanto incline a scambiare l'ordine degli elementi nelle tue regole concatenate.

L'ho menzionato prima, ma l'ordine invertito di definire le tue regole, se possibile, renderà più probabile che si verifichino errori quando le regole vengono ridefinite.

    
risposta data 18.07.2018 - 09:46
fonte

Leggi altre domande sui tag