¿Cómo cambia el concepto de una clase al pasar datos al constructor en lugar de los parámetros del método?

12

Digamos que estamos haciendo un analizador. Una implementación podría ser:

public sealed class Parser1
{
    public string Parse(string text)
    {
       ...
    }
}

O podríamos pasar el texto al constructor en su lugar:

public sealed class Parser2
{
    public Parser2(string text)
    {
       this.text = text;
    }

    public string Parse()
    {
       ...
    }
}

El uso es simple en ambos casos, pero ¿qué significa habilitar la entrada de parámetros en Parser1comparación con el otro? ¿Qué mensaje le envié a un compañero programador cuando mira la API? Además, ¿hay ventajas / desventajas técnicas en ciertos casos?

Otra pregunta surge cuando me doy cuenta de que una interfaz no tendría sentido en la segunda implementación:

public interface IParser
{
    string Parse();
}

... donde una interfaz en la primera podría servir al menos para algún propósito. ¿Eso significa algo en particular, que una clase es "interconectable" o no?

calentar
fuente
Esta es la primera vez que veo algunas preguntas y respuestas útiles sobre cómo la semántica de OO realmente expresa intención. Hasta ahora todo ha sido azúcar sintáctica, para mí.

Respuestas:

11

Hablando semánticamente, en OOP solo debe pasarle al constructor un conjunto de parámetros necesarios para construir la clase; de ​​manera similar cuando llama a un método, solo debe pasarle los parámetros que necesita para ejecutar su lógica de negocios.

Los parámetros que necesita pasar en el constructor son parámetros que no tienen un valor predeterminado sensible y si su clase es inmutable (o de hecho a struct), entonces se deben pasar las propiedades que no son predeterminadas.

Con respecto a sus dos ejemplos:

  • si pasa textal constructor, sugiere que la Parser2clase se construirá específicamente para analizar esa instancia de texto en un momento posterior. Será un analizador específico. Este suele ser el caso cuando la construcción de la clase es muy costosa o sutil, un RegEx puede compilarse en el constructor, por lo que una vez que tenga una instancia, puede reutilizarla sin tener que pagar el costo de la compilación; Otro ejemplo es la inicialización de la PRNG: es mejor si se hace raramente.
  • Si pasa textal método, señala que Parser1se puede reutilizar para analizar diferentes textos mediante llamadas.
Sklivvz
fuente
3
Agregaría que hay una señal débil de que Parser1 mantiene el estado, es decir, que una cadena de texto en particular puede producir resultados diferentes dependiendo de que se haya hecho previamente en una instancia de tat. Esto no es necesariamente así, pero puede serlo.
jmoreno
8

Bueno, recordemos lo que significa pasar una variable como parámetro constructor: Inicializa un objeto para usar sus variables de instancia en los métodos del objeto. El punto es que probablemente quieras usarlo en más de un método ya que quieres tener una alta cohesión en tu clase.

Pasar un parámetro directamente a un método significa enviar un mensaje a un objeto y, probablemente, recibir una respuesta. Por eso, el cliente quiere que el objeto le brinde un servicio.

En conclusión, esos son dos medios muy diferentes de pasar parámetros y usted debe elegir si su objeto debe entregar un servicio o proporcionar alguna funcionalidad inherentemente mientras administra algo de información internamente.

McMannus
fuente
+1. La cohesión es cómo decido si pertenece al método o al constructor.
jhewlett
4

El uso es simple en ambos casos, pero ¿qué significa habilitar la entrada de parámetros en Parser1, en comparación con el otro?

Es un cambio de diseño fundamental. Y el diseño debe transmitir intención y significado. ¿Necesita tener objetos separados para cada cadena que desea analizar? En otras palabras, ¿por qué necesitamos una instancia de analizador con stringX y otra instancia con stringY? ¿Qué hay en parse (ing) y la cadena dada que los dos deben vivir y morir juntos? Suponiendo que la "implementación subyacente [análisis]" (como dice Robert Harvey) no cambia, parece que no tiene sentido. E incluso entonces es cuestionable en mi humilde opinión.

¿Cómo cambia el concepto de una clase al pasar datos al constructor en lugar de los parámetros del método?

Los parámetros del constructor me dicen que estas cosas son necesarias para un objeto. El estado adecuado no está garantizado sin ellos. Además, sé cómo / por qué un analizador es fundamentalmente diferente de otro.

Los parámetros del constructor evitan que tenga que saber demasiado sobre cómo usar la clase. Si en cambio se supone que debo establecer ciertas propiedades, ¿cómo lo sé? Se abre una lata entera de gusanos. Que propiedades ¿En qué orden? Antes de usar qué métodos? y así.

Otra pregunta surge cuando me doy cuenta de que una interfaz no tendría sentido en la segunda implementación:

Una interfaz, como en API, son los métodos y propiedades expuestos al código del cliente. No te dejes envolver public interface { ... }exclusivamente. Entonces, el significado de la interfaz está en el dilema del parámetro o método del constructor o del método, NO public interface Iparservspublic sealed class Parser

La sealedclase es rara. Si estoy pensando en diferentes implementaciones de analizador, usted mencionó "Iparser", entonces la herencia es mi primer pensamiento. Es solo una extensión conceptual natural en mi pensamiento. IE todos los ParserXs son fundamentalmente Parsers. ¿De qué otra manera decirlo? ... Un Shepard alemán es un perro (herencia), pero puedo entrenar a mi loro a ladrar (actuar como un perro - "interfaz"); Pero Polly no es un perro, simplemente finge, después de haber aprendido un subconjunto de la ternura. Las clases, abstractas o de otro tipo, sirven perfectamente como interfaces .

radarbob
fuente
Si camina como un analizador y habla como un analizador, es ... ¡un pato!
2

La segunda versión de la clase se puede hacer inmutable.

La interfaz aún se puede utilizar para proporcionar la capacidad de intercambiar la implementación subyacente.

Robert Harvey
fuente
¿No puede hacerse inmutable también en la primera versión, pasando datos dentro de la clase de manera funcional?
ciscoheat
2
Absolutamente. Pero el patrón general de inmutabilidad es establecer los miembros de la clase utilizando el constructor y tener propiedades de solo lectura. Para la programación funcional, ni siquiera necesitas una clase.
Robert Harvey
Scylla and Charybdis: ¿elijo OO o datos inmutables? Nunca antes lo había escuchado así.
2

Analizador1

Construir con un constructor predeterminado y pasar texto de entrada a un método implica que Parser1 es reutilizable.

Analizador2

Pasar el texto de entrada al constructor implica que se debe crear un nuevo Parser2 para cada cadena de entrada.

Mike Partridge
fuente
Claro, entonces, si lo llevamos al siguiente nivel, ¿qué concluir acerca de un objeto reutilizable en comparación con un no reutilizable?
ciscoheat
No supondría nada más que eso, en lugar de diferir a la documentación.
Mike Partridge
Un "objeto reutilizable" en el que cambia su identidad es un no-sequitur. Y con marcos administrados que significan que ni siquiera tiene que tirar las cosas, simplemente sobrescribirlas o dejarlas fuera de alcance, ciertamente es innecesario. Construir lejos!