¿Múltiples clases con el mismo nombre, pero diferentes espacios de nombres?

11

Me he encontrado con algún código (C # si es importante) que tiene clases que tienen el mismo nombre, pero difieren en sus espacios de nombres. Todos tienden a representar la misma cosa lógica, pero a menudo son diferentes "vistas" del mismo objeto. Los diferentes espacios de nombres son a veces parte de la misma solución, e incluso el mismo dll.

Tengo mis pensamientos, pero no pude encontrar nada definitivo sobre esta práctica para considerar un uso inteligente de las herramientas disponibles o un patrón para evitar.

Esta pregunta sobre los nombres de los pitufos toca esto, pero desde la otra dirección. La desambiguación de los nombres probablemente requeriría un prefijo arbitrario y omnipresente, que esa pregunta quería eliminar.

¿Cuáles son las pautas en torno a esta práctica?

Telastyn
fuente
1
En OOP hay muchas excepciones, pero al principio considero este mal diseño, especialmente si todas las clases comparten el mismo nivel de accesibilidad. Cuando leo el nombre de una clase, en cualquier archivo, quiero saber en qué espacio de nombres se ubica la clase inmediatamente sin tener que referir las directivas de espacio de nombres.
Leopold Asperger
Hay un caso válido para tener múltiples objetos relacionados que tratan con el mismo estado, pero generalmente están separados por función. Quizás tenga un objeto que adapte un modelo para una vista. Otro podría realizar un cálculo en el objeto. Otro podría ser un adaptador o fachada. Lo que podría tener FooAdapter, FooViewer, FooCalculator, etc, pero ciertamente no es el nombre del mismo. Sin embargo, sin más información sobre las clases específicas de las que está hablando, es difícil dar una respuesta.
@JohnGaughan: considérelo Employeecomo ejemplo. Hay un empleado que es para otros empleados, uno para RRHH y otro para exposición externa. Todos se llaman "empleado" y todos tienen variedades de ayudantes (EmployeeRepository, EmployeeView, etc.). Todos están en el mismo dll, y aunque comparten algunas propiedades comunes (nombre, título), todos difieren (número de teléfono, salario).
Telastyn
Después de haber trabajado con sistemas que modelan Employeevarias veces, parece que necesita un modelo con la unión de todos los atributos e incluye un atributo typeo department. De lo contrario, hay una duplicación innecesaria de código.
55
¿No era esta una de las razones para comenzar con los espacios de nombres? Para permitir colisiones de nombres?
Dan Pichelman

Respuestas:

5

Intentaré dar una respuesta contra tal uso, después de haber visto y trabajado en esto también en uno de mis proyectos.

Legibilidad de código

En primer lugar, considere que la legibilidad del código es importante y esta práctica es contraria a eso. Alguien lee un fragmento de código y digamos que tiene una función doSomething(Employee e). Esto ya no es legible, porque debido a las 10 Employeeclases diferentes que existen en diferentes paquetes, primero tendrá que recurrir a la declaración de importación para averiguar cuál es realmente su entrada.

Sin embargo, esta es una vista de alto nivel y, a menudo, tenemos enfrentamientos de nombres aparentemente aleatorios, que a nadie le importan o incluso encuentran, porque el significado se puede derivar del código restante y en qué paquete está. Así que uno podría incluso discutir que, localmente, no hay problema, porque, por supuesto, si ve Employeedentro de un hrpaquete, debe saber que estamos hablando de una vista de recursos humanos del empleado.

Sin embargo, las cosas se rompen tan pronto como dejas estos paquetes. Una vez que está trabajando en un módulo / paquete / etc. diferente y necesita trabajar con un empleado, ya está sacrificando la legibilidad si no califica completamente su tipo. Además, tener 10 Employeeclases diferentes significa que la finalización automática de su IDE ya no funcionará y tendrá que elegir manualmente el tipo de empleado.

Duplicación de código

Debido a que la naturaleza de cada una de estas clases está relacionada entre sí, su código se deteriorará debido a una gran cantidad de duplicaciones. En la mayoría de los casos, tendrá algo como el nombre de un empleado o algún número de identificación, que cada una de las clases tiene que implementar. A pesar de que cada clase agrega su propio tipo de vista, si no comparten los datos subyacentes de los empleados, terminará con enormes cantidades de código inútil, pero costoso.

Complejidad de código

¿Qué puede ser tan complejo que puedas preguntar? Después de todo, cada una de las clases puede mantenerlo tan simple como quiera. Lo que realmente se convierte en un problema es cómo propaga los cambios. En un software razonablemente rico en funciones, puede cambiar los datos de los empleados, y desea reflejar eso en todas partes. Digamos que una mujer se acaba de casar y hay que cambiarle el nombre de XaYdebido a eso. Lo suficientemente difícil como para hacer esto bien en todo el lugar, pero aún más difícil cuando tienes todas estas clases distintas. Dependiendo de sus otras opciones de diseño, esto puede significar fácilmente que cada una de las clases tiene que implementar su propio tipo de oyente para recibir notificaciones de cambios, lo que básicamente se traduce en un factor que se aplica a la cantidad de clases con las que tiene que lidiar. . Y más duplicación de código, por supuesto, y menos legibilidad de código ... Factores como este pueden ser ignorables en el análisis de complejidad, pero seguramente son molestos cuando se aplican al tamaño de su base de código.

Código de comunicación

Además de los problemas anteriores con la complejidad del código, que también están relacionados con las opciones de diseño, también está reduciendo su claridad de diseño de nivel superior y pierde la capacidad de comunicarse adecuadamente con expertos en dominios. Cuando habla de arquitectura, diseños o requisitos, ya no es libre de hacer declaraciones simples como given an employee, you can do .... Un desarrollador ya no sabrá lo que employeerealmente significa en ese momento. Aunque un experto en dominios lo sabrá, por supuesto. Todos lo hacemos. algo así como. Pero en términos del software ya no es tan fácil comunicarse.

¿Cómo deshacerse del problema?

Después de darse cuenta de esto y si su equipo está de acuerdo en que es un problema lo suficientemente grande como para abordarlo, entonces tendrá que encontrar una manera de lidiar con eso. Por lo general, no puede pedirle a su gerente que le dé a todo el equipo una semana libre para que puedan sacar la basura. Entonces, en esencia, tendrá que encontrar una manera de eliminar parcialmente estas clases, una a la vez. La parte más importante de esto es decidir, con todo el equipo, qué Employeees realmente. No , no integrar todos los atributos individuales en un dios-empleado. Piense más en un empleado principal y, lo más importante, decida dónde Employeeresidirá esa clase.

Si realiza revisiones de código, es particularmente fácil asegurarse de que el problema al menos no crezca más, es decir, detenga a todos en su camino cuando quieran agregar otro Employeenuevamente. También tenga cuidado de que los nuevos subsistemas se adhieran a lo acordado Employeey no se les permita acceder a las versiones anteriores.

Dependiendo de su lenguaje de programación, es posible que también desee marcar las clases que eventualmente se eliminarán con algo como @Deprecatedayudar a su equipo a darse cuenta de inmediato de que están trabajando con algo que tendrá que ser cambiado.

En cuanto a deshacerse de las clases obsoletas de los empleados, puede decidir para cada caso individual cómo se elimina mejor, o simplemente acordar un patrón común. Puede pegar el nombre de la clase y envolverlo con el empleado real, puede usar patrones (viene a la mente decorador o adaptador), o, o o.

En pocas palabras: esta "práctica" es técnicamente sólida, pero está llena de costos ocultos que solo ocurrirán más adelante. Si bien es posible que no pueda deshacerse inmediatamente del problema, puede comenzar de inmediato con contener sus efectos nocivos.

Franco
fuente
Tener una clase Core parece una buena solución. Otras clases pueden extenderse de él. Sobre el IDE, la finalización automática es lo suficientemente inteligente como para saber qué clase sugerirle para que coincida mejor con el contexto. En general, no veo por qué este es un problema tan grande, especialmente si la base de código se usa internamente.
InformadoA
1
No es lo suficientemente inteligente una vez que estás fuera de un contexto. Considere una clase, que realmente necesita las dos clases problemáticas: el autocompletado no ayudará en absoluto. Y ni siquiera los diferenciará de las otras diez clases. Pero el verdadero problema es ese nuevo miembro del equipo, que ni siquiera sabe cuáles son todos esos dominios de paquetes y cuál debería elegir.
Frank
Si se usan varias clases con el mismo nombre en el mismo contexto, entonces es difícil. No he visto ese caso. He visto varias clases con el mismo nombre, pero a menudo no se usan en el mismo contexto que usted mencionó.
InformadoA
@randomA: lamentablemente, ese caso es bastante común en esta base de código.
Telastyn
Puntos positivos, pero realmente no ha dado una solución al problema central, solo la metodología una vez que se resuelve el problema central ("lo que realmente es un empleado"). La única "solución" obvia ("integrar todos los atributos individuales en un dios empleado") que descartó, con razón. Digamos que tiene DB.Employee, Marshalling.Employee, ViewModels.Employee, GUI.Views.Employee, etc., ¿cuál es su solución?
Pablo H
9

El Scala Collection Framework es un buen ejemplo de esto. Hay colecciones mutables e inmutables, así como colecciones seriales y paralelas (y en el futuro tal vez también distribuidas). Entonces, hay, por ejemplo, cuatro Maprasgos:

scala.collection.Map
scala.collection.immutable.Map
scala.collection.mutable.Map
scala.collection.concurrent.Map

más tres ParMaps:

scala.collecion.parallel.ParMap
scala.collecion.parallel.immutable.ParMap
scala.collecion.parallel.mutable.ParMap

Aún más interesante:

scala.collection.immutable.Map            extends scala.collection.Map
scala.collection.mutable.Map              extends scala.collection.Map
scala.collection.concurrent.Map           extends scala.collection.mutable.Map

scala.collecion.parallel.ParMap           extends scala.collection.Map
scala.collecion.parallel.immutable.ParMap extends scala.collecion.parallel.ParMap
scala.collecion.parallel.mutable.ParMap   extends scala.collecion.parallel.ParMap

Entonces, ParMapse ParMapextiende se extiende Mapy Mapse Mapextiende se extiende Map.

Pero, seamos sinceros: ¿qué otra cosa sería que llamarlos? Esto tiene mucho sentido. ¡Para eso están los espacios de nombres!

Jörg W Mittag
fuente
3
"¡Para eso están los espacios de nombres!" => +1
svidgen
Me gusta esto, pero en el mundo C # / .NET cuando moví clases paralelas a un subespacio de nombres paralelo, esto choca con la clase auxiliar System.Threading.Parallel, que estoy usando mucho para obtener paralelismo. No quería usar el espacio de nombres 'Concurrente', ya que eso ya se está utilizando para significar 'acceso concurrente' en las clases de colección. Como compromiso, he optado por el subespacio de nombres 'Paralelo'.
redcalx
2

Esta es una pregunta realmente interesante donde las dos respuestas esencialmente opuestas son correctas. Aquí hay un ejemplo del mundo real de esta discusión que estamos teniendo sobre un proyecto mío.

Creo que hasta ahora hay dos consideraciones muy importantes que te harían querer ir en una dirección u otra, basándose en las dos respuestas de @Jorg y @Frank:

  1. Si anticipa una situación en la que podría ser necesario usar más de una de las clases con el mismo nombre dentro del mismo contexto de código, esta es una razón para no usar el mismo nombre. Es decir, si necesita trabajar hr.Employeepero al mismo tiempo necesita ver los permisos a través de security.Employee, se volverá molesto muy rápido.
  2. Por el contrario, si todas sus clases con el mismo nombre tienen las mismas API o muy similares, entonces ese es un buen ejemplo de cuándo debe usar el mismo nombre con diferentes espacios de nombres. El ejemplo de Scala es precisamente este, donde todas las Mapclases implementan la misma interfaz o son subclases entre sí. En este caso, podría decirse que la ambigüedad o "confusión" puede considerarse buena, porque el usuario puede tratar correctamente todas las implementaciones de la clase o interfaz ... que es el punto de las interfaces y subclases.
S'pht'Kr
fuente