2009-02-07 21 views
7

Empecé redactando una pregunta: "¿Cuál es la mejor manera de realizar pruebas unitarias en un constructor (por ejemplo, __construct() en PHP5)", pero al leer las preguntas relacionadas, vi varios comentarios que parecían sugerir que establecer variables miembro o realizar cualquier operación complicada en el constructor son no-nos.¿Qué cosas no se hacen mejor en un constructor?

El constructor de la clase en cuestión toma un parámetro, realiza algunas operaciones en él (asegurándose de que pasa una prueba de olfateo y transformándolo si es necesario), y luego lo guarda en una variable miembro.

pensé que los beneficios de hacerlo de esta manera fueron:

1) que el código de cliente siempre sería seguro de tener un valor para esta variable miembro cada vez que se crea una instancia de un objeto de esta clase, y

2) se ahorra un paso en código de cliente (uno de los cuales podría ser concebiblemente perdida), por ejemplo,

$Thing = new Thing; 
$Thing->initialize($var); 

cuando podríamos hacer esto

$Thing = new Thing($var); 

y listo.

¿Es esto un no-no? Si es así, ¿por qué?

Respuesta

8

Esto viene a ser determinante en las discusiones de C++, y la conclusión general de que he llegado a no ha sido esto:

Si un objeto no podrá adquirir ninguna recursos externos, miembros deben pueden inicializar en el constructor. Esto implica hacer todo el trabajo en el constructor.

  • (x, y) de coordenadas (o en realidad cualquier otra estructura que es sólo una tupla glorificado)
  • de EE.UU. tabla de abreviaturas de búsqueda de estado

Si un objeto adquiere recursos que pueda controlar, que pueden ser asignados en el constructor:

  • descriptor de fichero abierto
  • asignado memoria
  • mango/puntero en una biblioteca externa

Si el objeto adquiere recursos que puede no del todo control, se debe se asigne fuera del constructor:

    conexión TCP
  • conexión
  • DB
  • nos ak reference

Siempre hay excepciones, pero esto cubre la mayoría de los casos.

-1

No debe poner las cosas en un constructor que solo se supone que se ejecuta una vez cuando se crea la clase.

Explicar.

Si tuviera una clase de base de datos. Cuando el constructor es la conexión con la base de datos Así

$db = new dbclass; 

Y ahora yo estoy conectado a la base de datos.

Luego tenemos una clase que usa algunos métodos dentro de la clase de la base de datos.

class users extends dbclass 
{ 
    // some methods 
} 

$users = new users 
// by doing this, we have called the dbclass's constructor again 
+0

En ese caso, prefiero pasar en una instancia de dbclass, o utilizarlo como un producto único, en lugar de extender la clase en si. – Ross

+0

Sí, hay formas de evitarlo, pero esto es solo un ejemplo. –

+0

Si no deseaba esa funcionalidad, puede anular el constructor en el objeto de usuario. Además, algunos idiomas, como C#, no heredan el constructor en absoluto. –

6

constructores son para inicializar el objeto, de modo

$Thing = new Thing($var); 

es perfectamente aceptable.

16

Mi regla de oro es que un objeto debe estar listo para usar después de que el constructor haya terminado. Pero a menudo hay una serie de opciones que pueden modificarse posteriormente.

Mi lista de hacer y no debe hacer:

  • constructores deben configurar las opciones básicas para el objeto.
  • Deberían tal vez crear instancias de objetos auxiliares.
  • Deberían no adquirir recursos (archivos, sockets, ...), a menos que el objeto claramente sea un envoltorio alrededor de algún recurso.

Por supuesto, sin reglas sin excepciones. Lo importante es que pienses en tu diseño y tus elecciones. Haga que el uso de objetos sea natural, y eso incluye informes de errores.

2

Para mejorar la capacidad de prueba de una clase, generalmente es bueno mantener su constructor lo más simple posible y pedirle solo las cosas que realmente necesita. Hay un excelente presentation disponible en YouTube como parte de la serie "Clean Code Talks" de Google explicando esto en detalle.

+1

+1 Acepto. Tenemos toneladas de código donde los constructores realmente abren archivos directamente para leer alguna información de configuración. De alguna manera, hace que estos objetos sean cómodos de usar, pero las pruebas de unidades de escritura serían imposibles. Lo bueno es que no tenemos pruebas de unidad ;-) –

0

Depende del tipo de sistema que intente diseñar, pero, en general, creo que los constructores se usan mejor solo para inicializar el "estado" del objeto, pero no realizan ninguna transición de estado. Lo mejor es que solo establezca los valores predeterminados.

Escribo luego un método de "manejo" en mis objetos para manejar cosas como la entrada del usuario, llamadas a la base de datos, excepciones, colación, lo que sea. La idea es que manejará cualquier estado en el que se encuentre el objeto en función de fuerzas externas (entrada del usuario, tiempo, etc.) Básicamente, todas las cosas que pueden cambiar el estado del objeto y requieren acción adicional se descubren y representan en el objeto.

Finalmente, puse un método de renderizado en la clase para mostrar al usuario algo significativo. Esto solo representa el estado del objeto para el usuario (cualquiera que sea).)

__construct (argumentos $)
mango()
render (Excepción $ ex = null)

0

El __construct método mágico está muy bien para su uso. La razón por la que ve inicializarse en muchos frameworks y aplicaciones es porque ese objeto se está programando en una interfaz o está intentando representar un patrón singleton/getInstance.

Estos objetos generalmente se colocan en el contexto o en un controlador y luego tienen la funcionalidad de interfaz común llamada por otros objetos de nivel superior.

1

Definitivamente debe evitar que el cliente tiene que llamar

$thing->initialize($var) 

Ese tipo de cosas absolutamente pertenece en el constructor. Es simplemente desagradable para el programador cliente hacer que lo llamen así. Existe una escuela de pensamiento (algo controvertida) que dice que debe escribir clases para que los objetos sean nunca en un estado no válido, y 'no inicializado' es un estado no válido.

Sin embargo, por razones de prueba y rendimiento, a veces es conveniente posponer ciertas inicializaciones hasta más adelante en la vida del objeto. En casos como estos, la evaluación perezosa es la solución.

Disculpas por poner la sintaxis de Java en una respuesta Python, pero:

// Constructor 
public MyObject(MyType initVar) { 
     this.initVar = initVar; 
} 

private void lazyInitialize() { 
    if(initialized) { 
     return 
    } 
    // initialization code goes here, uses initVar 
} 

public SomeType doSomething(SomeOtherType x) { 
    lazyInitialize(); 
    // doing something code goes here 
} 

Puede segmentar su inicialización lenta de manera que sólo las partes que lo necesiten inicializadas. Es común, por ejemplo, hacer esto en getters, solo por lo que afecta el valor que se obtiene.

4

El trabajo de un constructor es establecer una instancia de invariants.

Cualquier cosa que no contribuya a eso se mantiene mejor fuera del constructor.

0

Si $ var es absolutamente necesario para que $ cosa funcione, entonces es una DO

Cuestiones relacionadas