2013-08-05 3 views
7

Possiedo un servizio Web che eseguirà operazioni con alcuni dati passati (in particolare, InfoPath xml da una raccolta documenti di SharePoint). Attualmente sto usando Ninject per gestire i dati del modulo "strategia" da caricare. Ecco po 'di codice (domanda segue):Associazione di rilegatura basata sulla stringa

Web Service (punto di ingresso)

namespace Web.Services 
{ 
    public bool AddForm(XmlDocument form, string formName) 
    { 
     IKernel kernel = new StandardKernel(new FormsModule()); 
     var ctx = kernel.Get<IPFormDataContext>(formName); 

     return ctx.DoWork(form); 
    } 
} 

Ninject cose relative

namespace Core.Modules 
{ 
    public class FormsModule : NinjectModule 
    { 
     public override void Load() 
     { 
      Bind<IPFormDataContext>().ToSelf().Named("FormA"); 
      Bind<IPFormDataContext>().ToSelf().Named("FormB"); 
      // Snip 

      Bind<IPFormDataStrategy>().To<FormAStratgey>() 
       .WhenParentNamed("FormA"); 
      Bind<IPFormDataStrategy>().To<FormBStrategy>() 
       .WhenParentNamed("FormB"); 
      // Snip 
     } 
    } 
} 

modello correlati Cose

namespace Core.Forms 
{ 
    public class IPFormDataContext 
    { 
     private IPFormDataStrategy _ipFormDataStrategy; 

     public IPFormDataContext(IPFormDataStrategy strategy) 
     { 
      _ipFormDataStrategy = strategy; 
     } 

     public bool DoWork(XmlDocument form) 
     { 
      return _ipFormDataStrategy.DoWork(form); 
     } 
    } 

    public abstract class IPFormDataStrategy 
    { 
     public abstract bool DoWork(XmlDocument form); 
    } 
} 

namespace Core.Forms.FormStrategies 
{ 
    class FormAStrategy : IPFormDataStrategy 
    { 
     public override bool DoWork(XmlDocument form) 
     { 
      // Deserialize form using (xsd.exe generated) FormAData 
      // and perform some operation on the resulting data. 
      return resultOfWork; 
     } 
    } 
} 

FormBStrategy è più o meno lo stesso , così come le altre 7 strategie che non ho elencato. Sto cercando di trovare un modo per passare un modulo xml al servizio web e chiamare la deserializzazione del modulo corretta in base al tipo di modulo che sta arrivando.

Il codice sopra "funziona"; ma sembra che sto facendo una sorta di posizione di servizio in Ninject, che da quello che sto leggendo è una cosa negativa . Ma non riesco a pensare a un modo corretto per farlo. Non sono morto impostato sull'utilizzo di Ninject, o qualsiasi framework IOC/DI per quella materia.

È quello che sto facendo ... sbagliato? Potrei essere indicato nella giusta direzione?

+1

Questo sembra un buon posto per un generico: potresti fare qualcosa come 'IPFormDataContext dove T: IPFormDataStrategy' per ripulirlo? Non sono così familiare con il ninject. – lukegravitt

risposta

3

Se le classi che si presentano nel codice di esempio sono accurate (ovvero non ci sono molti più metodi e proprietà). Quindi la soluzione più semplice possibile potrebbe funzionare e si può sbarazzarsi di un certo numero di classi/dipendenze sulle classi.

Una soluzione semplice, che non si basa su un quadro/contenitore potrebbe essere:

public static class FormsProcessing 
{ 
    private static ConcurrentDictionary<string, Func<FormProcessor>> _registeredProcessors = new ConcurrentDictionary<string, Func<FormProcessor>>(); 

    public delegate bool FormProcessor(XmlDocument form); 

    public static void RegisterProcessor(string formKey, Func<FormProcessor> formsProcessorFactory) 
    { 
     _registeredProcessors.AddOrUpdate(formKey, formsProcessorFactory, (k, current) => formsProcessorFactory); 
    } 

    public static FormProcessor GetProcessorFor(string formKey) 
    { 
     Func<FormProcessor> processorFactory; 
     if (_registeredProcessors.TryGetValue(formKey, out processorFactory); 
      return processorFactory(); 
     return null; 
    } 

    public static bool Process(string formKey, XmlDocument form) 
    { 
     var processor = GetProcessorFor(formKey); 
     if (null == processor) 
      throw new Exception(string.Format("No processor for '{0}' forms available", formKey)); 
     return processor(form); 
    } 
} 

Usage:

namespace Web.Services 
{ 
    public class MyServiceClass 
    { 
     public bool AddForm(XmlDocument form, string formName) 
     { 
      return FormsProcessing.Process(formName, form); 
     } 
    } 
} 

E 'semplice ed esplicito, e non ha bisogno o esporre alcuna dipendenza su alcune strutture delle classi IPFormDataContext e IPFormDataStrategy. L'unica dipendenza esplicita che si ha è un delegato con la firma FormProcessor.

Simile a un contenitore, è necessario eseguire le registrazioni da qualche parte:

FormsProcessing.RegisterProcessor("FormA",() => new FormAStrategy().DoWork); 
FormsProcessing.RegisterProcessor("FormB",() => new FormBStrategy().DoWork); 

alternativa sarebbe facile aggiungere una qualche forma di (a base di convenzione) di registrazione automatica per la scansione di assemblee per il convegno (ad esempio, un firma dell'interfaccia).

3

Due cose che non mi piacciono:

  1. Creazione di un kernel all'interno del metodo AddForm. Questo non dovrebbe mai accadere, poiché inverte il pattern IoC, invece la classe a cui appartiene AddForm dovrebbe richiedere qualsiasi dipendenza di cui ha bisogno.
  2. L'uso di stringhe magiche. Non sembra giusto richiedere agli utenti di AddForm() di inviare una stringa che denomina la strategia.

Non sono abbastanza sicuro su come risolvere questo problema; una cosa che viene in mente è aggiungere una dipendenza Func<string, IPFormDataStrategy> alla classe che possiede AddForm (chiamarla classe X). Sto immaginando qualcosa di simile:

namespace Web.Services 
{ 
    public class X 
    { 
     private readonly Func<string, IPFormDataStrategy> _strategyResolver; 

     public X(Func<string, IPFormDataStrategy> strategyResolver) 
     { 
      _strategyResolver = strategyResolver; 
     } 

     public bool AddForm(XmlDocument form, string formName) 
     { 
      return _strategyResolver(formName).DoWork(form); 
     } 
    } 
} 

quindi è possibile utilizzare ToMethod per legare Func<string, IPFormDataStrategy>:

public class FormsModule : NinjectModule 
{ 
    public override void Load() 
    { 
     Bind<FormAStrategy>().ToSelf(); 
     Bind<FormBStrategy>().ToSelf(); 

     Bind<Func<string, IPFormDataStrategy>>().ToMethod(context => 
      new Func<string, IPFormDataStrategy>(formName => { 
       switch (formName) 
       { 
        case "FormA": 
         return context.Kernel.Get<FormAStrategy>(); 
         // Note, could also simply "return new FormAStrategy()" here. 
        case "FormB": 
         return context.Kernel.Get<FormBStrategy>(); 
        default: 
         throw new InvalidOperationException(formName + " is unrecognized"); 
       } 
      }) 
     ); 
    } 
} 

si può trovare questo inutilmente complessa, e forse lo è ... Mi piace perché rende esplicita la dipendenza della classe X (cioè, ottiene una strategia data un nome di modulo), piuttosto che dargli accesso all'intero kernel. Questo approccio consolida anche la logica per ottenere una strategia in una singola istruzione switch. Essa si basa ancora sulle corde magiche, ma non sono sicuro di come ottenere intorno a quello, senza sapere di più ... contesto

+0

L'applicazione che chiama il servizio Web è un gestore eventi Elenco SharePoint. Volevo mantenere il gestore di eventi il ​​più semplice possibile, quindi la stringa magica. I requisiti di questo progetto includono la possibilità di aggiungere ulteriori moduli con un impatto minimo in futuro.Sto bene aggiornando il servizio web di volta in volta, ma l'aggiornamento di un gestore di eventi dell'elenco è stato così doloroso. – Slipfish

1

Service Locator è generally an anti-pattern, ma la cosa importante è capire il motivo per cui è un anti modello. Le ragioni sono solitamente legate al refactoring e alla sicurezza del tipo. Penso che il modo migliore per determinare se stai facendo qualcosa di sbagliato è ridurre il problema ai suoi requisiti assolutamente assoluti, e quindi giudicare il percorso più semplice per arrivarci.

quanto ho capito, le vostre esigenze sono:

  1. ricezione dati del modulo XML in un webservice
  2. Sulla base del tipo di modulo, chiamare la logica appropriata

mie ulteriori domande si farebbe be:

  1. Come si identifica il tipo di modulo? È un campo nel documento xml? (se è così, usalo)
  2. I tipi di modulo potrebbero cambiare spesso?

Quando si riduce ad esso, è necessario utilizzare una sorta di identificatore. Come ha sottolineato @McGarnagle, le stringhe magiche possono non essere sincronizzate con il codice. Potresti usare il nome Type della classe Strategy, ma ha lo stesso problema "potrebbe non essere sincronizzato".

Se i tipi di modulo non sono suscettibili di modifiche, è sufficiente utilizzare un'istruzione switch e creare istanze proprie delle istanze Strategy. La semplicità è il miglior modello di progettazione da seguire per la manutenibilità.

Se è probabile che cambino, il localizzatore di servizio potrebbe funzionare. Se l'implementazione di Service Locator è limitata solo a questo codice, non sarà così terribile da mantenere.

E su Ninject, non sono sicuro se questo benchmark è ancora valido, ma Funq è molto più veloce e mi piace la sintassi migliore. (Se si decide di utilizzare un contenitore DI)