¿Podemos vivir sin constructores?

25

Digamos de alguna manera que todos los objetos se crean de esta manera $ obj = CLASS :: getInstance (). Luego inyectamos dependencias usando setters y realizamos la inicialización inicial usando $ obj-> initInstance (); ¿Hay problemas o situaciones reales que no puedan resolverse si no usamos constructores?

Ps la razón para crear objetos de esta manera, es que podemos reemplazar la clase dentro de getInstance () de acuerdo con algunas reglas.

Estoy trabajando en PHP, si eso importa

Axel Foley
fuente
¿Qué lenguaje de programación?
mosquito
2
PHP (¿por qué es importante?)
Axel Foley
8
Parece que lo que quieres hacer se puede lograr usando el patrón Factory.
SuperM
1
Solo como una nota: la patern de fábrica @superM mencionada es una forma de implementar la Inversión de Control (Inyección de Dependencia) es otra.
Joachim Sauer
¿No es eso aproximadamente lo que JavaScript hace con los objetos?
Thijser

Respuestas:

44

Yo diría que esto dificulta severamente su espacio de diseño.

Los constructores son un gran lugar para inicializar y validar los parámetros pasados. Si ya no puede usarlos para eso, entonces la inicialización, el manejo del estado (o simplemente negar el constructor de objetos "rotos") se vuelve mucho más difícil y parcialmente imposible.

Por ejemplo, si cada Fooobjeto necesita un, Frobnicatorentonces podría verificar en su constructor si el Frobnicatorno es nulo. Si quitas el constructor, entonces es más difícil de verificar. ¿Verifica en cada punto donde se utilizaría? ¿En un init()método (externalizando efectivamente el método del constructor)? ¿Nunca lo compruebas y esperas lo mejor?

Si bien es probable que aún pueda implementar todo (después de todo, todavía está completo), algunas cosas serán mucho más difíciles de hacer.

Personalmente, sugiero buscar en la inyección de dependencia / inversión de control . Estas técnicas también permiten cambiar las clases de implementación concretas, pero no impiden escribir / usar constructores.

Joachim Sauer
fuente
¿Qué quiso decir con "o simplemente negar constructor de objetos" rotos "?
Geek
2
@ Geek: un constructor puede inspeccionar su argumento y decidir si esos resultarían en un objeto de trabajo (por ejemplo, si su objeto necesita un, HttpCliententonces verifica si ese parámetro no es nulo). Y si no se cumplen esas restricciones, puede generar una excepción. Eso no es realmente posible con el enfoque de construir y establecer valores.
Joachim Sauer
1
Creo que OP solo describía la externalización del constructor en el interior init(), lo cual es completamente posible, aunque esto simplemente impone una carga de mantenimiento más.
Peter
26

2 ventajas para los constructores:

Los constructores permiten que los pasos de construcción de un objeto se realicen atómicamente.

Podría evitar un constructor y usar setters para todo, pero ¿qué pasa con las propiedades obligatorias como sugirió Joachim Sauer? Con un constructor, un objeto posee su propia lógica de construcción para garantizar que no haya instancias no válidas de dicha clase .

Si crear una instancia de Foorequiere que se establezcan 3 propiedades, el constructor podría tomar una referencia a las 3, validarlas y lanzar una excepción si no son válidas.

Encapsulamiento

Al confiar únicamente en los colocadores, la carga recae en el consumidor de un objeto para construirlo adecuadamente. Podría haber diferentes combinaciones de propiedades que sean válidas.

Por ejemplo, cada Fooinstancia necesita una instancia de Barcomo propiedad baro una instancia de BarFindercomo propiedad barFinder. Puede usar cualquiera de los dos. Puede crear un constructor para cada conjunto válido de parámetros y aplicar las convenciones de esa manera.

La lógica y la semántica de los objetos viven dentro del objeto mismo. Es buena encapsulación.

Brandon
fuente
15

Sí, puedes vivir sin constructores.

Claro, puede terminar con un montón de código de placa de caldera duplicado. Y si su aplicación es de cualquier escala, es probable que pase mucho tiempo tratando de localizar el origen de un problema cuando ese código repetitivo no se use de manera consistente en toda la aplicación.

Pero no, no "necesita" estrictamente sus propios constructores. Por supuesto, tampoco estrictamente 'necesita' clases y objetos.

Ahora, si su objetivo es usar algún tipo de patrón de fábrica para la creación de objetos, eso no es mutuamente exclusivo de usar constructores al inicializar sus objetos.

Gran maestro B
fuente
Absolutamente. Incluso se puede vivir sin clases y objetos, para empezar.
JensG
55
¡Todos saluden al poderoso ensamblador!
Davor Ždralo
9

La ventaja de usar constructores es que hacen que sea más fácil asegurarse de que nunca tendrá un objeto no válido.

Un constructor le brinda la oportunidad de establecer todas las variables miembro del objeto en un estado válido. Cuando se asegura de que ningún método mutador pueda cambiar el objeto a un estado no válido, nunca tendrá un objeto no válido, lo que lo salvará de muchos errores.

Pero cuando se crea un nuevo objeto en un estado no válido y debe llamar a algunos establecedores para ponerlo en un estado válido en el que pueda usarse, se arriesga a que un consumidor de la clase olvide llamar a estos establecedores o los llame incorrectamente y terminas con un objeto no válido.

Una solución alternativa podría ser crear objetos solo a través de un método de fábrica que verifica la validez de cada objeto que crea antes de devolverlo a la persona que llama.

Philipp
fuente
3

$ obj = CLASE :: getInstance (). Luego inyectamos dependencias usando setters y realizamos la inicialización inicial usando $ obj-> initInstance ();

Creo que estás haciendo esto más difícil de lo que debe ser. Podemos inyectar dependencias perfectamente a través del constructor, y si tiene muchas de ellas, simplemente use una estructura similar a un diccionario para que pueda especificar cuáles quiere usar:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

Y dentro del constructor, puede garantizar la coherencia de esta manera:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args luego se puede usar para establecer miembros privados según sea necesario.

Cuando se realiza por completo dentro del constructor de esta manera, nunca habrá un estado intermedio donde $objexista pero no se inicialice, como lo habría en el sistema descrito en la pregunta. Es mejor evitar tales estados intermedios, porque no puede garantizar que el objeto siempre se use correctamente.

Izkata
fuente
2

En realidad estaba pensando en cosas similares.

La pregunta que hice fue "¿Qué hace el constructor, y es posible hacerlo de manera diferente?" Llegué a esas conclusiones:

  • Asegura que algunas propiedades se inicialicen. Al aceptarlos como parámetros y configurarlos. Pero esto podría ser implementado fácilmente por el compilador. Simplemente anotando los campos o propiedades como "obligatorios", el compilador comprobará durante la creación de la instancia si todo está configurado correctamente. La llamada para crear la instancia probablemente sería la misma, simplemente no habría ningún método constructor.

  • Asegura que las propiedades sean válidas. Esto podría lograrse fácilmente mediante una condición de afirmación. Nuevamente, simplemente anotaría las propiedades con las condiciones correctas. Algunos idiomas ya hacen esto.

  • Alguna lógica de construcción más compleja. Los patrones modernos no recomiendan hacer eso en el constructor, pero proponen usar métodos o clases especializados de fábrica. Entonces, el uso de constructores en este caso es mínimo.

Entonces para responder a su pregunta: Sí, creo que es posible. Pero requeriría algunos grandes cambios en el diseño del lenguaje.

Y acabo de notar que mi respuesta es bastante OT.

Eufórico
fuente
2

Sí, puede hacer casi todo sin usar constructores, pero esto es claramente una pérdida de beneficios de los lenguajes de programación orientados a objetos.

En las lenguas modernas (Voy a hablar aquí sobre C # qué programa I en) puede limitar partes de código que se pueden ejecutar solamente en un constructor. Gracias a lo cual puedes evitar errores torpes. Una de esas cosas es el modificador de solo lectura :

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Según lo recomendado por Joachim Sauer anteriormente, en lugar de usar el Factorypatrón de diseño, lea sobre Dependency Injection. Recomendaría leer Inyección de dependencias en .NET por Mark Seemann .

SOReader
fuente
1

Instanciar un objeto con un tipo dependiendo de los requisitos es absolutamente posible. Podría ser el objeto en sí mismo, utilizando variables globales del sistema para devolver un tipo específico.

Sin embargo, tener una clase en el código que puede ser "Todo" es el concepto de tipo dinámico . Personalmente, creo que este enfoque crea inconsistencia en su código, hace que los complejos de prueba * y "el futuro se vuelva incierto" con respecto al flujo del trabajo propuesto.

* Me refiero al hecho de que las pruebas deben considerar primero el tipo, luego el resultado que desea lograr. Entonces estás creando una gran prueba anidada.

marcocs
fuente
1

Para equilibrar algunas de las otras respuestas, afirmando:

Un constructor le brinda la oportunidad de establecer todas las variables miembro del objeto en un estado válido ... nunca tendrá un objeto no válido, lo que lo salvará de muchos errores.

y

Con un constructor, un objeto posee su propia lógica de construcción para garantizar que no haya instancias no válidas de dicha clase.

Tales declaraciones a veces implican la suposición de que:

Si una clase tiene un constructor que, para cuando sale, ha puesto el objeto en un estado válido, y ninguno de los métodos de la clase muta ese estado para hacerlo inválido, entonces es imposible que el código fuera de la clase detecte un objeto de esa clase en un estado no válido.

Pero esto no es del todo cierto. La mayoría de los idiomas no tienen una regla en contra de que un constructor pase this(o selfcomo lo llame el idioma) a código externo. Tal constructor cumple completamente con la regla establecida anteriormente, y sin embargo, corre el riesgo de exponer los objetos semi-construidos a código externo. Es un punto menor pero fácil de pasar por alto.

Daniel Earwicker
fuente
0

Esto es algo anecdótico, pero generalmente reservo constructores para el estado esencial para que el objeto sea completo y utilizable. Salvo cualquier inyección de setter, una vez que se ejecuta el constructor, mi objeto debería ser capaz de realizar las tareas requeridas.

Cualquier cosa que pueda diferirse, la dejo fuera del constructor (preparación de valores de salida, etc.). Con este enfoque, siento que no uso constructores para nada, pero la inyección de dependencia tiene sentido.

Esto también tiene el beneficio adicional de cablear su proceso de diseño mental para no hacer nada prematuramente. No inicializará ni ejecutará una lógica que quizás nunca termine siendo utilizada porque, en el mejor de los casos, todo lo que está haciendo es la configuración básica para el trabajo por delante.

Omega
fuente