¿Es el acoplamiento con cadenas "más flojo" que con los métodos de clase?

18

Estoy comenzando un proyecto de grupo escolar en Java, usando Swing. Es una interfaz gráfica de usuario sencilla en la aplicación de escritorio de la base de datos.

El profesor nos dio el código del proyecto del año pasado para que pudiéramos ver cómo hace las cosas. Mi impresión inicial es que el código es mucho más complicado de lo que debería ser, pero imagino que los programadores a menudo piensan esto cuando miran el código que no acaban de escribir.

Espero encontrar razones por las cuales su sistema es bueno o malo. (Le pregunté al profesor y él dijo que vería más tarde por qué es mejor, lo que no me satisface)

Básicamente, para evitar cualquier acoplamiento entre sus objetos persistentes, modelos (lógica de negocios) y vistas, todo se hace con cadenas. Los objetos persistentes que se almacenan en la base de datos son tablas hash de cadenas, y los modelos y vistas se "suscriben" entre sí, proporcionando claves de cadena para los "eventos" a los que se suscriben.

Cuando se activa un evento, la vista o modelo envía una cadena a todos sus suscriptores que deciden qué hacer para ese evento. Por ejemplo, en uno de los métodos de escucha de acción de vistas (creo que esto simplemente establece el bicycleMakeField en el objeto persistente):

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

Esa llamada finalmente llega a este método en el modelo del vehículo:

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

El profesor dice que esta forma de hacer cosas es más extensible y mantenible que tener la vista simplemente llamando a un método en un objeto de lógica de negocios. Él dice que no hay acoplamiento entre las vistas y los modelos porque no saben de la existencia de los demás.

Creo que este es un tipo de acoplamiento peor, porque la vista y el modelo tienen que usar las mismas cadenas para funcionar. Si elimina una vista o modelo, o comete un error tipográfico en una cadena, no obtendrá errores de compilación. También hace que el código sea mucho más largo de lo que creo que debe ser.

Quiero discutir esto con él, pero él usa su experiencia en la industria para contrarrestar cualquier argumento que yo, el estudiante inexperto, pueda hacer. ¿Hay alguna ventaja en su enfoque que me estoy perdiendo?


Para aclarar, quiero comparar el enfoque anterior, con tener la vista obviamente acoplada al modelo. Por ejemplo, puede pasar el objeto del modelo del vehículo a la vista y luego, para cambiar la "marca" del vehículo, haga lo siguiente:

vehicle.make = bicycleMakeField.getText();

Esto reduciría 15 líneas de código que actualmente SÓLO se usan para configurar la marca del vehículo en un solo lugar, en una sola línea de código legible. (Y dado que este tipo de operación se realiza cientos de veces en toda la aplicación, creo que sería una gran victoria para la legibilidad y la seguridad).


Actualizar

El líder de mi equipo y yo reestructuramos el marco de la forma en que queríamos hacerlo usando la escritura estática, informamos al profesor y terminamos ofreciéndole una demostración. Es lo suficientemente generoso como para dejarnos usar nuestro marco siempre y cuando no le pidamos ayuda, y si podemos mantener al resto de nuestro equipo al día, lo que nos parece justo.

Philip
fuente
12
ANTIGUO TESTAMENTO. Creo que tu profesor debería actualizarse y no usar código antiguo. En la academia no hay razón para usar una versión JAVA de 2006 cuando es 2012.
Farmor
3
Por favor, manténganos informados cuando finalmente obtenga su respuesta
JeffO
44
Independientemente de la calidad del código o de la sabiduría de la técnica, debe eliminar la palabra "magia" de su descripción de las cadenas utilizadas como identificadores. "Cuerdas mágicas" implica que el sistema no es lógico, y va a colorear su punto de vista y envenenar cualquier discusión que tenga con su instructor. Las palabras como "claves", "nombres" o "identificadores" son más neutrales, tal vez más descriptivas y es más probable que den lugar a una conversación útil.
Caleb
1
Cierto. No los he llamado cuerdas mágicas mientras hablaba con él, pero tienes razón, no hay necesidad de hacer que suene peor de lo que es.
Philip
2
El enfoque que describe su profesor (notificar a los oyentes a través de eventos nombrados como cadenas) puede ser bueno. En realidad, las enumeraciones podrían tener más sentido, pero este no es el mayor problema. El verdadero problema que veo aquí es que el objeto que recibe la notificación es el modelo en sí mismo, y luego debe enviarlo manualmente según el tipo de evento. En un lenguaje donde las funciones son de primera clase, registraría una función , no un objeto para un evento. En Java, supongo que uno debería usar algún tipo de objeto envolviendo una sola función
Andrea

Respuestas:

32

El enfoque que propone su profesor se describe mejor como mecanografiado en cadena y es incorrecto en casi todos los niveles.

El acoplamiento se reduce mejor en la inversión de dependencia , que lo hace mediante un envío polimórfico, en lugar de cablear varios casos.

back2dos
fuente
3
Escribe "casi todos los niveles", pero no ha escrito por qué no es (en su opinión) un caso adecuado en este ejemplo. Sería interesante saberlo.
Hakre 01 de
1
@hakre: No hay casos adecuados, al menos no en Java. Dije que "está mal en casi todos los niveles", porque es mejor que no usar ninguna indirecta y simplemente lanzar todo el código en un solo lugar. Sin embargo, el uso de constantes de cadenas mágicas (globales) como base para un envío manual es solo una forma complicada de usar objetos globales al final.
back2dos
Entonces su argumento es: nunca hay casos adecuados, ¿entonces este tampoco es uno? Eso no es un argumento y en realidad no es de mucha ayuda.
Hakre
1
@hakre: Mi argumento es que este enfoque es malo por varias razones (párrafo 1: en la explicación de tipeado en cadena se dan suficientes razones para evitar esto) y no se debe usar, porque hay una mejor (párrafo 2 )
back2dos
3
@hakre Me imagino que una situación que justificaría este enfoque sería si un asesino en serie lo hubiera pegado con cinta adhesiva a su silla y estuviera sosteniendo una motosierra sobre su cabeza, riéndose maniáticamente y ordenándole que use literales de cuerda en todas partes para la lógica de envío. Además de eso, es preferible confiar en las características del lenguaje para hacer este tipo de cosas. En realidad, puedo pensar en otro caso relacionado con cocodrilos y ninjas, pero eso es un poco más complicado.
Rob
19

Tu profesor lo está haciendo mal . Está tratando de desacoplar completamente la vista y el modelo forzando la comunicación a viajar a través de una cadena en ambas direcciones (la vista no depende del modelo y el modelo no depende de la vista) , lo que anula totalmente el propósito del diseño orientado a objetos y MVC. Parece que, en lugar de escribir clases y diseñar una arquitectura basada en OO, está escribiendo módulos dispares que simplemente responden a mensajes basados ​​en texto.

Para ser algo justo con el profesor, está tratando de crear en Java lo que se parece mucho a la interfaz común .NET llamada INotifyPropertyChanged , que notifica a los suscriptores de los eventos de cambio mediante la aprobación de cadenas que especifican qué propiedad ha cambiado. Al menos en .NET, esta interfaz se usa principalmente para comunicarse desde un modelo de datos para ver objetos y elementos GUI (enlace).

Si se hace bien , tomar este enfoque puede tener algunos beneficios¹:

  1. La Vista depende del Modelo, pero el Modelo no necesita depender de la Vista. Esto es apropiado Inversión de Control .
  2. Tiene un solo lugar en su código de Vista que gestiona la respuesta a las notificaciones de cambio del modelo, y solo un lugar para suscribirse / darse de baja, lo que reduce la probabilidad de una pérdida de memoria de referencia colgada.
  3. Hay menos gastos generales de codificación cuando desea agregar o eliminar un evento del editor del evento. En .NET, hay un mecanismo incorporado de publicación / suscripción llamado, eventspero en Java tendría que crear clases u objetos y escribir código de suscripción / cancelación de suscripción para cada evento.
  4. La mayor desventaja de este enfoque es que el código de suscripción puede no estar sincronizado con el código de publicación. Sin embargo, esto también es un beneficio en algunos entornos de desarrollo. Si se desarrolla un nuevo evento en el modelo y la vista aún no está actualizada para suscribirse, es probable que la vista siga funcionando. Del mismo modo, si se elimina un evento, tiene un código muerto, pero aún podría compilarse y ejecutarse.
  5. Al menos en .NET, Reflection se usa de manera muy efectiva aquí para llamar automáticamente a los captadores y establecedores dependiendo de la cadena que se pasó al suscriptor.

Entonces, en resumen (y para responder a su pregunta), se puede hacer, pero el enfoque que está tomando su profesor tiene la culpa de no permitir al menos una dependencia unidireccional entre la Vista y el Modelo. Simplemente no es práctico, demasiado académico, y no es un buen ejemplo de cómo usar las herramientas disponibles.


¹ Busque en Google para INotifyPropertyChangedencontrar un millón de formas en que las personas intentan evitar el uso de cuerdas mágicas al implementarlo.

Kevin McCormick
fuente
Parece que describiste SOAP aquí. : D ... (pero sí, entiendo tu punto y tienes razón)
Konrad Rudolph
11

enums (o public static final String s) deben usarse en lugar de cadenas mágicas.

Tu profesor no entiende OO . Por ejemplo, tener una clase de vehículo concreto?
¿Y que esta clase entienda la marca de una bicicleta?

El vehículo debe ser abstracto y debe haber una subclase concreta llamada Bike. Jugaría según sus reglas para obtener una buena calificación y luego olvidaría su enseñanza.

Farmor
fuente
3
A menos que el objetivo de este ejercicio sea mejorar el código utilizando mejores principios de OO. En ese caso, refactorizar.
joshin4colours
2
@ joshin4colours Si StackExchange lo estuviera calificando, estaría en lo cierto; pero dado que el calificador es la misma persona a la que se le ocurrió la idea, nos daría malas notas a todos porque sabe que su implementación fue mejor.
Dan Neely
7

Creo que tienes un buen punto, las cuerdas mágicas son malas. Alguien en mi equipo tuvo que depurar un problema causado por cadenas mágicas hace un par de semanas (pero fue en su propio código que escribieron, así que mantuve la boca cerrada). Y sucedió ... en la industria !!

La ventaja de su enfoque es que podría ser más rápido codificar, especialmente para pequeños proyectos de demostración. Las desventajas son que es propenso a los errores tipográficos y cuanto más se usa la cadena, más posibilidades hay de que un código tipográfico arruine las cosas. Y si alguna vez desea cambiar una cadena mágica, refactorizar cadenas es más difícil que refactorizar variables, ya que las herramientas de refactorización también pueden tocar cadenas que contienen la cadena mágica (como una subcadena) pero no son la cadena mágica, y no deberían deben tocarse. Además, es posible que deba mantener un diccionario o un índice de cadenas mágicas para que los nuevos desarrolladores no comiencen a inventar nuevas cadenas mágicas con el mismo propósito, y no pierdan el tiempo buscando cadenas mágicas existentes.

En este contexto, parece que una enumeración podría ser mejor que las cadenas mágicas o incluso las constantes de cadena globales. Además, los IDE a menudo le brindarán algún tipo de asistencia de código (autocompletar, refactorizar, buscar todas las referencias, etc.) cuando use cadenas o enumeraciones constantes. Un IDE no ofrecerá mucha ayuda si usa cadenas mágicas.

FrustratedWithFormsDesigner
fuente
Estoy de acuerdo en que las enumeraciones son mejores que los literales de cadena. Pero incluso entonces, ¿es realmente mejor llamar a un "stateChangeRequest" con una clave de enumeración, o simplemente llamar a un método en el modelo directamente? De cualquier manera, el modelo y la vista están acoplados en ese único lugar.
Philip
@Philip: Bueno, supongo que para desacoplar el modelo y verlo en ese punto, es posible que necesites algún tipo de clase de controlador para hacer eso ...
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner - Sí. Una arquitectura MVC es la más desacoplada
cdeszaq
Bien, si otra capa ayudara a desacoplarlos, no discutiría en contra de eso. Pero esa capa aún podría escribirse estáticamente y usar métodos reales en lugar de mensajes de cadena o enumeración para conectarlos.
Philip
6

Usar 'experiencia en la industria' es un argumento pobre. Su profesor necesita encontrar un argumento mejor que ese :-).

Como otros han dicho, el uso de cadenas mágicas ciertamente está mal visto, el uso de enumeraciones se considera mucho más seguro. Una enumeración tiene significado y alcance , las cuerdas mágicas no. Aparte de eso, la forma en que su profesor está desacoplando las preocupaciones se considera una técnica más antigua y más frágil que la utilizada en la actualidad.

Veo que @backtodos ha cubierto esto mientras respondía esto, así que simplemente agregaré mi opinión de que al usar la Inversión de control (de la cual la Inyección de dependencias es una forma, se ejecutará en el marco de Spring en la industria en poco tiempo. ..) se considera una forma más moderna de lidiar con este tipo de desacoplamiento.

Martijn Verburg
fuente
2
Totalmente de acuerdo academia debe tener requisitos mucho más altos que la industria. La academia == mejor forma teórica; La industria == mejor forma práctica
Farmor
3
Desafortunadamente, algunos de los que enseñan esto en la academia lo hacen porque no pueden cortarlo en la industria. En el lado positivo, algunos de los académicos son brillantes y no deben mantenerse ocultos en alguna oficina corporativa.
FrustratedWithFormsDesigner
4

Seamos bastante claros; existe inevitablemente cierto acoplamiento allí. El hecho de que haya un tema suscribible es un punto de acoplamiento, como lo es la naturaleza del resto del mensaje (como "cadena única"). Dado eso, la pregunta se convierte en si el acoplamiento es mayor cuando se realiza mediante cadenas o tipos. La respuesta a eso depende de si le preocupa el acoplamiento que se distribuye en el tiempo o el espacio: si los mensajes se van a preservar en un archivo antes de leerlos más tarde, o si se van a enviar a otro proceso (especialmente si eso no está escrito en el mismo idioma), el uso de cadenas puede hacer las cosas mucho más simples. La desventaja es que no hay verificación de tipo; los errores no se detectarán hasta el tiempo de ejecución (y a veces ni siquiera entonces).

Una estrategia de mitigación / compromiso para Java puede ser usar una interfaz escrita, pero donde esa interfaz escrita está en un paquete separado. Debe consistir solo en Java interfacesy tipos de valores básicos (por ejemplo, enumeraciones, excepciones). No debe haber ningún implementaciones de interfaces en ese paquete. Luego, todos implementan contra eso y, si es necesario transmitir los mensajes de una manera compleja, una implementación particular de un delegado de Java puede usar cadenas mágicas según sea necesario. También hace que sea mucho más fácil migrar el código a un entorno más profesional como JEE u OSGi, y ayuda mucho a cosas pequeñas como las pruebas ...

Compañeros de Donal
fuente
4

Lo que su profesor propone es el uso de "cuerdas mágicas". Si bien "combina libremente" las clases entre sí, lo que permite cambios más fáciles, es un antipatrón; las cadenas deben usarse MUY, MUY raramente para contener o controlar las instrucciones del código, y cuando tiene que suceder absolutamente, el valor de las cadenas que está utilizando para controlar la lógica debe definirse central y constantemente para que haya UNA autoridad sobre el valor esperado de cualquier cadena particular

La razón por la cual es muy simple; las cadenas no se comprueban en el compilador para determinar la sintaxis o la coherencia con otros valores (en el mejor de los casos, se verifica la forma adecuada con respecto a los caracteres de escape y otro formato específico del idioma dentro de la cadena). Puedes poner lo que quieras en una cadena. Como tal, puede obtener un algoritmo / programa irremediablemente roto para compilar, y no es hasta que se ejecuta que se produce un error. Considere el siguiente código (C #):

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

... todo este código se compila, primer intento, pero el error es obvio con una segunda mirada. Existen numerosos ejemplos de este tipo de programación, y todos están sujetos a este tipo de error, INCLUSO SI define cadenas como constantes (porque las constantes en .NET se escriben en el manifiesto de cada biblioteca en la que se usan, lo que debe entonces todos se recompilarán cuando se cambie el valor definido para que el valor cambie "globalmente"). Como el error no se detecta en tiempo de compilación, debe detectarse durante las pruebas en tiempo de ejecución, y eso solo está garantizado con una cobertura de código del 100% en pruebas unitarias con ejercicio de código adecuado, que generalmente solo se encuentra en la prueba de fallas más absoluta , sistemas en tiempo real.

KeithS
fuente
2

En realidad, esas clases todavía están estrechamente unidas. ¡Excepto que ahora están estrechamente acoplados de tal manera que el compilador no puede decirte cuándo se rompen las cosas!

Si alguien cambia "BicycleMake" a "BicyclesMake", nadie descubrirá que las cosas están rotas hasta el tiempo de ejecución.

Los errores de tiempo de ejecución son mucho más costosos de corregir que los errores de tiempo de compilación: esto es solo todo tipo de maldad.

17 de 26
fuente