¿Cómo organizar mejor los archivos de clase e interfaz?

17

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 ModelOney ModelTwo, estas clases realizan un tipo similar de funcionalidad pero no están relacionadas entre sí. Sin embargo, tengo una tercera clase CommonFuncque contiene algunas funciones públicas que se implementa en tanto ModelOney ModelTwoy ha sido un factor fuera de acuerdo DRY. Los dos modelos se instancian dentro de la ModelMainclase (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 ModelMainy 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.

Peter M
fuente
1
Ugh Huele que tienes una relación 1: 1 de interfaz / clase. ¿Qué contenedor de IoC estás usando? Ninject proporciona mecanismos que no requieren una relación 1: 1.
RubberDuck
1
@RubberDuck FWIW Estoy usando Unity. No pretendo ser un experto en eso, pero si mis clases están bien diseñadas con responsabilidades únicas, ¿cómo no termino con una proporción de casi 1: 1?
Peter M
¿Necesita IBase o la base puede ser abstracta? ¿Por qué tener IDerivedOne cuando ya implementa IBase? Debería depender de IBase, no derivado. No sé acerca de Unity, pero otros contenedores de IoC le permiten realizar inyecciones "sensibles al contexto". Básicamente cuando Client1necesita un IBase, proporciona un Derived1. Cuando Client2necesita un IBase, el IoC proporciona un Derived2.
RubberDuck
1
Bueno, si la base es abstracta, entonces no hay razón para tenerla interface. An interfacees realmente solo una clase abstracta con todos los miembros virtuales.
RubberDuck
1
RubberDuck es correcto. Las interfaces superfluas son simplemente molestas.
Frank Hileman

Respuestas:

4

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.

Frank Hileman
fuente
Estoy familiarizado con el enlace que proporcionó, pero no estoy seguro de cómo se relaciona con la organización de archivos. Si bien VS predetermina los nombres de ruta para el espacio de nombres inicial, sé que esta es una elección arbitraria. También he actualizado mi código 'muestra' y posibilidades de diseño.
Peter M
@PeterM No estoy seguro de qué IDE está utilizando, pero Visual Studio usa los nombres de directorio para generar automáticamente las declaraciones de espacio de nombres al agregar una clase, por ejemplo. Esto significa que si bien puede haber diferencias entre los nombres de directorio y los nombres de espacios de nombres, es más doloroso trabajar de esa manera. Entonces la mayoría de la gente no hace eso.
Frank Hileman
Estoy usando VS. Y sí, sé dolor. Trae recuerdos del diseño de archivos de Visual Source Safe.
Peter M
1
@PeterM Con respecto a la relación 1: 1 de interfaz a clase. Algunas herramientas requieren esto. Veo esto como un defecto de la herramienta: .net tiene suficientes capacidades de reflexión para no necesitar tales restricciones en dicha herramienta.
Frank Hileman
1

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.

Bernhard Hiller
fuente
Revisé mi ejemplo de código y agregué algunas opciones más
Peter M
1

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.

Shawnhcorey
fuente
-2

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.

Larissa Savchekoo
fuente