10

Ho cercato di implementare un'applicazione con accoppiamento lento in un'app MVC5 asp.net. Ho un regolatore:Come impedire l'uso improprio del costruttore in classe C#

public class HeaderController : Controller 
    { 
     private IMenuService _menuService; 

     public HeaderController(IMenuService menuService) 
     { 
      this._menuService = menuService; 
     } 

     // 
     // GET: /Header/ 
     public ActionResult Index() 
     { 

      return View(); 
     } 


     public ActionResult GetMenu() 
     { 

      MenuItem menu = this._menuService.GetMenu(); 
      return View("Menu", menu); 

     } 

    } 

E il servizio viene utilizzato in questo controller è:

public class MenuService : IMenuService 
{ 
    private IMenuRespository _menuRepository; 

    public MenuService(IMenuRespository menuRepository) 
    { 
     this._menuRepository = menuRepository; 
    } 

    public MenuItem GetMenu() 
    { 
     return this._menuRepository.GetMenu(); 
    } 
} 

E il repository utilizzato nella classe di servizio è:

public class MenuRepository : IMenuRespository 
    { 
     public MenuItem GetMenu() 
     { 
      //return the menu items 
     } 
    } 

Le interfacce utilizzate per il servizio e il deposito sono come tali:

public interface IMenuService 
    { 
     MenuItem GetMenu(); 
    } 

public interface IMenuRespository 
    { 
     MenuItem GetMenu(); 
    } 

Il costruttore per HeaderController utilizza MenuService utilizzando Iniezione costruttore e io ho ninject come contenitore DI che gestisce questo.

Tutto grandi opere - tranne che, nel mio controller, posso ancora fare questo:

MenuItem menu = new MenuService(new MenuRepository()); 

... che rompe l'architettura. Come posso evitare che il "nuovo" venga usato in questo modo?

+2

Basta non fare quella? Non è sicuro il motivo per cui questo è più problematico del nominare il controller 'HeaderCantroller' "che rompe l'architettura" ... C'è una ragione particolare che stai cercando (come un particolare pattern che non può essere fermato con una buona nomenclatura/revisione del codice)? –

+0

Non è possibile convalidare l'argomento all'interno del costruttore e generare un'eccezione se utilizzata in modo errato? –

+1

@AlexeiLevenkov: l'utilizzo del costruttore viola direttamente la 'separazione delle preoccupazioni' e accoppia strettamente HeaderController a MenuService e MenuRepository. –

risposta

7

Un modo per farlo sarebbe spostare le interfacce e le implementazioni in progetti/assiemi separati di Visual Studio e fare solo riferimento al progetto di implementazione nei progetti che effettivamente ne hanno bisogno - tutto il resto può fare riferimento al progetto di interfaccia per il proprio IMenuService - a quel punto il codice può consumare l'interfaccia, ma in realtà non fa nuove implementazioni.

È quindi possibile fare riferimento al progetto di implementazione ovunque si DI nelle proprie dipendenze.

WebApp Soluzione:

WebApp Proj (Controllers etc.) -> Service Interface Proj

Servizio Impl Progetto -> Service Interface Proj

Anche così questo è un buon approccio, non è affatto infallibile - l'altra componente è l'educazione e la revisione del codice per elaborare le migliori pratiche che funzionano per la tua squadra come testabilità e iniezione di dipendenza.

+0

Hmm - ma questo codice non lo include: MenuItem menu = this._menuService.GetMenu(); bisogno di un riferimento all'attuale implementazione di MenuService? –

+0

sicuro - ma non si sa nulla dell'istanza che si sta utilizzando - a parte il fatto che implementa 'IMenuService' – BrokenGlass

+1

Ma se ho il riferimento, posso comunque chiamare semplicemente MenuService (nuovo MenuRepository()), no? –

4

Presumo parte dei problemi con l'istanziazione manuale dell'oggetto può venire con lavorare con un team di grandi dimensioni, per cui alcuni membri utilizzano la tecnica di iniezione del costruttore nel modo sbagliato. Se questo è il caso, ho trovato più o meno educandoli sul framework risolto la maggior parte dei problemi. Occasionalmente, potresti trovare qualcuno che lo fa nel modo sbagliato, ma non spesso. Un'altra alternativa potrebbe essere quella di aggiungere un attributo [EditorBrowsable(EditorBrowsableState.Never)] al costruttore del controllore. Il costruttore sparirà dall'intelligenza; beh, sembrerà sparito. Può ancora essere usato, tuttavia.

È possibile suddividere le implementazioni in un'altra DLL non direttamente referenziata (implicitamente referenziata) dal progetto MVC, quindi, poiché non esiste un riferimento diretto, non è possibile utilizzare tali tipi direttamente. Con le interfacce in un progetto, a cui ogni progetto fa riferimento, e il progetto con le implementazioni indirettamente referenziate, sarebbero quindi incluse solo le interfacce. Mi raccomando di includere un riferimento diretto nel progetto di test dell'unità, se si stanno facendo test unitari, per migliorare la copertura del test.

+0

Spiega alle persone di non usarlo: -)? Qual è questa risposta? SE qualcuno non segue le linee guida, come le limiti a livello di codice? E la domanda è: perché dovremmo implementare interfacce se le persone possono accedere direttamente all'attuazione effettiva? –

+3

@JitendraPancholi: questa è in realtà una buona risposta. Questo è un problema generale che vedo con gli architetti, che cercano piuttosto di impedire alle persone di essere in grado di fare cose sbagliate, che spesso portano a complesse strutture di codice inutili dove gli sviluppatori trovano ancora una quantità enorme di cose da fare e modi di lavorare sbagliati intorno alle restrizioni dell'architetto. Invece, gli sviluppatori dovrebbero essere istruiti e il team dovrebbe avere revisioni del codice, perché sono molto più efficaci. – Steven

+0

@Steven: Invece di queste restrizioni per linee guida, usa l'approccio menzionato da BrokenGlass e AlexeiLevenkov nella risposta sopra per creare una buona architettura per ridurre gli errori umani. –

2

Come principio generale di progettazione, le interfacce (Contratti) dovrebbero essere in un unico assieme e l'implementazione dovrebbe in un altro assieme. L'assembly Contracts deve essere di riferimento nel progetto MVC e l'assembly implementato deve essere copiato nella cartella "bin". Quindi utilizzare "Dynamic Module Loading" per caricare i tipi. In questo modo eviterai il problema sopra citato e questa è una soluzione più ampia. Perché è possibile sostituire l'implementazione senza creare UI e gruppi di contatti.

+5

Quale principio di design è questo? – Steven

3

Coppia di possibili opzioni (che non ho mai provato, ma potrebbe avere qualche gambe):

  • si potrebbe forse scrivere una regola FxCop che errori se il costruttore viene utilizzato nel codice.

  • è possibile contrassegnare il costruttore come obsoleto e far fallire il server di compilazione se si utilizzano metodi obsoleti nel codice.

Se il contenitore DI utilizza attraverso la riflessione Tutto questo dovrebbe essere ok (anche se nel caso FxCop si potrebbe probabilmente non buttare se fosse in un metodo nello spazio dei nomi Ninject)

+0

Meglio implementare esplicitamente i metodi di interfaccia, quindi Se qualcuno prova a creare un'istanza della classe e ad accedere ai metodi, quindi non apparirà nemmeno a causa del livello di accesso. –

+1

@JitendraPancholi ok, ma quelle opzioni sono coperte in altre risposte, stavo cercando di dare alcune alternative che potrebbero essere possibili. Non mi rendevo conto che esisteva un solo modo per rivestire questo gatto. –

Problemi correlati