Cómo escribir constructores que podrían no crear una instancia adecuada de un objeto

11

A veces necesitas escribir un constructor que puede fallar. Por ejemplo, digamos que quiero crear una instancia de un objeto con una ruta de archivo, algo así como

obj = new Object("/home/user/foo_file")

Mientras la ruta apunte a un archivo apropiado, todo estará bien. Pero si la cadena no es una ruta válida, las cosas deberían romperse. ¿Pero cómo?

Tú podrías:

  1. lanzar una excepción
  2. devolver objeto nulo (si su lenguaje de programación permite a los constructores devolver valores)
  3. devolver un objeto válido pero con un indicador que indica que su ruta no se configuró correctamente (ugh)
  4. ¿otros?

Supongo que las "mejores prácticas" de varios lenguajes de programación implementarían esto de manera diferente. Por ejemplo, creo que ObjC prefiere (2). Pero (2) sería imposible de implementar en C ++ donde los constructores deben tener vacío como tipo de retorno. En ese caso, supongo que se usa (1).

En el lenguaje de programación que elija, ¿puede mostrar cómo manejaría este problema y explicar por qué?

garageàtrois
fuente
1
Las excepciones en Java son una forma de manejar esto.
Mahmoud Hossam
Los constructores de C ++ no regresan void, devuelven un objeto.
gablin
66
@gablin: En realidad, eso tampoco es estrictamente cierto. newllama operator newpara asignar la memoria, luego el constructor para llenarla. El constructor no devuelve nada y newdevuelve el puntero del que obtuvo operator new. Sin embargo, si "no devuelve nada" implica "retornos void" está en juego.
Jon Purdy
@ Jon Purdy: Hm, suena razonable. Buena llamada.
gablin

Respuestas:

8

Nunca es bueno confiar en un constructor para hacer el trabajo sucio. Además, tampoco está claro para otro programador si el trabajo se realizará en el constructor a menos que exista documentación explícita que lo indique (y que el usuario de la clase lo haya leído o se lo hayan dicho).

Por ejemplo (en C #):

public Sprite
{
    public Sprite(string filename)
    {
    }
}

¿Qué sucede si el usuario no quiere cargar el archivo de inmediato? ¿Qué sucede si quieren hacer un almacenamiento en caché a pedido del archivo? No pueden Puede pensar en poner un bool loadFileargumento en el constructor, pero esto hace que esto sea complicado, ya que todavía necesita un Load()método para cargar el archivo.

Dado el escenario actual, será más flexible y más claro para los usuarios de la clase hacer esto:

Sprite sprite = new Sprite();
sprite.Load("mario.png");

O alternativamente (para algo como un recurso):

Sprite sprite = new Sprite();
sprite.Source = "mario.png";
sprite.Cache();

// On demand caching of file contents.
public void Cache()
{
     if (image == null)
     {
         try
         {
             image = new Image.FromFile(Source);
         }
         catch(...)
         {
         }
     }
}
Nick Bedford
fuente
en su caso, si falla la carga (..), tenemos un objeto totalmente inútil. No estoy seguro de que quiero sprites sin ningún tipo de sprites ... Pero las miradas de interrogación más como sujeto de holywar verdadera pregunta :)
avtomaton
7

En Java, podría usar excepciones o usar el patrón de fábrica, que le permitiría devolver nulo.

En Scala, puede devolver una Opción [Foo] desde un método de fábrica. Eso también funcionaría en Java, pero sería más engorroso.

Kim
fuente
Uso de excepciones o de fábrica en lenguajes .NET también.
Beth Whitezel
3

Lanza una excepción.

Null necesita ser verificado si puede devolverlo (y no será verificado)

Para eso están las excepciones marcadas. Sabes que podría fallar. Las personas que llaman tienen que manejar esto.

Tim Williscroft
fuente
3

En C ++ , los constructores se utilizan para crear / inicializar miembros de la clase.

No hay una respuesta correcta a esta pregunta. Pero lo que he observado hasta ahora es que la mayoría de las veces es el cliente (o quien vaya a usar su API) quien elige cómo debe manejar estas cosas.

A veces pueden pedirle que asigne todos los recursos que el objeto pueda necesitar en el constructor y arroje una excepción si algo falla (abortando la creación del objeto), o no haga nada de eso en el constructor, y asegúrese de que la creación siempre tenga éxito , dejando estas tareas para que las haga alguna función miembro.

Si depende de usted elegir el comportamiento, las excepciones son la forma predeterminada de C ++ para manejar los errores y debe usarlos cuando pueda.

karlphillip
fuente
1

También puede crear una instancia del objeto sin parámetros, o solo con parámetros que seguramente nunca fallarán, y luego usar una función o método de inicialización desde el cual puede lanzar una excepción de forma segura o hacer lo que desee.

gablin
fuente
66
Creo que devolver un objeto inutilizable que requiere una mayor inicialización es la peor opción. Ahora tiene un fragmento de varias líneas que debe copiarse cada vez que se usa la clase, o se incorpora a un nuevo método. También puede hacer que el constructor haga todo el trabajo y arroje una excepción si el objeto no se puede construir.
Kevin Cline
2
@kevin: depende del objeto. Las fallas probablemente se deban a recursos externos, por lo que una entidad podría querer registrar la intención (por ejemplo, nombre de archivo) pero permitir repetidos intentos de inicialización completa. Para un objeto de valor, probablemente me inclinaría a informar un error que pueda capturar el error subyacente, por lo que probablemente arroje una excepción (¿envuelta?).
Dave
-4

Prefiero no inicializar ninguna clase o lista en el constructor. Inicialice una clase o lista cuando lo necesite. P.ej.

public class Test
{
    List<Employee> test = null;
}

No hagas esto

public class Test
{
    List<Employee> test = null;

    public Test()
    {
        test = new List<Employee>();
    }
}

En su lugar, inicializar cuando sea necesario. Ej.

public class Test
{
    List<Employee> emp = null;

    public Test()
    { 
    }

    public void SomeFunction()
    {
         emp = new List<Employee>();
    }
}
prueba
fuente
1
Este es un mal consejo. No debe permitir que los objetos estén en un estado no válido. Para eso está el constructor. Si necesita una lógica compleja, utilice el patrón de fábrica.
AlexFoxGill
1
Los constructores están allí para establecer los invariantes de clase, el código de muestra no ayuda a afirmar eso en absoluto.
Niall