Tengo esta función API:
public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d,
string e, string f, out Guid code)
No me gusta Porque el orden de los parámetros se vuelve innecesariamente significativo. Se hace más difícil agregar nuevos campos. Es más difícil ver lo que se pasa. Es más difícil refactorizar el método en partes más pequeñas porque crea otra sobrecarga al pasar todos los parámetros en las subfunciones. El código es más difícil de leer.
Se me ocurrió la idea más obvia: tener un objeto encapsulando los datos y pasarlos en lugar de pasar cada parámetro uno por uno. Esto es lo que se me ocurrió:
public class DoSomeActionParameters
{
public string A;
public string B;
public DateTime C;
public OtherEnum D;
public string E;
public string F;
}
Eso redujo mi declaración de API a:
public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)
Agradable. Parece muy inocente, pero en realidad introdujimos un gran cambio: introdujimos la mutabilidad. Porque lo que habíamos estado haciendo anteriormente era pasar un objeto inmutable anónimo: parámetros de función en la pila. Ahora creamos una nueva clase que es muy mutable. Creamos la capacidad de manipular el estado de la persona que llama . Eso apesta. Ahora quiero que mi objeto sea inmutable, ¿qué hago?
public class DoSomeActionParameters
{
public string A { get; private set; }
public string B { get; private set; }
public DateTime C { get; private set; }
public OtherEnum D { get; private set; }
public string E { get; private set; }
public string F { get; private set; }
public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d,
string e, string f)
{
this.A = a;
this.B = b;
// ... tears erased the text here
}
}
Como puede ver, en realidad volví a crear mi problema original: demasiados parámetros. Es obvio que ese no es el camino a seguir. ¿Que voy a hacer? La última opción para lograr tal inmutabilidad es usar una estructura "de solo lectura" como esta:
public struct DoSomeActionParameters
{
public readonly string A;
public readonly string B;
public readonly DateTime C;
public readonly OtherEnum D;
public readonly string E;
public readonly string F;
}
Eso nos permite evitar constructores con demasiados parámetros y lograr la inmutabilidad. En realidad soluciona todos los problemas (ordenamiento de parámetros, etc.). Todavía:
- Todos (incluidos FXCop y Jon Skeet) están de acuerdo en que exponer los campos públicos es malo .
- Eric Lippert et al dicen que confiar en campos de solo lectura para la inmutabilidad es una mentira .
Fue entonces cuando me confundí y decidí escribir esta pregunta: ¿Cuál es la forma más directa en C # para evitar el problema de "demasiados parámetros" sin introducir mutabilidad? ¿Es posible usar una estructura de solo lectura para ese propósito y, sin embargo, no tener un mal diseño de API?
Aclaraciones
- Suponga que no hay violación del principio de responsabilidad única. En mi caso original, la función simplemente escribe los parámetros dados en un solo registro DB.
- No estoy buscando una solución específica para la función dada. Estoy buscando un enfoque generalizado para tales problemas. Estoy específicamente interesado en resolver el problema de "demasiados parámetros" sin introducir mutabilidad o un diseño terrible.
ACTUALIZAR
Las respuestas proporcionadas aquí tienen diferentes ventajas / desventajas. Por lo tanto, me gustaría convertir esto a una wiki comunitaria. Creo que cada respuesta con código de muestra y Pros / Contras sería una buena guía para problemas similares en el futuro. Ahora estoy tratando de averiguar cómo hacerlo.
DoSomeActionParameters
es un objeto desechable, que se descartará después de la llamada al método.Respuestas:
Un estilo adoptado en los marcos suele ser como agrupar parámetros relacionados en clases relacionadas (pero una vez más problemático con la mutabilidad):
fuente
Utilice una combinación de constructor y API de estilo de lenguaje específico de dominio - Interfaz fluida. El API es un poco más detallado, pero con intellisense es muy rápido de escribir y fácil de entender.
fuente
DoSomeAction
era un método para ello.Lo que tienes allí es una indicación bastante segura de que la clase en cuestión está violando el Principio de Responsabilidad Única porque tiene demasiadas dependencias. Busque formas de refactorizar esas dependencias en grupos de dependencias de fachada .
fuente
Simplemente cambie su estructura de datos de parámetros de a
class
astruct
ay estará listo.El método ahora obtendrá su propia copia de la estructura. El método no puede observar los cambios realizados en la variable de argumento, y la persona que llama no puede observar los cambios que realiza el método en la variable. El aislamiento se logra sin inmutabilidad.
Pros:
Contras:
fuente
¿Qué tal crear una clase de generador dentro de su clase de datos? La clase de datos tendrá todos los establecedores como privados y solo el constructor podrá establecerlos.
fuente
DoSomeActionParameters.Builder
se puede usar una instancia para crear y configurar exactamente unaDoSomeActionParameters
instancia. Después de llamarBuild()
los cambios posteriores a lasBuilder
propiedades de 's, continuará modificando las propiedades de la instancia originalDoSomeActionParameters
, y las llamadas posteriores aBuild()
continuarán devolviendo la mismaDoSomeActionParameters
instancia. Realmente debería serlopublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
.No soy un programador de C # pero creo que C # admite argumentos con nombre: (F # sí y C # es en gran medida una característica compatible para ese tipo de cosas) Sí: http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342
Entonces llamar a su código original se convierte en:
Esto no requiere más espacio / pensamiento que la creación de su objeto y tiene todos los beneficios, del hecho de que no ha cambiado lo que está sucediendo en el sistema infalible en absoluto. Ni siquiera tiene que recodificar nada para indicar que los argumentos están nombrados
Editar: aquí hay un artículo que encontré al respecto. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ Debo mencionar que C # 4.0 admite argumentos con nombre, 3.0 no
fuente
¿Por qué no simplemente hacer una interfaz que imponga la inmutabilidad (es decir, solo getters)?
Es esencialmente su primera solución, pero obliga a la función a usar la interfaz para acceder al parámetro.
y la declaración de función se convierte en:
Pros:
struct
soluciónContras:
DoSomeActionParameters
es una clase que podría asignarse aIDoSomeActionParameters
fuente
IDoSomeActionParameters
y desea capturar los valores allí no tiene forma de saber si mantener la referencia será suficiente o si debe copiar los valores a algún otro objeto. Las interfaces legibles son útiles en algunos contextos, pero no como un medio para hacer que las cosas sean inmutables.Sé que esta es una vieja pregunta, pero pensé en meterme con mi sugerencia, ya que solo tuve que resolver el mismo problema. Ahora, sin duda, mi problema era ligeramente diferente al suyo, ya que tenía el requisito adicional de no querer que los usuarios pudieran construir este objeto por sí mismos (toda la hidratación de los datos provenía de la base de datos, por lo que podía encarcelar toda la construcción internamente). Esto me permitió usar un constructor privado y el siguiente patrón;
fuente
Podría usar un enfoque de estilo de constructor, aunque dependiendo de la complejidad de su
DoSomeAction
método, esto podría ser un poco pesado. Algo en este sentido:fuente
Además de la respuesta manji, es posible que desee dividir una operación en varias más pequeñas. Comparar:
y
Para aquellos que no conocen POSIX, la creación del niño puede ser tan fácil como:
Cada elección de diseño representa una compensación sobre las operaciones que puede realizar.
PD. Sí, es similar al constructor, solo al revés (es decir, en el lado de la persona que llama en lugar de la persona que llama). Puede o no ser mejor que el generador en este caso específico.
fuente
aquí es un poco diferente de Mikeys, pero lo que estoy tratando de hacer es hacer todo lo menos posible para escribir
DoSomeActionParameters es inmutable, ya que puede y no puede crearse directamente, ya que su constructor predeterminado es privado.
El inicializador no es inmutable, sino solo un medio de transporte.
El uso aprovecha el inicializador en el Inicializador (si obtiene mi deriva) Y puedo tener valores predeterminados en el constructor predeterminado del Inicializador
Los parámetros serán opcionales aquí, si desea que se requieran algunos, puede ponerlos en el constructor predeterminado de Initializer
Y la validación podría ir en el método Crear
Entonces ahora parece que
Todavía soy un poco kooki, lo sé, pero lo intentaré de todos modos
Editar: al mover el método de creación a una estática en el objeto de parámetros y agregar un delegado que pasa el inicializador, se elimina algo de lo extraño de la llamada
Entonces la llamada ahora se ve así
fuente
Use la estructura, pero en lugar de los campos públicos, tenga propiedades públicas:
Jon y FXCop estarán satisfechos porque está exponiendo propiedades y no campos.
Eric estará satisfecho porque al usar propiedades, puede asegurarse de que el valor solo se establezca una vez.
Un objeto semiinmutable (el valor puede establecerse pero no modificarse). Funciona por valor y tipos de referencia.
fuente
this
son mucho más malas que los campos públicos. No estoy seguro de por qué piensas que solo establecer el valor una vez hace que las cosas estén "bien"; Si uno comienza con una instancia predeterminada de una estructura, los problemas asociados conthis
las propiedades de mutación seguirán existiendo.Una variante de la respuesta de Samuel que utilicé en mi proyecto cuando tuve el mismo problema:
Y para usar:
En mi caso, los parámetros fueron modificables intencionalmente, porque los métodos de establecimiento no permitieron todas las combinaciones posibles, y simplemente expusieron combinaciones comunes de ellos. Esto se debe a que algunos de mis parámetros eran bastante complejos y los métodos de escritura para todos los casos posibles habrían sido difíciles e innecesarios (rara vez se usan combinaciones locas).
fuente
DoMagic
embargo, tiene en su contrato que no modificará el objeto. Pero supongo que no protege de modificaciones accidentales.