OK ... después de toda la discusión, estoy cambiando mi pregunta ligeramente para reflejar mejor un ejemplo concreto con el que estoy tratando.
Tengo dos clases ModelOne
y ModelTwo
, estas clases realizan un tipo similar de funcionalidad pero no están relacionadas entre sí. Sin embargo, tengo una tercera clase CommonFunc
que contiene algunas funciones públicas que se implementa en tanto ModelOne
y ModelTwo
y ha sido un factor fuera de acuerdo DRY
. Los dos modelos se instancian dentro de la ModelMain
clase (que a su vez se instancia en un nivel superior, etc., pero me detengo en este nivel).
El contenedor de IoC que estoy usando es Microsoft Unity . No pretendo ser un experto en él, pero entiendo que registras una tupla de interfaz y clase con el contenedor y cuando quieres una clase concreta le preguntas al contenedor IoC por cualquier objeto que coincida con una interfaz específica. Esto implica que para cada objeto que quiero instanciar desde Unity, debe haber una interfaz coincidente. Debido a que cada una de mis clases realiza una funcionalidad diferente (y no superpuesta), esto significa que hay una relación 1: 1 entre la interfaz y la clase 1 . Sin embargo, no significa que estoy escribiendo servilmente una interfaz para cada clase que escribo.
Así, en cuanto al código, termino 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 pregunta es sobre cómo organizar mi solución. ¿Debo mantener la clase y la interfaz juntas? ¿O debería mantener las clases y las interfaces juntas? P.EJ:
Opción 1 : organizada por nombre de clase
MySolution
|
|-MyProject
| |
|-Models
| |
|-Common
| |
| |-CommonFunc.cs
| |-ICommonFunc.cs
|
|-Main
| |
| |-IModelMain.cs
| |-ModelMain.cs
|
|-One
| |
| |-IModelOne.cs
| |-ModelOne.cs
|
|-Two
|
|-IModelTwo.cs
|-ModelTwo.cs
|
Opción 2 : organizada por funcionalidad (principalmente)
MySolution
|
|-MyProject
| |
|-Models
| |
|-Common
| |
| |-CommonFunc.cs
| |-ICommonFunc.cs
|
|-IModelMain.cs
|-IModelOne.cs
|-IModelTwo.cs
|-ModelMain.cs
|-ModelOne.cs
|-ModelTwo.cs
|
Opción 3 - Interfaz de separación e implementación
MySolution
|
|-MyProject
|
|-Interfaces
| |
| |-Models
| | |
| |-Common
| | |-ICommonFunc.cs
| |
| |-IModelMain.cs
| |-IModelOne.cs
| |-IModelTwo.cs
|
|-Classes
|
|-Models
| |
|-Common
| |-CommonFunc.cs
|
|-ModelMain.cs
|-ModelOne.cs
|-ModelTwo.cs
|
Opción 4 : llevar el ejemplo de funcionalidad más allá
MySolution
|
|-MyProject
| |
|-Models
| |
|-Components
| |
| |-Common
| | |
| | |-CommonFunc.cs
| | |-ICommonFunc.cs
| |
| |-IModelOne.cs
| |-IModelTwo.cs
| |-ModelOne.cs
| |-ModelTwo.cs
|
|-IModelMain.cs
|-ModelMain.cs
|
No me gusta la opción 1 debido al nombre de la clase en la ruta. Pero como estoy tendiendo a una relación 1: 1 debido a mi elección / uso de IoC (y eso puede ser discutible), esto tiene ventajas al ver la relación entre los archivos.
La opción 2 me resulta atractiva, pero ahora he enturbiado las aguas entre el ModelMain
y los submodelos.
La opción 3 funciona para separar la definición de interfaz de la implementación, pero ahora tengo estos descansos artificiales en los nombres de ruta.
Opción 4. Tomé la Opción 2 y la ajusté para separar los componentes del modelo principal.
¿Hay una buena razón para preferir uno sobre el otro? ¿O cualquier otro diseño potencial que me haya perdido?
1. Frank hizo un comentario de que tener una relación 1: 1 se remonta a los días C ++ de los archivos .h y .cpp. Sé de dónde viene. Mi comprensión de la Unidad parece ponerme en este rincón, pero tampoco estoy seguro de cómo salir de ella si también sigues el adagio de Program to an interface
Pero esa es una discusión para otro día.
2. He omitido los detalles de cada constructor de objetos. Aquí es donde el contenedor de IoC inyecta objetos según sea necesario.
fuente
Client1
necesita unIBase
, proporciona unDerived1
. CuandoClient2
necesita unIBase
, el IoC proporciona unDerived2
.interface
. Aninterface
es realmente solo una clase abstracta con todos los miembros virtuales.Respuestas:
Como una interfaz es abstractamente similar a una clase base, use la misma lógica que usaría para una clase base. Las clases que implementan una interfaz están estrechamente relacionadas con la interfaz.
Dudo que prefiera un directorio llamado "Clases base"; la mayoría de los desarrolladores no querrían eso, ni un directorio llamado "Interfaces". En c #, los directorios también son espacios de nombres por defecto, lo que lo hace doblemente confuso.
El mejor enfoque es pensar cómo dividiría las clases / interfaces si tuviera que poner algunas en una biblioteca separada, y organizar los espacios de nombres / directorios de manera similar. Las pautas de diseño de .NET Framework tienen sugerencias de espacios de nombres que pueden ser útiles.
fuente
Tomo el segundo enfoque, con algunas carpetas / espacios de nombres más en proyectos complejos, por supuesto. Significa que desconecto las interfaces de las implementaciones concretas.
En un proyecto diferente, es posible que deba conocer la definición de la interfaz, pero no es necesario conocer ninguna clase concreta que la implemente, especialmente cuando utiliza un contenedor de IoC. Por lo tanto, esos otros proyectos solo necesitan hacer referencia a los proyectos de interfaz, no a los proyectos de implementación. Esto puede mantener bajas las referencias y puede evitar problemas de referencia circular.
fuente
Como siempre con cualquier pregunta de diseño, lo primero que debe hacer es determinar quiénes son sus usuarios. ¿Cómo van a usar su organización?
¿Son codificadores que utilizarán sus clases? Entonces, separar las interfaces del código puede ser lo mejor.
¿Son mantenedores de su código? Luego, mantener las interfaces y clases juntas puede ser lo mejor.
Siéntate y crea algunos casos de uso. Entonces la mejor organización puede aparecer ante usted.
fuente
Creo que es mejor almacenar interfaces en la biblioteca separada. En primer lugar, este tipo de diseño aumenta la dependencia. Es por eso que la implementación de estas interfaces puede ser realizada por otro lenguaje de programación.
fuente