Como escrever construtores que possam falhar ao instanciar corretamente um objeto

11

Às vezes você precisa escrever um construtor que possa falhar. Por exemplo, digamos que eu queira instanciar um objeto com um caminho de arquivo, algo como

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

Desde que o caminho aponte para um arquivo apropriado, tudo ficará bem. Mas se a string não for um caminho válido, as coisas devem quebrar. Mas como?

Você poderia:

  1. lançar uma exceção
  2. retornar objeto nulo (se sua linguagem de programação permitir que os construtores retornem valores)
  3. retorna um objeto válido, mas com uma bandeira indicando que seu caminho não foi definido corretamente (ugh)
  4. outras?

Suponho que as "melhores práticas" de várias linguagens de programação implementariam isso de maneira diferente. Por exemplo, acho que o ObjC prefere (2). Mas (2) seria impossível implementar em C ++ onde os construtores devem ter nulo como um tipo de retorno. Nesse caso, considero que (1) é usado.

Na sua linguagem de programação de escolha, você pode mostrar como lidaria com esse problema e explicar por quê?

garageàtrois
fonte
1
Exceções em Java são uma maneira de lidar com isso.
Mahmoud Hossam
Construtores C ++ não retornam void- eles retornam um objeto.
gablin
6
@ gablin: Na verdade, isso também não é verdade. newchamadas operator newpara alocar a memória e, em seguida, o construtor para preenchê-la. O construtor não retorna nada e newretorna o ponteiro que obteve operator new. Se "não retorna nada" implica "retornos void", está em jogo.
Jon Purdy
@ Jon Purdy: Hum, parece razoável. Boa decisão.
precisa saber é o seguinte

Respostas:

8

Nunca é bom contar com um construtor para fazer o trabalho sujo. Além disso, também não está claro para outro programador se o trabalho será ou não executado no construtor, a menos que haja documentação explícita afirmando isso (e que o usuário da classe tenha lido ou dito isso).

Por exemplo (em C #):

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

O que acontece se o usuário não quiser carregar o arquivo imediatamente? E se eles quiserem fazer cache sob demanda do arquivo? Eles não podem. Você pode pensar em colocar um bool loadFileargumento no construtor, mas isso fica confuso, pois agora você ainda precisa de um Load()método para carregar o arquivo.

Dado o cenário atual, será mais flexível e claro para os usuários da classe fazer isso:

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

Ou, alternativamente (para algo como um 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
fonte
no seu caso, se a carga (..) falhar, temos um objeto totalmente inútil. Eu não tenho certeza que eu quero Sprite sem qualquer Sprite ... Mas a questão mais parece assunto holywar do que verdadeira questão :)
avtomaton
7

Em Java, você pode usar exceções ou usar o padrão de fábrica, o que permitiria retornar nulo.

No Scala, você pode retornar uma opção [Foo] de um método de fábrica. Isso funcionaria em Java também, mas seria mais complicado.

Kim
fonte
Usando exceções ou fábrica nas linguagens .NET também.
Beth Whitezel
3

Lance uma exceção.

O nulo precisa ser verificado se você pode devolvê-lo (e não será verificado)

É para isso que servem as exceções verificadas. Você sabe que pode falhar. Os chamadores precisam lidar com isso.

Tim Williscroft
fonte
3

No C ++ , construtores são usados ​​para criar / inicializar membros da classe.

Não há resposta certa para esta pergunta. Mas o que observei até agora é que na maioria das vezes é o cliente (ou quem vai usar sua API) que escolhe como você deve lidar com essas coisas.

Às vezes, eles podem solicitar que você aloque todos os recursos que o objeto possa precisar no construtor e lançar uma exceção se algo falhar (anulando a criação do objeto), ou não fazer nada disso no construtor e garantir que a criação sempre será bem-sucedida , deixando essas tarefas para alguma função de membro.

Se você escolher o comportamento, as exceções são a maneira C ++ padrão para lidar com erros e você deve usá-las quando puder.

karlphillip
fonte
1

Você também pode instanciar o objeto sem parâmetros ou apenas com parâmetros que certamente nunca falharão e, em seguida, usará uma função ou método de inicialização a partir do qual poderá gerar uma exceção com segurança ou fazer o que quiser.

gablin
fonte
6
Eu acho que retornar um objeto inutilizável que requer mais inicialização é a pior escolha. Agora você tem um fragmento de várias linhas que precisa ser copiado sempre que a classe é usada ou fatorada em um novo método. Você também pode pedir ao construtor para fazer todo o trabalho e lançar uma exceção se o objeto não puder ser construído.
precisa
2
@ Kevin: Depende do objeto. As falhas provavelmente ocorrerão devido a recursos externos; portanto, uma entidade pode querer registrar a intenção (por exemplo, nome do arquivo), mas permitir tentativas repetidas para inicializar completamente. Para um objeto de valor, eu provavelmente me inclinaria a relatar um erro que pode capturar o erro subjacente, provavelmente lançando uma exceção (encapsulada?).
Dave
-4

Prefiro não inicializar nenhuma classe ou lista no construtor. Inicialize uma classe ou lista sempre que precisar. Por exemplo.

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

Não faça isso

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

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

Em vez disso, inicialize quando necessário, por exemplo.

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

    public Test()
    { 
    }

    public void SomeFunction()
    {
         emp = new List<Employee>();
    }
}
teste
fonte
1
Este é um péssimo conselho. Você não deve permitir que os objetos estejam em um estado inválido. É para isso que serve o construtor. Se você precisar de lógica complexa, use o padrão de fábrica.
AlexFoxGill
1
Os construtores estão lá para estabelecer os invariantes de classe, o código de exemplo não ajuda a afirmar isso.
Niall