2013-05-14 10 views
9

Mi capita spesso di incontrare situazioni in cui voglio creare un'istanza di un oggetto passandogli alcuni dati o forse un altro oggetto ma i dati o l'oggetto devono essere validi o nello stato giusto. Sono sempre un po 'incerto sul modo "corretto" di farlo. Qui è il mio esempio:Quale schema di progettazione utilizzare per convalidare i dati e creare un oggetto

Data questa classe:

class BusinessObject() 
{ 
    const Threshold = 10; 

    public BusinessObject(SetOfData<SomeType> setofdata) 
    { 
     // an example of some validation 
     if (setofdata.count > Threshold) 
     { 
      // performance some business logic 
      // set properties 
     } 
    } 
} 

E 'possibile incorrere in alcuni problemi se si esegue questa operazione:

var setofdata = new SetOfData<SomeType>(); 

// if data is not valid then the object will be created incorrectly 
var businessObject = new BusinessObject(setofdata); 

Quindi le mie soluzioni sono sempre stati:

class BusinessObjectBuilder() 
{ 
    public BusinessObject Build(SetOfData<SomeType> setofdata) 
    { 
     // an example of some validation 
     if (setofdata.count > Threshold) 
      return new BusinessObject(setofdata); 
     } 
     else 
     { 
      return null; 
     } 
    } 
} 

Oppure rendere privato il costruttore e aggiungere un metodo di produzione statico:

class BusinessObject() 
{ 
    const Threshold = 10; 

    public static Create(SetOfData<SomeType> setofdata) 
    { 
     if (setofdata.count > Threshold) 
     { 
      return new BusinessObject(setofdata); 
     } 
     else 
     { 
      return null; 
     } 
    } 

    private BusinessObject(SetOfData<SomeType> setofdata) 
    { 
     // performance some business logic 
     // set properties 
    } 
} 

idealmente non vorrei un'eccezione se i dati non è valido, come ci potrebbe essere più oggetti di business essere creati in un unico processo e non voglio che l'intero processo di fallire se uno convalida non riesce e la cattura e sopprimere le eccezioni non va bene.

Inoltre, tutti gli esempi che ho letto del metodo Factory o Factory Abstract implicano il passaggio in qualche tipo o enum e un oggetto corretto che viene costruito e restituito. Non sembrano mai coprire questo scenario.

Quindi quali sono le convenzioni in questo scenario? Qualsiasi consiglio sarebbe molto apprezzato.

+0

Solo una nota, le fabbriche non hanno bisogno di avere "qualche tipo o enum" passato; possono prendere qualsiasi tipo di dati (anche un 'SetOfData ') o nessun dato (senza parametri). Gli esempi tendono ad usarli perché è un modo abbastanza comune/semplice di usarli/descriverli. Se lo desideri, potresti sempre creare un 'BusinessObjectValidator' affinché la factory possa fare leva su quali verificherebbero i parametri, ma vorrei lanciarla nel metodo di creazione di fabbrica, se il controllo è semplice come descrivi. –

risposta

11

La convalida del costruttore IMHO è la migliore per molte situazioni in cui è necessario assicurarsi che nessun oggetto possa essere creato se non si specifica il parametro specificato.

public class BusinessObject 
{ 
    const Threshold = 10; 

    public BusinessObject(SetOfData<SomeType> setofdata) 
    { 
     // an example of some validation 
     if (setofdata.count > Threshold) 
     { 
      throw new InvalidOperationException("Set data must be above treshold"); 
     } 
    } 
} 

Tuttavia, questo ha cattiva attuazione quando:

  • Si può avere oggetto non valido come quando in stato di bozza, ecc
  • Utilizzato in ORM quando costruttore di default necessario
  • Se la convalida pesante la logica si verifica

Per i punti n. 1 e 2, non posso suggerire alcuna altra opzione eccetto richiesta - convalida - invio meccanismo.

Per il punto n. 3, la ragione è che la classe farà troppo per la convalida stessa e per la creazione di un codice monolitico. Se c'è molta logica di convalida, suggerisco di implementare il modello di builder con validatore iniettato e di rendere interno il costruttore di BusinessObject.

public class BusinessObjectBuilder 
{ 
    public BusinessObjectBuilder(IBusinessObjectValidator validator){ 
     this.validator = validator; 
    } 
    IBusinessObjectValidator validator; 

    public BusinessObject Build(SetOfData<SomeType> setofdata) 
    { 
     // an example of some validation 
     if (validator.IsValid(setofdata)) 
      return new BusinessObject(setofdata); 
     } 
     else 
     { 
      throw new //exception 
     } 
    } 
} 

Questo impone la programmazione modulare e previene il codice monolitico.

Sia parte del codice è:

  • facile da testare
  • facile da rivedere
  • estensibile
1

Non posso dire il modo "corretto". Ma posso dirti che mio modo =)

Vorrei scegliere il modello di fabbrica. Se non si desidera interrompere l'applicazione in seguito a un errore di convalida, la fabbrica potrebbe riempire le parti difettose con valori predefiniti.

2

Forse si potrebbe implementare la Strategy Pattern al Factory (method) per fornire alcune funzionalità di validazione:

public interface DataValidationStrategy { 
    boolean isValid(SetOfData<SomeType> setofdata); 
} 

public class ThresholdValidation implements DataValidationStrategy { 
    private int threshold; 

    public ThresholdValidation(int threshold) { 
     this.threshold = threshold; 
    } 

    @Override 
    public booleam isValid(SetOfData<SomeType> setofdata) { 
     return setofdata.count > threshold; 
    } 
} 

Ora creare tanti diversi validation classes come desiderato e quindi cambiare il metodo di create:

public static Create(SetOfData<SomeType> setofdata, DataValidationStrategy validation) 
{ 
    if (validation.isValid(setofData)) 
    { 
     return new BusinessObject(setofdata); 
    } 
    else 
    { 
     return null; 
    } 
} 

Modifica: Inoltre, potresti prendere in considerazione l'utilizzo di prototype o null object invece di un valore di ritorno null.

+2

Credo che l'oggetto nullo sarebbe dannoso in questa situazione. Il consumatore può verificare nullo e presupporre che se BusinessObject restituito non è nullo, è stato creato correttamente. E controllando se l'oggetto restituito da Create non sia Null Object sarebbe un esempio di uso scorretto del pattern. – lisp

1

Credo che sia pratica generale per generare un'eccezione da un metodo come Create quando gli argomenti non sono corretti. Hai ragione sulle tue riserve sulla restituzione di null in questo scenario e cercando di evitare l'uso di eccezioni per il controllo del flusso. Si potrebbe semplicemente avere:

public bool CanBuild(SetOfData<SomeType> setofdata) 
{ 
    return validator.IsValid(setofdata); 
} 

public BusinessObject Build(SetOfData<SomeType> setofdata) 
{ 
    if (validator.IsValid(setofdata)) 
    { 
     return new BusinessObject(setofdata); 
    } 
    throw new ArgumentException(); 
} 

ho ancora lasciare il lancio eccezione, perché non c'è alcuna garanzia che setofdata è stato convalidato con CanBuild. Questo fornisce i mezzi per evitare l'uso di eccezioni per il controllo del flusso, ma ha lo svantaggio di convalidare due volte. Per eliminare la doppia convalida, inserisci TryCreate accanto o al posto di Create. Solo osservando la firma si vede che, oltre a creare l'oggetto business, questo metodo esegue la convalida dei dati di input e restituisce il risultato di questa convalida in forma diversa da un'eccezione. (Vedere int.Parse e int.TryParse)

public bool TryBuild(SetOfData<SomeType> setofdata, out BusinessObject businessObject) 
{ 
    if (validator.IsValid(setofdata)) 
    { 
     businessObject = new BusinessObject(setofdata); 
     return true; 
    } 
    businessObject = null; 
    return false; 
} 

Questo metodo di convalida avrebbe chiamato i costruttori inaccessibile agli altri che non contengono la convalida, mentre tutti i costruttori ampiamente accessibili sarebbe ancora convalidare ed eccezione tiro.

Naturalmente i metodi Built e TryBuilt possono essere statici (come in int) oppure è possibile applicare un modello predefinito.

0

Dai un'occhiata al quadro OVal. Puoi estenderlo con le tue annotazioni.

Problemi correlati