15

La mia attuale applicazione consente agli utenti di definire moduli Web personalizzati attraverso una serie di schermate di amministrazione. è essenzialmente un'applicazione di tipo EAV. Pertanto, non riesco a scrivere codice HTML o codice ASP.NET per eseguire il rendering di una determinata pagina. Invece, l'interfaccia utente richiede un'istanza di un oggetto Form dal livello di servizio, che a sua volta ne costruisce uno utilizzando diverse tabelle RDMBS. Modulo contiene il tipo di classi che ci si aspetta di vedere in un tale contesto: Form =>IEnumerable<FormSections> =>IEnumerable<FormFields>Si tratta di un tipico caso d'uso per IOC?

Ecco ciò che il livello di servizio si presenta come:

public class MyFormService: IFormService{ 

     public Form OpenForm(int formId){ 
      //construct and return a concrete implementation of Form 
     } 
} 

Tutto funziona splendidamente (per un po ') . L'interfaccia utente è nient'altro su quali sezioni/campi esistano in un dato modulo: rende felicemente l'oggetto Form che riceve in una pagina ASP.NET funzionale.

Alcune settimane più tardi, ottengo un nuovo requisito dal business: Quando si visualizzano versioni non modificabili (cioè di sola lettura) di un modulo, alcuni valori di campo devono essere uniti insieme e altri campi inventati/calcolati devono essere aggiunto. Nessun problema, dico. Semplicemente modificare la mia classe di servizio in modo che i suoi metodi sono più esplicito:

public class MyFormService: IFormService{ 

     public Form OpenFormForEditing(int formId){ 
      //construct and return a concrete implementation of Form 
     } 

     public Form OpenFormForViewing(int formId){ 
      //construct and a concrete implementation of Form 
      //apply additional transformations to the form 
     } 
} 

Anche in questo caso tutto funziona alla grande e l'equilibrio è stato ripristinato alla forza. L'interfaccia utente continua ad essere agnostica su ciò che è nella forma, e la nostra separazione delle preoccupazioni è raggiunta. Solo poche settimane dopo, tuttavia, l'azienda emette un nuovo requisito: in determinati scenari, dovremmo applicare solo un po 'di delle trasformazioni di modulo a cui ho fatto riferimento sopra.

A questo punto, sembra che l'approccio del "metodo esplicito" abbia raggiunto un punto morto, a meno che non voglio finire con un'esplosione di metodi (OpenFormViewingScenario1, OpenFormViewingScenario2, ecc.). Invece, presento un altro livello di riferimento indiretto:

public interface IFormViewCreator{ 
     void CreateView(Form form); 
} 

public class MyFormService: IFormService{ 

     public Form OpenFormForEditing(int formId){ 
      //construct and return a concrete implementation of Form 
     } 

     public Form OpenFormForViewing(int formId, IFormViewCreator formViewCreator){ 
      //construct a concrete implementation of Form 
      //apply transformations to the dynamic field list 
      return formViewCreator.CreateView(form); 
     } 
} 

In superficie, questo sembra approccio accettabile e tuttavia v'è un certo odore. Vale a dire, l'interfaccia utente, che ha vissuto in ignorante beatitudine sui dettagli di implementazione di OpenFormForViewing, deve possedere la conoscenza e creare un'istanza di IFormViewCreator.

  1. Le mie domande sono due: Esiste un modo meglio per ottenere il componibilità che sto cercando? (forse da utilizzando un container IoC o una casa di fabbrica arrotolata per creare il calcestruzzo IFormViewCreator )?
  2. Ho danneggiato fondamentalmente l'astrazione qui?
+2

+1 per la buona domanda. – Lucero

+0

Sono d'accordo con te riguardo l'odore. Sembra che l'interfaccia utente non debba conoscere IFormViewCreator. Una domanda per te: l'interfaccia utente è a conoscenza dei parametri che dettano il "sapore" di cui dovresti tornare? Potrebbe avere più senso che l'interfaccia utente passi in un oggetto che funge da contenitore per i vari valori che determinano quali trasformazioni applicare. Il tuo metodo OpenFormForViewing() sarebbe quindi responsabile per l'inoltro alla classe factory, o per capire in modo esplicito quali "sapori" restituire. –

risposta

9

Come ho capito la domanda, è necessario modificare il modulo prima di inviarlo al livello dell'interfaccia utente. Mi sembra che uno Decorator sia sul posto. Conserva la vecchia interfaccia IFormService senza IFormViewCreator.

È ora possibile creare uno o più Decorating FormService (s) che implementano il filtraggio o la modifica desiderata.

public class MyDecoratingFormService : IFormService 
{ 
    private readonly IFormService formService; 

    public MyDecoratingFormService(IFormService formService) 
    { 
     if(formService == null) 
     { 
      throw new ArgumentNullException("formService"); 
     } 

     this.formService = formService; 
    } 

    public Form OpenFormForEditing(int formId) 
    { 
     var form = this.formService.OpenFormForEditing(formId); 
     return this.TransformForm(form); 
    } 

    public Form OpenFormForViewing(int formId) 
    { 
     var form = this.formService.OpenFormForViewing(formId); 
     return this.TransformForm(form); 
    } 

    public Form TransformForm(Form form) 
    { 
     // Implement transformation/filtering/modification here 
    } 
} 

Ora è possibile decorare l'implementazione IFormService originale con uno o più di questi Decoratori.

IFormService formService = new MyDecoratingFormService(new MyFormService()); 

È possibile avvolgere tutti gli elementi decorativi (ciascuno con la propria responsabilità) l'uno intorno all'altro come si desidera.

Non è necessario esplicitamente un contenitore DI per farlo, ma si adatta bene ad altri modelli DI. Io uso Decorator tutto il tempo :)

+0

+1 per una buona risposta. Spero di generare più discussioni prima di dare una risposta. –

+0

@Dirk - se pensi che sia una buona risposta, perché non accettare la risposta ??? –

+0

Perché SO non me lo consente. Non è disponibile alcun segno di spunta "Accetto". È perché ho messo una taglia su questa domanda? –

0

A meno che non voglio finire con un esplosione di metodi (OpenFormViewingScenario1, OpenFormViewingScenario2, ecc).

Non è possibile rimuovere la complessità intrinseca dei requisiti. In un modo o nell'altro, dovrai rilevare il caso d'uso e agire di conseguenza.

È possibile elencare tutte le combinazioni possibili o provare a calcolare l'elaborazione in modo intelligente, se possibile. Ad esempio, se a causa della natura stessa del requisito ci sono le possibilità di N * M = X, quindi avere un elenco completo dei metodi X non è davvero buono. Il fattore dovrebbe essere tale da creare i possibili moduli X da una composizione dei casi N e M.

Senza conoscere il requisito esatto è difficile da dire. Quindi ci sono molti modi possibili per calcolare tale composizione, ad es. Decorator, ChainOfResponsability, Filtro, ecc. Questi sono tutti modi per comporre la logica complessa.

Vale a dire, l'interfaccia utente, che viveva nella beatitudine ignorante circa i dettagli di implementazione di OpenFormForViewing, deve possedere conoscenza e creare un'istanza di IFormViewCreator.

La presentazione deve essere consegnata una forma creata da qualche altra parte, in modo da rimanere agnostica dalla creazione della forma. Il modulo dovrà comunque essere assemblato/creato da qualche parte.

In MVC, tale logica va nel controller che aggiornerebbe il modello/modulo e invierà alla visualizzazione/rendering corretta. Lo stesso può essere fatto con ASP.NET