Questo viola le eccezioni usando la "regola" del controllo di flusso?

1

Ho intenzione di utilizzare questa interfaccia in un'architettura plug-in.

/// <summary>
/// Generic interface allowing you to react to an event.
/// You can block the event or just use it for notification.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IEventObserver<in T> where T : class
{
    /// <summary>
    /// Throw an exception in here if you want to prevent the event firing.
    /// </summary>
    /// <param name="o"></param>
    void CanFireEvent(T o);

    /// <summary>
    /// This is only called if no handler threw an event in the CanFire.
    /// </summary>
    /// <param name="o"></param>
    void OnBeforeEvent(T o);

    /// <summary>
    /// This is called after the event has happened.
    /// </summary>
    /// <param name="o"></param>
    void OnAfterEvent(T o);
}

Esempio di utilizzo:

public interface ISendMessageObserver : IEventObserver<SendMessageEventArgs>
{
}

Un plug-in può quindi implementare ISendMessageObserver e impedire l'invio di messaggi generando un'eccezione nel metodo CanFireEvent . Tuttavia, so che le eccezioni non dovrebbero essere usate per il normale controllo del flusso del programma, tuttavia è normale e ci si aspetta che i plugin interrompano il flusso. Sono andato per questo perché mi piacerebbe che il plugin fornisse un motivo per bloccare l'evento che può essere registrato o presentato all'utente.

Potrei cambiare CanFireEvent in:

bool CanFireEvent(T o, ref message);

o

SomeTypeContainingAbortReason CanFireEvent(T o);

Ma entrambi non hanno nemmeno l'odore ideale.

Quindi cosa ne pensano le persone, eccezioni per il controllo del flusso OK in questa istanza?

Modifica

Ho separato CanFire nella sua interfaccia e l'ho rinominato come PreviewEvent .

/// <summary>
/// Generic interface allowing you to react to and block an event.
/// </summary>
/// <typeparam name="TArgs"></typeparam>
/// <typeparam name="TBlock"></typeparam>
public interface IEventPreviewer<in TArgs, out TBlock>
    where TArgs : class
    where TBlock : class, IEventBlock
{
    /// <summary>
    /// Return an IEventBlock to prevent the event from firing.
    /// </summary>
    /// <param name="args"></param>
    TBlock PreviewEvent(TArgs args);
}

public interface IEventBlock
{
    string BlockReason { get; }
}
    
posta weston 28.05.2013 - 10:24
fonte

1 risposta

4

No, le eccezioni non sono mai lo strumento giusto per il normale controllo del flusso. Le eccezioni dovrebbero essere utilizzate solo per indicare che una funzione / metodo non è in grado di soddisfare il suo contratto a causa di motivi esterni.

Nel caso del tuo metodo CanFireEvent , il nome della funzione suggerisce chiaramente una risposta sì / no e quindi un valore di ritorno booleano, ma la seconda alternativa (dove il 'No' viene elaborato con una ragione fissa si adatta anche.

Se dovessi avere una tale funzione nel mio sistema degli eventi, sicuramente non permetterò di fornire un motivo in formato libero. A che serve vedere la ragione "Perché posso."

Veniamo a ciò, consentendo ai plug-in di impedire l'invio di messaggi rende un sistema estremamente fragile. È necessario un solo plug-in che blocchi in modo coerente tutti i messaggi per interrompere l'intero sistema. Potresti avere un metodo bool CanHandleEvent(T) , con il quale il plug-in può indicare che può fare qualcosa di utile con l'evento, ma avere CanFireEvent è semplicemente troppo ampio. E la maggior parte dei sistemi di eventi non ha nemmeno il CanHandleEvent , si aspetta solo che tu sia in grado di gestire gli eventi in ogni momento (in cui la gestione può essere ignorata).

Aggiornamento

Sulla base dei commenti, vedo che il meccanismo degli eventi descritto non è così generico come pensavo e che gli eventi di blocco non hanno il significato "questo evento non può essere gestito ora", ma piuttosto "l'azione proposta è non valido secondo le regole aziendali ". Questo cambia un po 'l'ultima parte della mia risposta.

Se le regole aziendali possono influenzare la validità di un'azione, e questo non può essere facilmente determinato in anticipo, allora introdurrei un'interfaccia nel sistema sulla falsariga di

public interface IActionValidator<in T> // or IEventValidator if you prefer
where T : class
{
    bool IsActionValid(T o, ref message);
}

L'ho separato dall'interfaccia per gestire effettivamente l'azione, poiché posso immaginare che i due siano implementati in classi / componenti separati.

    
risposta data 28.05.2013 - 10:54
fonte

Leggi altre domande sui tag