2010-05-19 4 views
15

tengo clases como ésta:¿Hay algo de malo en tomar medidas inmediatas en los constructores?

class SomeObject 
{ 
    public function __construct($param1, $param2) 
    { 
     $this->process($param1, $param2); 
    } 
    ... 
} 

Así que inmediatamente puede "llamar" como una especie de función global al igual que

new SomeObject($arg1, $arg2); 

que tiene los beneficios de

  • siendo conciso,
  • siendo fácil de entender,

pero puede romper reglas no escritas de semántica al no esperar hasta que se llame a un método.

¿Debo continuar sintiéndome mal debido a una mala práctica, o realmente no hay nada de qué preocuparse?

Aclaración:

  • deseo que haya una instancia de la clase.
  • Uso solo los métodos internos de la clase.
  • Inicializo el objeto en el constructor, pero también llamo a los métodos "importantes" que toman las acciones.
  • Soy egoísta a la luz de estas oraciones.

Ejemplo:

para darle una idea de cómo normalmente utilizo este enfoque:

new Email('[email protected]', 'Subject line', 'Body Text'); 

evito abusar de ella, por supuesto, pero en mi opinión, esto es realmente práctico.

+1

Lee más como si deseara una función estática en ese objeto. – AlG

+0

@ qor72 Definitivamente quiero una instancia de esa clase, pero soy perezoso para escribir variables y llamar al mismo método una y otra vez. – pestaa

+0

No entiendo de ninguna manera por qué esto es más fácil que llamar 'SomeObject-> process ($ param1, $ param2)' directamente. – fearofawhackplanet

Respuesta

8

si el código en el constructor es parte de crear e inicializar el objeto para su uso, a continuación, Lo pondría allí, pero ese soy yo personalmente, algunas personas pueden estar en desacuerdo

Sin embargo, parece que lo que estás haciendo no está destinado a construir el objeto/clase, sino que hace algún otro proceso. esto es malo, y debe hacerse en un método diferente.

Guarde el constructor para la construcción.

+0

Agregó un ejemplo arriba. La cuestión es, digamos, que 'dispatch()' en el ejemplo no pertenece realmente a la categoría "otros procesos", pero tampoco a la creación de objetos. ¿Cuál es tu opinión? – pestaa

+0

@ pestaa- todo lo que dijiste está bien, EXCEPTO para enviar el correo electrónico. Eso no es parte de crear e inicializar, y definitivamente pertenece a un método diferente. ¿Qué sucede si desea pasar el objeto de correo electrónico a alguna parte o modificar algo antes de enviarlo? no puedes, a menos que tengas más constructores. saca el envío, y estás bien. –

5

Esta es una mala práctica.

El uso de un constructor como función global no es en absoluto para lo que está destinado, y se confundiría fácilmente. No es fácil de entender en absoluto.

Si desea una función global, declarar una:

function myGlobalFunction(){ return $something; } 

Realmente no es tan difícil ...

e incluso si no lo está utilizando como una función, se debe usa el constructor para hacer exactamente eso, construir un objeto. Si hace más que eso, no lo está usando para el propósito correcto, y los futuros contribuyentes probablemente se confundirán rápidamente.

Por lo tanto, programe de una manera que tenga sentido. Realmente no es tan difícil. Unos pocos golpes de teclado adicionales pueden ahorrarte mucha confusión.

Si tiene que tomar siempre ciertas acciones después de hacer una nueva instancia de una clase, trata de una fábrica:

class myFactory{ 
     public static function makeObject(){ 
      $obj = new Object(); 
      $obj->init1(); 
      return $obj; 
     } 
} 

$obj = myFactory::makeObject(); 
+0

No tenía la intención de usarlo como una función global, era una mera metáfora. – pestaa

+0

A veces hago esto para llamar a una función "clear()" para tener todo el código de restablecimiento en un solo lugar, de lo contrario podría tener un comportamiento incoherente. – catchmeifyoutry

+0

-1: Estoy de acuerdo con @pestaa. Tu respuesta realmente no corresponde a la pregunta. –

4

La navaja de Occam dice entia non sunt multiplicanda praeter necessitatem, literalmente: "las entidades no deben multiplicarse más allá de lo necesario". No hay nada de malo en hacer trabajo en un constructor [*], pero no requiere que las personas que llaman crean un objeto que no usan, solo para obtener los efectos secundarios de su constructor.

[*] Suponiendo que se sienta cómodo con el mecanismo que utiliza su lenguaje de programación para indicar la falla de un constructor. Devolver un código de éxito/falla de un constructor podría no ser una opción, por lo que si desea evitar una excepción en el caso de falla, entonces podría reducirse a establecer indicadores "utilizables" en el objeto, lo cual no es muy bueno. para la usabilidad tampoco.

+1

+1 por usar latin: D también por el aspecto lógico, no técnico de este debate –

+0

+1 Absolutamente. Entonces, en sus términos, esta pregunta es acerca de si llamar a la esencia de una clase en el constructor es un efecto secundario o no. (La gestión de errores no es un problema en mi situación, pero usted hizo puntos válidos que otros pueden considerar.) – pestaa

+0

Diría que la "esencia de una clase" son los objetos de esa clase, y su interfaz y comportamiento definidos por la clase. Si la esencia de su clase es que blanquea la pantalla (o lo que sea), ya ningún usuario le importará qué objetos utiliza para hacerlo, entonces no es "realmente" una clase, es una rutina. Algunos lenguajes (Java) le obligan a poner cada función en una clase, lo que lleva a la existencia de * clases * que no son * tipos *. Creo que ese requisito no tiene sentido, aunque supongo que guarda el idioma/bytecode que necesita tener una sintaxis/formato para funciones gratuitas. –

0

En mi opinión, depende de cuál sea el propósito de la función.

Por ejemplo, algo que creo que sería totalmente apropiado llamar al final de un constructor podría ser algo así como una función refreshCache(). Es una función pública real, como otros objetos pueden desear llamarla, pero al mismo tiempo usted sabe que es la misma lógica que desea aplicar inicialmente al configurar su objeto.

En un sentido general, supongo que el tipo de funciones (públicas) que desea llamar durante un constructor suelen ser funciones idempotentes que manipulan el estado local, a menudo cosas como la población de caché/captación previa de cosas/etc.

Si usted tiene un objeto que hace algún tipo de procesamiento en una entrada, que sin duda evitar

s = new MyObject(a, b) 
return s.getResult() 

a favor de

s = new MyObject() // perhaps this is done elsewhere, even once at startup 
return s.process(a, b) 

como posiblemente el último es más clara en cuanto a lo que es en realidad sucede (especialmente si le das a process() un nombre real :-)), y te permite reutilizar un solo objeto más liviano para los cálculos, lo que a su vez hace que sea más fácil pasarlo.

Por supuesto, si tiene un constructor bastante complejo (esto es probablemente algo malo en general, pero indudablemente hay algunos casos donde tiene sentido), entonces nunca es malo separar un bloque de funcionalidad relacionada en una función privada para la legibilidad.

+0

Andrzej, no guardo el objeto en una variable, ni siquiera un segundo. Por lo tanto, no puedo llamar a un segundo método (más allá del constructor). Al menos no en PHP, ya que estamos sin encadenamiento de métodos. Su respuesta es más un debate sobre la convención de nombres. ¡Y estoy de acuerdo! – pestaa

0

Esto es conciso, pero no es remotamente idiomático, por lo que no es fácil de entender para otros.

En el futuro, tal vez ni siquiera sea fácil para usted comprenderlo.

Puede que no sea importante para usted, pero un problema con esto es que hace que sus clases sean más difíciles de probar: consulte Flaw: Constructor does Real Work.

+0

No estoy de acuerdo con "Constructor does Real Work" siendo _hasta_ un defecto. A veces, un constructor necesita hacer un trabajo real para configurar el entorno para el nuevo objeto. Si se saca dicha inicialización del controlador, la construcción del objeto será parcial o incompleta, lo que requerirá que el llamador recuerde inicializar el objeto correctamente. Por ejemplo, tengo un constructor 'UIPanel', que de hecho _does_ se llena con un' respaldo = nuevo HotSpot (dimensiones) '. (para detectar clics del mouse y mostrar un color base). – bobobobo

1

Creo que esta es una mala práctica por dos razones.

  • No está claro para quien llama que se están llevando a cabo otras operaciones más allá de lo mínimo necesario para la inicialización.
  • Lleva a escenarios de codificación incómodos si esta operación arroja una excepción.

En cuanto al primer punto ... hay una suposición implícita de que los constructores solo realizan el trabajo suficiente para construir un objeto en un estado definido y consistente. Poner trabajo extra en ellos puede llevar a confusión si el trabajo es de larga duración o IO obligado. Recuerde, el constructor no puede transmitir ningún significado a través de su nombre como métodos. Una alternativa es crear un método de fábrica estático con un nombre significativo que devuelva una nueva instancia.

En cuanto al segundo punto ... si el constructor contiene una operación que arroja excepciones impredeciblemente (en contraste con excepciones lanzadas debido a la validación de parámetros, por ejemplo), su código de manejo de excepciones se torna incómodo. Considere el siguiente ejemplo en C#. Observe cómo el buen diseño tiene una sensación más elegante. No importa, el hecho de que el método claramente nombrado es al menos un orden de magnitud más legible.

public class BadDesign : IDisposable 
{ 
    public BadDesign() 
    { 
    PerformIOBoundOperation(); 
    } 

    private void PerformIOBoundOperation() { } 
} 

public class GoodDesign : IDisposable 
{ 
    public GoodDesign() 
    { 

    } 

    public void PerformIOBoundOperation() { } 
} 

public static void Main() 
{ 
    BadDesign bad = null; 
    try 
    { 
    bad = new BadDesign(); 
    } 
    catch 
    { 
    // 'bad' is left as null reference. There is nothing more we can do. 
    } 
    finally 
    { 
    if (bad != null) 
    { 
     bad.Dispose(); 
    } 
    } 

    GoodDesign good = new GoodDesign(); 
    try 
    { 
    good.PerformIOBoundOperation(); 
    } 
    catch 
    { 
    // Do something to 'good' to recover from the error. 
    } 
    finally 
    { 
    good.Dispose(); 
    } 
} 
1

Cosas que estar preocupado con respecto a este patrón:

  • Rompiendo Principio de responsabilidad única
  • La implementación funcional de descomposición antipatrón
  • no cumplir las expectativas de los consumidores comunes

individual Principio de responsabilidad

Esto es una ligera variación del principio original, pero se aplica en cambio a las funciones. Si su constructor está haciendo más de una cosa (construcción y procesamiento), hace que el mantenimiento de este método sea más difícil en el futuro. Si una persona que llama no quiere procesar durante la construcción, no le dejas ninguna opción. Si el "procesamiento" de un día requiere pasos adicionales que no deberían estar en el constructor, debe refactorizar en cualquier lugar donde use este método.

Funcional de descomposición antipatrón

Sin conocer detalles, estaría preocupado de que el código que hace esto implementa este antipatrón. Los objetos no son realmente objetos, sino unidades de programación funcionales envueltas en un disfraz orientado a objetos.

expectativas de los consumidores comunes

¿Le llama la media esperar que este comportamiento del constructor? Puede haber lugares donde el procesamiento adicional durante la construcción puede ser una conveniencia de programación manual corta. Pero esto debería ser explícitamente documentado y bien entendido por los llamadores.El uso general del objeto aún debería tener sentido en este contexto y debería ser natural para el que llama usarlo de esta manera.

También comprenda lo que está forzando a su consumidor. Si está procesando en el constructor, está obligando al consumidor a pagar ese procesamiento (en términos de tiempo de procesamiento), lo quiera o no. Elimina la posibilidad de hacer un procesamiento "diferido" u otras formas de optimización.

¿Uso aceptable?

Solo en lugares donde el uso común de la clase requiere una inicialización que puede ser opcional en el momento de la construcción. La clase File es un buen ejemplo:

/* This is a common usage of this class */ 
File f; 
f.Open(path); 

/* This constructor constructs the object and puts it in the open state in one step 
* for convenience */ 
File f(path); 

Pero incluso esto es cuestionable. ¿La clase de archivo mantiene la ruta al archivo internamente o solo se usa para abrir un manejador de archivo? Si almacena la ruta del archivo, ahora Open() y el nuevo constructor tienen más de una responsabilidad (SetPath (p) y Open()). En ese caso, tal vez el constructor de conveniencia de File (path) no debería abrir el archivo, sino simplemente establecer la ruta en el objeto.

Hay muchas consideraciones basadas en los objetos en cuestión. Considere escribir pruebas unitarias para sus objetos. Le ayudarán a resolver muchos de estos casos de uso.

0

Su clase es técnicamente buena, y podría ser simplemente una filosofía personal.

sino a otros leyendo esto que podría obtener algunas ideas locas sobre constructores, lee esto:

http://www.ibm.com/developerworks/java/library/j-jtp0618.html

Si vas a empezar a tirar todo el "presente" de referencia, que puede terminar en muchos problemas, especialmente en entornos con múltiples subprocesos. Solo un FYI.

Cuestiones relacionadas