¿Cuándo debería y no debería usar la palabra clave 'nuevo'?

15

Vi una presentación de Google Tech Talk sobre Pruebas unitarias , dada por Misko Hevery, y me dijo que evitara usar la newpalabra clave en el código de lógica de negocios.

Escribí un programa y terminé usando la newpalabra clave aquí y allá, pero fueron principalmente para crear instancias de objetos que contienen datos (es decir, no tenían ninguna función o método).

Me pregunto si hice algo mal cuando utilicé la nueva palabra clave para mi programa. ¿Y dónde podemos romper esa 'regla'?

Sal
fuente
2
¿Puedes agregar una etiqueta relacionada con el lenguaje de programación? El nuevo existe en muchos lenguajes (C ++, Java, Ruby, por nombrar algunos) y tiene una semántica diferente.
sakisk

Respuestas:

16

Esta es más orientación que una regla estricta.

Al usar "nuevo" en su código de producción, está acoplando su clase con sus colaboradores. Si alguien quiere usar a otro colaborador, por ejemplo, algún tipo de colaborador simulado para pruebas unitarias, no puede hacerlo, porque el colaborador se crea en la lógica de su negocio.

Por supuesto, alguien necesita crear estos nuevos objetos, pero a menudo es mejor dejarlo en uno de dos lugares: un marco de inyección de dependencia como Spring, o en cualquier clase que esté instanciando su clase de lógica de negocios, inyectada a través del constructor.

Por supuesto, puedes llevar esto demasiado lejos. Si desea devolver una nueva ArrayList, entonces probablemente esté bien, especialmente si se trata de una Lista inmutable.

La pregunta principal que debe hacerse es "¿es la responsabilidad principal de este fragmento de código crear objetos de este tipo, o es solo un detalle de implementación que podría mover razonablemente a otro lugar?"

Bill Michell
fuente
1
Bueno, si quieres que sea una lista inmutable, deberías usar Collections.unmodifiableListo algo así. Pero sé lo que quieres decir :)
MatrixFrog
Sí, pero de alguna manera necesita crear la lista original que luego convierte en no modificable ...
Bill Michell
5

El quid de esta pregunta es al final: me pregunto si hice algo mal cuando utilicé la nueva palabra clave para mi programa. ¿Y dónde podemos romper esa 'regla'?

Si puede escribir pruebas unitarias efectivas para su código, entonces no hizo nada malo. Si su uso newhizo difícil o imposible probar el código de la unidad, entonces debe reevaluar su uso de nuevo. Podría extender este análisis a la interacción con otras clases, pero la capacidad de escribir pruebas unitarias sólidas suele ser un proxy bastante bueno.

ObscureRobot
fuente
5

En resumen, cada vez que usa "nuevo", está estrechamente acoplando la clase que contiene este código al objeto que se está creando; Para crear una instancia de uno de estos objetos, la clase que hace la instancia debe saber acerca de la clase concreta que se está instanciando. Por lo tanto, cuando use "nuevo", debe considerar si la clase en la que está colocando la instanciación es un "buen" lugar para que resida ese conocimiento, y si está dispuesto a realizar cambios en esta área si la forma del objeto instanciado iban a cambiar.

El acoplamiento apretado, que es un objeto que tiene conocimiento de otra clase concreta, no siempre se debe evitar; en algún nivel, algo, EN ALGÚN LUGAR, tiene que saber cómo crear este objeto, incluso si todo lo demás trata con el objeto al recibir una copia de él desde otro lugar. Sin embargo, cuando la clase que se crea cambia, cualquier clase que conozca la implementación concreta de esa clase debe actualizarse para tratar correctamente los cambios de esa clase.

La pregunta que siempre debe hacer es: "¿Hacer que esta clase sepa cómo crear esta otra clase se convertirá en una responsabilidad al mantener la aplicación?" Ambas metodologías de diseño principales (SOLID y GRASP) generalmente responderían "sí", por razones sutilmente diferentes. Sin embargo, son solo metodologías, y ambas tienen la limitación extrema de que no fueron formuladas en base al conocimiento de su programa único. Como tal, solo pueden errar por el lado de la precaución, y suponen que cualquier punto de acoplamiento apretado EVENTUALMENTE le causará un problema relacionado con la realización de cambios en uno o ambos lados de este punto. Debes tomar una decisión final sabiendo tres cosas; la mejor práctica teórica (que consiste en acoplar libremente todo porque cualquier cosa puede cambiar); el costo de implementar la mejor práctica teórica (que puede incluir varias capas nuevas de abstracción que facilitarán un tipo de cambio mientras obstaculizan otro); y la probabilidad del mundo real de que alguna vez sea necesario el tipo de cambio que está anticipando.

Algunas pautas generales:

  • Evite el acoplamiento estrecho entre bibliotecas de código compilado. La interfaz entre las DLL (o un EXE y sus DLL) es el lugar principal donde el acoplamiento estrecho presentará una desventaja. Si realiza un cambio en una clase A en DLL X, y la clase B en el EXE principal sabe acerca de la clase A, debe volver a compilar y liberar ambos binarios. Dentro de un solo binario, un acoplamiento más estricto es generalmente más permisible porque el binario completo debe reconstruirse para cualquier cambio de todos modos. A veces, tener que reconstruir múltiples archivos binarios es inevitable, pero debe estructurar su código para que pueda evitarlo cuando sea posible, especialmente en situaciones en las que el ancho de banda es escaso (como implementar aplicaciones móviles; impulsar una nueva DLL en una actualización es mucho más barato) que empujar todo el programa).

  • Evite el acoplamiento estrecho entre los principales "centros lógicos" de su programa. Puede pensar en un programa bien estructurado que consiste en cortes horizontales y verticales. Los sectores horizontales pueden ser niveles de aplicación tradicionales, como UI, Controlador, Dominio, DAO, Datos; los segmentos verticales pueden definirse para ventanas o vistas individuales, o para "historias de usuario" individuales (como crear un nuevo registro de algún tipo básico). Al hacer una llamada que se mueve hacia arriba, hacia abajo, hacia la izquierda o hacia la derecha en un sistema bien estructurado, generalmente debe abstraer dicha llamada. Por ejemplo, cuando la validación necesita recuperar datos, no debe tener acceso a la base de datos directamente, pero debe hacer una llamada a una interfaz para la recuperación de datos, que está respaldada por el objeto real que sabe cómo hacer esto. Cuando algún control de IU necesita realizar una lógica avanzada que involucre otra ventana, debería abstraer la activación de esta lógica a través de un evento y / o devolución de llamada; no tiene que saber qué se hará como resultado, lo que le permite cambiar lo que se hará sin cambiar el control que lo activa.

  • En cualquier caso, considere cuán fácil o difícil será hacer un cambio, y qué tan probable será dicho cambio. Si un objeto que está creando solo se usa desde un lugar, y no prevé ese cambio, entonces el acoplamiento apretado generalmente es más permisible, e incluso puede ser superior en esta situación al acoplamiento flojo. El acoplamiento flexible requiere abstracción, que es una capa adicional que evita el cambio a objetos dependientes cuando la implementación de una dependencia debe cambiar. Sin embargo, si la interfaz en sí misma debe cambiar (agregar una nueva llamada al método o agregar un parámetro a una llamada al método existente), entonces una interfaz en realidad aumenta la cantidad de trabajo necesario para realizar el cambio. Debe sopesar la probabilidad de que diferentes tipos de cambio afecten el diseño,

KeithS
fuente
3

Los objetos que solo contienen datos, o los DTO (objetos de transferencia de datos) están bien, los crea todo el día. Donde desea evitar newes en las clases que realizan acciones, desea que las clases consumidoras se programen contra la interfaz , donde pueden invocar esa lógica y consumirla, pero no ser responsables de crear las instancias de las clases que contienen la lógica . Esas clases de ejecución de acciones o que contienen lógica son dependencias. La charla de Misko está dirigida a inyectar esas dependencias y dejar que otra entidad sea responsable de crearlas.

Anthony Pegram
fuente
2

Otros ya han mencionado este punto, pero querían citar esto del Código Limpio del Tío Bob (Bob Martin) porque podría facilitar la comprensión del concepto:

"Un mecanismo poderoso para separar la construcción del uso es la Inyección de Dependencias (DI), la aplicación de la Inversión de Control (IoC) a la gestión de la dependencia. La Inversión de Control mueve las responsabilidades secundarias de un objeto a otros objetos que están dedicados al propósito, apoyando así la responsabilidad solo principio . En el contexto de la gestión de la dependencia, un objeto no debe asumir la responsabilidad de crear instancias de dependencias en sí. En su lugar, debe pasar esta responsabilidad a otro mecanismo de "autoridad", invirtiendo de ese modo el control. Debido a que la configuración es una preocupación mundial, este el mecanismo autoritario generalmente será la rutina "principal" o un contenedor de propósito especial ".

Creo que siempre es una buena idea seguir esta regla. Otros mencionaron el más importante IMO -> desacopla su clase de sus dependencias (colaboradores). La segunda razón es que hace que sus clases sean más pequeñas y más sencillas, más fáciles de entender e incluso reutilizar en otros contextos.

c_maker
fuente