Come organizzare al meglio i file di classe e di interfaccia?


17

OK .. dopo tutta la discussione sto cambiando leggermente la mia domanda per riflettere meglio un esempio concreto di cui mi sto occupando.


Ho due classi ModelOnee ModelTwo, Queste classi eseguono un tipo simile di funzionalità ma non sono correlate tra loro. Tuttavia ho una terza classe CommonFuncche contiene alcune funzionalità pubbliche che sono implementate in entrambi ModelOnee ModelTwoche sono state fattorizzate come da DRY. I due modelli sono istanziati all'interno della ModelMainclasse (che a sua volta è istanziata a un livello superiore ecc. - Ma mi sto fermando a questo livello).

Il contenitore IoC che sto usando è Microsoft Unity . Non pretendo di esserne un esperto, ma la mia comprensione è che registri una tupla di interfaccia e classe con il contenitore e quando vuoi una classe concreta chiedi al contenitore IoC qualunque oggetto corrisponda a un'interfaccia specifica. Ciò implica che per ogni oggetto che voglio creare un'istanza da Unity, ci deve essere un'interfaccia corrispondente. Poiché ciascuna delle mie classi esegue funzionalità diverse (e non sovrapposte), ciò significa che esiste un rapporto 1: 1 tra interfaccia e classe 1 . Tuttavia, ciò non significa che sto scrivendo con disinvoltura un'interfaccia per ogni classe che scrivo.

Quindi, per quanto riguarda il codice, finisco con 2 :

public interface ICommonFunc 
{ 
}

public interface IModelOne 
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelTwo
{ 
   ICommonFunc Common { get; } 
   .. 
}

public interface IModelMain 
{ 
  IModelOne One { get; } 
  IModelTwo Two { get; } 
  ..
}

public class CommonFunc : ICommonFunc { .. }

public class ModelOne : IModelOne { .. }

public class ModelTwo : IModelTwo { .. }

public class ModelMain : IModelMain { .. }

La domanda è su come organizzare la mia soluzione. Devo tenere la classe e l'interfaccia insieme? O dovrei tenere insieme classi e interfacce? PER ESEMPIO:

Opzione 1 : organizzata in base al nome della classe

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-Main
          |   |
          |   |-IModelMain.cs
          |   |-ModelMain.cs
          |
          |-One
          |   |
          |   |-IModelOne.cs
          |   |-ModelOne.cs
          |
          |-Two
              |
              |-IModelTwo.cs
              |-ModelTwo.cs
              |

Opzione 2 : organizzata per funzionalità (principalmente)

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Common
          |   |
          |   |-CommonFunc.cs
          |   |-ICommonFunc.cs
          |
          |-IModelMain.cs
          |-IModelOne.cs
          |-IModelTwo.cs
          |-ModelMain.cs
          |-ModelOne.cs
          |-ModelTwo.cs
          |

Opzione 3 - Interfaccia e implementazione separate

MySolution
  |
  |-MyProject
      |
      |-Interfaces
      |   |
      |   |-Models
      |   |   |
      |       |-Common
      |       |   |-ICommonFunc.cs
      |       |
      |       |-IModelMain.cs
      |       |-IModelOne.cs
      |       |-IModelTwo.cs
      |
      |-Classes
          | 
          |-Models
          |   |
              |-Common
              |   |-CommonFunc.cs
              |
              |-ModelMain.cs
              |-ModelOne.cs
              |-ModelTwo.cs
              |

Opzione 4 : approfondire l'esempio di funzionalità

MySolution
  |
  |-MyProject
  |   |
      |-Models
      |   |
          |-Components
          |   |
          |   |-Common
          |   |   |
          |   |   |-CommonFunc.cs
          |   |   |-ICommonFunc.cs
          |   |   
          |   |-IModelOne.cs
          |   |-IModelTwo.cs
          |   |-ModelOne.cs
          |   |-ModelTwo.cs
          |
          |-IModelMain.cs
          |-ModelMain.cs
          |

Non mi piace l'opzione 1 a causa del nome della classe nel percorso. Ma poiché sto tendendo al rapporto 1: 1 a causa della mia scelta / utilizzo dell'IoC (e questo potrebbe essere discutibile), questo ha dei vantaggi nel vedere la relazione tra i file.

L'opzione 2 mi piace, ma ora ho confuso le acque tra il ModelMaine il sottomodello.

L'opzione 3 funziona per separare la definizione dell'interfaccia dall'implementazione, ma ora ho queste interruzioni artificiali nei nomi dei percorsi.

Opzione 4. Ho preso l'opzione 2 e l'ho modificata per separare i componenti dal modello principale.

C'è una buona ragione per preferire l'una all'altra? O eventuali altri layout che mi sono perso?


1. Frank ha commentato che il rapporto 1: 1 riporta ai giorni C ++ di file .h e .cpp. So da dove viene. La mia comprensione di Unity sembra mettermi in questo angolo, ma non sono nemmeno sicuro di come uscirne se stai anche seguendo il proverbio di Program to an interface Ma questa è una discussione per un altro giorno.

2. Ho tralasciato i dettagli di ciascun costruttore di oggetti. Qui è dove il contenitore IoC inietta oggetti come richiesto.


1
Ugh. Puzza di avere un rapporto interfaccia / classe 1: 1. Quale contenitore IoC stai usando? Ninject fornisce meccanismi che non richiedono un rapporto 1: 1.
RubberDuck,

1
@RubberDuck FWIW Sto usando Unity. Non pretendo di esserne un esperto, ma se le mie lezioni sono ben progettate con singole responsabilità, come non finire con un rapporto 1: 1?
Peter M,

Hai bisogno di IBase o la base potrebbe essere astratta? Perché IDerivedOne quando implementa già IBase? Dovresti dipendere da IBase, non derivato. Non conosco Unity, ma altri contenitori IoC ti permettono di fare un'iniezione "sensibile al contesto". Fondamentalmente quando ha Client1bisogno di un IBase, fornisce un Derived1. Quando è Client2necessario IBase, l'IoC fornisce a Derived2.
RubberDuck,

1
Bene, se la base è astratta, non c'è motivo di averne una interface. An interfaceè davvero solo una classe astratta con tutti i membri virtuali.
RubberDuck,

1
RubberDuck è corretto. Le interfacce superflue sono semplicemente fastidiose.
Frank Hileman,

Risposte:


4

Poiché un'interfaccia è astrattamente simile a una classe base, usa la stessa logica che utilizzeresti per una classe base. Le classi che implementano un'interfaccia sono strettamente correlate all'interfaccia.

Dubito che preferiresti una directory chiamata "Classi di base"; la maggior parte degli sviluppatori non lo vorrebbe, né una directory chiamata "Interfacce". In c #, le directory sono anche spazi dei nomi per impostazione predefinita, il che lo rende doppiamente confuso.

L'approccio migliore è pensare a come spezzare le classi / interfacce se dovessi inserirne alcune in una libreria separata e organizzare gli spazi dei nomi / le directory in modo simile. Le linee guida per la progettazione del framework .net contengono suggerimenti sullo spazio dei nomi che potrebbero essere utili.


Ho una certa familiarità con il collegamento fornito, ma non sono sicuro di come sia correlato all'organizzazione dei file. Mentre VS imposta automaticamente i nomi dei percorsi per lo spazio dei nomi iniziale, so che questa è una scelta arbitraria. Inoltre ho aggiornato il mio codice "campione" e le possibilità di layout.
Peter M,

@PeterM Non sono sicuro di quale IDE si sta utilizzando, ma Visual Studio utilizza i nomi di directory per generare automaticamente le dichiarazioni dello spazio dei nomi quando si aggiunge una classe, ad esempio. Ciò significa che mentre puoi avere differenze tra nomi di directory e nomi di spazi dei nomi, è più doloroso lavorare in quel modo. Quindi la maggior parte delle persone non lo fa.
Frank Hileman,

Sto usando VS. E sì, conosco il dolore. Riporta ricordi del layout di file sicuro di Visual Source.
Peter M,

1
@PeterM Per quanto riguarda il rapporto 1: 1 tra interfaccia e classe. Alcuni strumenti lo richiedono. Considero questo come un difetto dello strumento - .net ha abbastanza capacità di riflessione per non aver bisogno di tali restrizioni in nessuno di questi strumenti.
Frank Hileman,

1

Prendo il secondo approccio, ovviamente con alcune cartelle / spazi dei nomi in progetti complessi. Significa che disaccoppiamento le interfacce dalle implementazioni concrete.

In un progetto diverso, potrebbe essere necessario conoscere la definizione dell'interfaccia, ma non è affatto necessario conoscere alcuna classe concreta che lo implementa, soprattutto quando si utilizza un contenitore IoC. Quindi quegli altri progetti devono solo fare riferimento ai progetti di interfaccia, non ai progetti di implementazione. Ciò può mantenere bassi i riferimenti e prevenire problemi di riferimento circolari.


Ho rivisto il mio esempio di codice e aggiunto alcune altre opzioni
Peter M,

1

Come sempre con qualsiasi domanda di progettazione, la prima cosa da fare è determinare chi sono i tuoi utenti. Come useranno la tua organizzazione?

Sono programmatori che useranno le tue lezioni? Quindi separare le interfacce dal codice potrebbe essere la cosa migliore.

Sono manutentori del tuo codice? Quindi tenere insieme le interfacce e le classi potrebbe essere la cosa migliore.

Siediti e crea alcuni casi d'uso. Quindi la migliore organizzazione potrebbe apparire davanti a te.


-2

Penso che sia meglio archiviare le interfacce nella libreria separata. Innanzitutto, questo tipo di design aumenta la dipendenza. Ecco perché l'implementazione di queste interfacce può essere effettuata da un altro linguaggio di programmazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.