Esposizione di funzionalità con una raccolta di valori enumerati o con funzioni booleane

1

Spiegazione di alto livello

Ho un oggetto con alcuni metodi:

public class Foo
{
    public void Bar() { }
    public void Baz() { }
}

Questi metodi non possono essere eseguiti incondizionatamente, c'è qualche convalida da fare. Mi piacerebbe anche esporre queste condizioni a un cliente (utente) in qualche modo.

Potrei farlo con una raccolta di valori enum:

public enum FooAction
{
    Bar,
    Baz
}

public class Foo
{
    public void Bar() { if (GetAvailableActions().Contains(FooAction.Bar)) }
    public void Baz() { if (GetAvailableActions().Contains(FooAction.Baz)) }

    public IEnumerable<FooAction> GetAvailableActions() { }
}

Potrei farlo anche con le funzioni booleane:

public class Foo
{
    public void Bar() { if (CanBar()) }
    public void Baz() { if (CanBaz()) }

    public bool CanBar() { }
    public bool CanBaz() { }
}

Ho cercato di trovare un motivo per preferire l'uno rispetto all'altro, ma posso solo pensare a un possibile beneficio in termini di prestazioni, a seconda di quanti dati di input avrebbero questi metodi in comune. E probabilmente detto beneficio di prestazione sarebbe trascurabile.

Esistono problemi reali che potrebbero verificarsi con una soluzione e non con l'altra? O il tutto si riduce a preferenze personali?

Esempio concreto

public class Patient
{
    public IEnumerable<Prescription> Prescriptions { get; set; }
}

public class Prescription
{
    public IEnumerable<Administration> Administrations { get; set; }
}

public class Administration
{
    public void Administer(string foo, int bar) { }

    public void Prepare(string baz, bool bat) { }
}
  • Prepare(...) può essere chiamato solo se Prepare(...) non è stato ancora chiamato prima
  • Administer(...) non può essere chiamato se Prescription ha un Administration non ordinato pianificato in un momento precedente.
  • Administer(...) non può essere chiamato se Administration dipende dal peso di Patient e il peso di Patient è sconosciuto.

Queste regole possono essere molto semplici e molto complesse. Nel client, un utente può fare clic sul pulsante "Amministra", compilare un modulo e fare clic sul pulsante "Conferma". Non voglio consentire all'utente di fare clic sul pulsante "Amministra" per aprire il modulo, se queste condizioni preliminari indicano che l'azione avrà esito negativo a prescindere dai dati immessi nel modulo.

Ho aggiunto le e perché è nel contesto di un DDD / CQRS, ma non sono sicuro se questo è importante per questo problema.

    
posta Stijn 16.08.2016 - 14:16
fonte

2 risposte

2

Dato che lo stai facendo con CQRS in mente, penso che le tue classi dovrebbero assomigliare più

Classe di comando: incapsula i parametri di comando

 class Administer
    {
       public Administer(string foo, int bar)
       {
          ....
       }
       public string Foo {get; private set;}
       public int Bar {get; private set;}
    }

Gestore di comandi: passa il comando ai tipi specializzati, non eseguire alcuna convalida qui

class HandleAdminister
{
   private readonly Administration _administration;
   public   HandleAdminister(Administration administration)
   {
      _administration = administration;
   }
   public void Handle(Administer command)
   {
       administration.Administer(command.Foo, command.Bar);
   }
}

Validatore dei comandi: la convalida è un problema separato, creiamo un tipo per gestire solo la convalida

class ValidateAdminister
{
  private readonly IReadEntities _readEntities;
  public ValidateAdminister(IReadEntities readEntities)
  {
     _readEntities = readEntities;
  }
  public bool Validate(Administer command)
  {
     ... heavy or light validation here
     var unadministered = _readEntities
               .Query<Prescription>()
               .Any(c=>c.Unadministered && c.Foo == command.Foo);
     return !unadministered;
  }
}

Codice client per eseguire un comando:

var administration = ...
var command = new Administer("foo", 19);
var handler = new HandleAdminister(administration);
var validator = new ValidateAdminister(...);

if (validator.Validate(command))
    handler.Handle(command);

Una possibile relazione tra ViewModel e validatori

class PatientViewModel
{
   public bool CanAdminister {get; set;}
   public bool CanPrepare {get; set;}
   public Patient Patient {get; set;}
}


var model = new PatientViewModel();
model.Patient = ...;
model.CanAdminister = administerCommandValidator.Validate(administerCommand);
model.CanPrepare = prepareCommnadValidator.Validate(prepareCommand);

Il punto è che puoi usare il validatore per convalidare il comando prima di eseguirlo e anche per costruire un modello per l'interfaccia utente.

    
risposta data 16.08.2016 - 16:51
fonte
0

Per essere onesti, non penso che consiglierei ciò che segue come una buona soluzione per il vero problema di design che hai a portata di mano (giudica te stesso), ma solo per il gusto di restare vicino alla tua idea iniziale , c'è anche questo altro modello basato su flag di opzioni che puoi esprimere abbastanza facilmente in C #:

public enum Actions
{
    // (Powers of 2 bitmask)
    Bar = 1,
    Baz = 2,
    Gee = 4,
    Bip = 8
}

public class Foo
{
    private bool CanDo(Actions actions) { return (FeasibleActions & actions) != 0; }

    private string Self { get { return GetType().Name; } }

    protected virtual void DoBar() { Console.WriteLine("{0}: Bar!", Self); }

    protected virtual void DoBaz() { Console.WriteLine("{0}: Baz!", Self); }

    protected virtual void DoGee() { Console.WriteLine("{0}: Gee!", Self); }

    protected virtual void DoBip() { Console.WriteLine("{0}: Bip!", Self); }

    public Actions FeasibleActions { get; protected set; }

    public Foo()
    {
        // Initial feasible actions for Foo
        FeasibleActions = Actions.Bar | Actions.Baz;
    }

    public void Bar() { if (CanDo(Actions.Bar)) { DoBar(); } /* else { throw some exception }... */ }

    public void Baz() { if (CanDo(Actions.Baz)) { DoBaz(); } /* else { throw some exception }... */ }

    public void Gee() { if (CanDo(Actions.Gee)) { DoGee(); } /* else { throw some exception }... */ }

    public void Bip() { if (CanDo(Actions.Bip)) { DoBip(); } /* else { throw some exception }... */ }
}

public class Acme : Foo
{
    protected override void DoBaz() { base.DoBaz(); FeasibleActions |= Actions.Bip; }

    public Acme() //(implicit : base())
    {
        // Initial feasible actions for Acme: same as Foo's, plus Gee
        FeasibleActions |= Actions.Gee;
    }
}

quindi:

        var foo = new Foo();
        var acme = new Acme();

        foo.Bar(); // "Bar!"
        foo.Bip(); // (does nothing or throws an exception)
        foo.Baz(); // "Baz!"
        foo.Gee(); // (does nothing or throws an exception)
        foo.Bip(); // (does nothing or still throws an exception)

        acme.Bar(); // "Bar!"
        acme.Bip(); // (does nothing or throws an exception)
        acme.Baz(); // "Baz!"
        acme.Gee(); // "Gee!"
        acme.Bip(); // "Bip!" (thanks to prior call to Baz)

'HTH,

    
risposta data 27.08.2016 - 00:35
fonte

Leggi altre domande sui tag