¿Está bien tener una clase con solo propiedades para fines de refactorización?

79

Tengo un método que toma 30 parámetros. Tomé los parámetros y los puse en una clase, para poder pasar un parámetro (la clase) al método. ¿Está perfectamente bien en el caso de la refactorización pasar un objeto que encapsule todos los parámetros incluso si eso es todo lo que contiene?

Xaisoft
fuente
Las siguientes respuestas son buenas. Siempre configuro una clase para este propósito. ¿Está utilizando propiedades automáticas? msdn.microsoft.com/en-us/library/bb384054.aspx
Harv
También conocido como tipo POD (datos antiguos sin formato).
new123456
17
Si bien muchas de las respuestas dadas son excelentes, y de hecho es una buena idea refactorizar esos parámetros en algo un poco más fácil de manejar, diría que el hecho de que este método necesite 30 datos distintos para hacer su trabajo es una señal que está haciendo demasiado. Pero, de nuevo, no hemos visto el método en cuestión, ni siquiera conocemos su propósito.
usuario
1
Entonces, ese método que tomó todos los parámetros, ¿lo movió a la nueva clase? ¿O el método antiguo simplemente toma la nueva clase como su único parámetro? Me gustaría poner el método en la nueva clase, si tiene sentido. Las clases con solo datos y sin métodos se denominan clases anémicas, y se siente un poco menos OO. Sin embargo, no es una regla estricta y rápida, solo algo para mirar.
Kurt
@Michael: Estoy de acuerdo contigo. Otro punto: creo que no debe crear un objeto dedicado al método a menos que su interno tenga una especie de relación fuera del método.
Saturn Technologies

Respuestas:

75

Esa es una gran idea. Por lo general, es cómo se realizan los contratos de datos en WCF, por ejemplo.

Una ventaja de este modelo es que si agrega un nuevo parámetro, el consumidor de la clase no necesita cambiar solo para agregar el parámetro.

Como menciona David Heffernan, puede ayudar a autodocumentar el código:

FrobRequest frobRequest = new FrobRequest
{
    FrobTarget = "Joe",
    Url = new Uri("http://example.com"),
    Count = 42,
};
FrobResult frobResult = Frob(frobRequest);
McKay
fuente
Excelente. Tenía la impresión de que tener una clase con propiedades y sin métodos era inútil.
Xaisoft
2
Si solo contendrá miembros de datos, prefiero hacerlo estructura. El uso de struct da la impresión de que son solo datos.
Yousf
28
el uso de struct tiene efectos secundarios como copiar al pasar la semántica. Asegúrese de estar al tanto de eso antes de tomar tal decisión
Adrian Zanescu
Así es como se escriben generalmente los módulos javascript, y parece ser el mejor método para aceptar más o menos parámetros a medida que el módulo madura. someFunction ({myParam1: 'algo', myParam2: 'algoElse'});
Kristian
63

Si bien otras respuestas aquí señalan correctamente que pasar una instancia de una clase es mejor que pasar 30 parámetros, tenga en cuenta que una gran cantidad de parámetros puede ser un síntoma de un problema subyacente.

Por ejemplo, muchas veces los métodos estáticos aumentan en su número de parámetros, porque deberían haber sido métodos de instancia todo el tiempo, y estás pasando mucha información que podría mantenerse más fácilmente en una instancia de esa clase.

Alternativamente, busque formas de agrupar los parámetros en objetos de un nivel de abstracción superior. Volcar un montón de parámetros no relacionados en una sola clase es un último recurso en mi opinión.

Consulte ¿Cuántos parámetros son demasiados? para obtener más ideas sobre esto.

D'Arcy Rittich
fuente
¿En qué sentido estos parámetros no están relacionados? Todos se utilizan con este método. Esa es una relación muy fuerte. Además, los parámetros de la pila son generalmente preferibles al estado. Piense en el multihilo.
David Heffernan
12
Eso no se puede responder sin ver el código del OP, pero no estoy de acuerdo en que simplemente porque dos valores se usan juntos en un método, tienen una relación sólida. Si eso fuera cierto, el diseño OO terminaría significando la creación de una gran clase que contenga todas las propiedades posibles utilizadas en su aplicación.
D'Arcy Rittich
No, tienes razón, no necesariamente relacionado. Pero tampoco necesariamente sin relación. Entonces, como dices, necesitaría ver el código para estar seguro.
David Heffernan
23
30 parámetros? Me conformaría con probablemente no relacionado y también probablemente indicativo de un método que es demasiado largo, tiene una alta complejidad ciclomática y es un refugio para errores. Considere que estamos hablando de un método cuyo comportamiento tiene al menos 30 dimensiones en las que puede variar. Si hay pruebas unitarias para este método, no me gustaría ser el que las escriba TBH.
Steve Rowbotham
3
Otro escenario probable es que todos los parámetros estén relacionados, pero mal organizados. Es muy probable que los grupos de parámetros deban agruparse en objetos distintos en lugar de distribuirse por partes. Digamos que tengo un método que opera con información como "persona", "dirección", "prescripción" ... que fácilmente podría llegar a 30 parámetros si cada una de las piezas discretas de información se pasara a mi método por separado.
Nate CK
25

Es un buen comienzo. Pero ahora que tiene esa nueva clase, considere darle la vuelta al código. Mueva el método que toma esa clase como parámetro a su nueva clase (por supuesto, pasando una instancia de la clase original como parámetro). Ahora tiene un método grande, solo en una clase, y será más fácil dividirlo en métodos más pequeños, manejables y probables. Algunos de esos métodos pueden regresar a la clase original, pero una buena parte probablemente permanecerá en su nueva clase. Ha pasado de Introducir objeto de parámetro a Reemplazar método con Objeto de método .

Tener un método con treinta parámetros es una señal bastante fuerte de que el método es demasiado largo y complicado. Demasiado difícil de depurar, demasiado difícil de probar. Por lo tanto, debe hacer algo al respecto, y Introducir objeto de parámetro es un buen lugar para comenzar.

Carl Manaster
fuente
4
¡Esto es ABSOLUTAMENTE IMPORTANTE! Crear estos paquetes de atributos es genial solo porque es el primer paso para crear nuevas clases con métodos propios, y casi siempre encontrarás métodos que pertenecen a tus nuevas clases, ¡búscalos! Creo que esta es la respuesta más crítica en este grupo, debería estar cerca de la cima.
Bill K
15

Si bien la refactorización a un objeto de parámetro no es en sí misma una mala idea, no debería usarse para ocultar el problema de que una clase que necesita 30 piezas de datos provistas de otra parte aún podría ser una especie de olor a código. La refactorización de Introducir objeto de parámetro probablemente debería considerarse como un paso en el camino de un proceso de refactorización más amplio en lugar de como el final de ese procedimiento.

Una de las preocupaciones que realmente no aborda es la de Feature Envy. ¿El hecho de que la clase a la que se le pasa el objeto de parámetro esté tan interesada en los datos de otra clase no indica que tal vez los métodos que operan sobre esos datos deban trasladarse al lugar donde residen los datos? Es realmente mejor identificar grupos de métodos y datos que pertenecen juntos y agruparlos en clases, aumentando así la encapsulación y haciendo que su código sea más flexible.

Después de varias iteraciones de dividir el comportamiento y los datos con los que opera en unidades separadas, debería encontrar que ya no tiene clases con un número enorme de dependencias, lo que siempre es un mejor resultado final porque hará que su código sea más flexible.

Steve Rowbotham
fuente
10

Esa es una idea excelente y una solución muy común al problema. Los métodos con más de 2 o 3 parámetros se vuelven cada vez más difíciles de entender.

Encapsular todo esto en una sola clase hace que el código sea mucho más claro. Debido a que sus propiedades tienen nombres, puede escribir código autodocumentado como este:

params.height = 42;
params.width = 666;
obj.doSomething(params);

Naturalmente, cuando tienes muchos parámetros, la alternativa basada en la identificación posicional es simplemente horrible.

Otro beneficio más es que se pueden agregar parámetros adicionales al contrato de interfaz sin forzar cambios en todos los sitios de llamadas. Sin embargo, esto no siempre es tan trivial como parece. Si diferentes sitios de llamadas requieren valores diferentes para el nuevo parámetro, entonces es más difícil buscarlos que con el enfoque basado en parámetros. En el enfoque basado en parámetros, agregar un nuevo parámetro fuerza un cambio en cada sitio de llamada para proporcionar el nuevo parámetro y puede dejar que el compilador haga el trabajo de encontrarlos todos.

David Heffernan
fuente
9

Martin Fowler llama a este objeto Introducir parámetro en su libro Refactorización . Con esa cita, pocos lo llamarían una mala idea.

Austin Salonen
fuente
1
Sí, pero para 30 parámetros, algo está mal en otro lugar que debe arreglarse primero
manojlds
5

30 parámetros es un desastre. Creo que es mucho más bonito tener una clase con las propiedades. Incluso podría crear varias "clases de parámetros" para grupos de parámetros que encajen en la misma categoría.

C.Evenhuis
fuente
3

También podría considerar usar una estructura en lugar de una clase.

¡Pero lo que intentas hacer es muy común y una gran idea!

Tad Donaghe
fuente
De hecho, pensé en usar una estructura, pero tenía curiosidad por saber si habría alguna desventaja si uso una estructura.
Xaisoft
¿Por qué David? ¿Qué tiene que ver el número de campos con esto? Sólo curioso.
Tad Donaghe
2
Como cuestión de rendimiento. Si bien semánticamente tiene más sentido usar una estructura aquí, ya que solo el valor de cada campo importa y la identidad de referencia es irrelevante, 30 o más campos son más para copiar (digamos que son en su mayoría tipos de referencia; 120 bytes en 32 bits y 240 bytes en 64) que con una clase (4 u 8 bytes). Sin embargo, la naturaleza de copia por valor de las estructuras significa que una vez copiadas, el acceso será más rápido que con un tipo de referencia, por lo que el umbral donde la estructura es menos eficiente es mayor que el tamaño de 1 puntero que implica el anterior. En> 4 tamaños de puntero, es hora de considerarlo.
Jon Hanna
Increíble. ¡Gracias por la explicación! :)
Tad Donaghe
3

Puede ser razonable usar una clase Plain Old Data ya sea que esté refactorizando o no. Tengo curiosidad por saber por qué pensaste que podría no ser así.

Jon Hanna
fuente
No recuerdo exactamente, pero creo que cuando mencioné en algún lugar de aquí hace un tiempo que estaba creando una clase con solo propiedades, se despreció en ese momento. En cuanto a su segundo punto, ¿está diciendo que solo los parámetros que no cambian deben pasarse en el método, en otras palabras, si el método cambia el parámetro, no debe pasarse como parámetro?
Xaisoft
Las clases que son solo propiedades pueden tener mal olor (ver la respuesta de Steve Rowbotham) que indican algo que debería ser una clase "completa" en su lugar. Una buena señal es si terminas usando clases similares más de una vez. Sin embargo, este no siempre es el caso, y en un sorteo entre una clase de 30 campos y un método de 30 parámetros, hay una buena opción para inclinarse por el primero. Además, puede ser un comienzo en la construcción de esa clase "completa".
Jon Hanna
... Estoy eliminando mi comentario sobre la inmutabilidad, ya que estaba pensando más en la clase de que esto es solo un método público (donde un objeto de "solicitud" describe la intención de la persona que llama). Aquí, cambiar la "solicitud" puede generar confusión. En un caso único, es más práctico ser mutable y construir sobre la marcha. La inmutabilidad solo significaría reemplazar una llamada de 30 argumentos con un constructor de 30 argumentos seguido de una llamada, por lo que no habría ganancia.
Jon Hanna
3

¿Quizás los parámetros opcionales y con nombre de C # 4.0 sean una buena alternativa a esto?

De todos modos, el método que está describiendo también puede ser bueno para abstraer el comportamiento del programa. Por ejemplo, podría tener una función estándar SaveImage(ImageSaveParameters saveParams)en una interfaz donde ImageSaveParameterstambién hay una interfaz y puede tener parámetros adicionales dependiendo del formato de imagen. Por ejemplo, JpegSaveParameterstiene una Qualitypropiedad mientras que PngSaveParameterstiene una BitDepthpropiedad.

Así es como lo hace el cuadro de diálogo guardar guardar en Paint.NET, por lo que es un ejemplo muy real.

loog
fuente
3

Como se indicó anteriormente: es el paso correcto a seguir, pero considere también lo siguiente:

  • su método puede ser demasiado complejo (debería considerar dividirlo en más métodos, o incluso convertirlo en una clase separada)
  • si crea la clase para los parámetros, hágalo inmutable
  • si muchos de los parámetros podrían ser nulos o podrían tener algún valor predeterminado, es posible que desee utilizar el patrón de construcción para su clase.
Obtener
fuente
0

Tantas grandes respuestas aquí. Me gustaría agregar mis dos centavos.

El objeto de parámetro es un buen comienzo. Pero aún se puede hacer más. Considere lo siguiente (ejemplos de ruby):

/ 1 / En lugar de simplemente agrupar todos los parámetros, vea si puede haber una agrupación significativa de parámetros. Es posible que necesite más de un objeto de parámetro.

def display_line(startPoint, endPoint, option1, option2)

podría convertirse

def display_line(line, display_options)

/ 2 / El objeto de parámetro puede tener un número menor de propiedades que el número original de parámetros.

def double_click?(cursor_location1, control1, cursor_location2, control2)

podría convertirse

def double_click?(first_click_info, second_click_info) 
                       # MouseClickInfo being the parameter object type 
                       # having cursor_location and control_at_click as properties

Dichos usos le ayudarán a descubrir posibilidades de agregar un comportamiento significativo a estos objetos de parámetros. Descubrirá que se deshacen de su olor inicial de clase de datos antes para su comodidad. : -)

rpattabi
fuente