Comment écrire des constructeurs qui pourraient ne pas réussir à instancier correctement un objet

11

Parfois, vous devez écrire un constructeur qui peut échouer. Par exemple, disons que je veux instancier un objet avec un chemin de fichier, quelque chose comme

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

Tant que le chemin pointe vers un fichier approprié, tout va bien. Mais si la chaîne n'est pas un chemin valide, les choses devraient se casser. Mais comment?

Vous pourriez:

  1. jeter une exception
  2. return null object (si votre langage de programmation permet aux constructeurs de renvoyer des valeurs)
  3. retourne un objet valide mais avec un drapeau indiquant que son chemin n'a pas été correctement défini (ugh)
  4. autres?

Je suppose que les "meilleures pratiques" de divers langages de programmation implémenteraient cela différemment. Par exemple, je pense que ObjC préfère (2). Mais (2) serait impossible à implémenter en C ++ où les constructeurs doivent avoir void comme type de retour. Dans ce cas, je suppose que (1) est utilisé.

Dans le langage de programmation de votre choix, pouvez-vous montrer comment gérer ce problème et expliquer pourquoi?

garageàtrois
la source
1
Les exceptions en Java sont une façon de gérer cela.
Mahmoud Hossam
Les constructeurs C ++ ne renvoient pas void- ils renvoient un objet.
gablin
6
@gablin: En fait, ce n'est pas strictement vrai non plus. newappelle operator newpour allouer la mémoire, puis le constructeur pour la remplir. Le constructeur ne renvoie rien et newrenvoie le pointeur dont il est issu operator new. Que "ne retourne rien" implique "retours void" est à gagner, cependant.
Jon Purdy
@Jon Purdy: Hm, ça semble raisonnable. Bon appel.
gablin

Réponses:

8

Il n'est jamais bon de compter sur un constructeur pour faire le sale boulot. En outre, il n'est pas clair non plus pour un autre programmeur si le travail va être effectué dans le constructeur à moins qu'il n'y ait une documentation explicite le stipulant (et que l'utilisateur de la classe l'ait lu ou ait été informé).

Par exemple (en C #):

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

Que se passe-t-il si l'utilisateur ne souhaite pas charger le fichier immédiatement? Que faire s'ils souhaitent effectuer une mise en cache à la demande du fichier? Ils ne peuvent pas. Vous pourriez penser à mettre un bool loadFileargument dans le constructeur, mais ce qui rend ce désordre que vous maintenant encore besoin d' une Load()méthode pour charger le fichier.

Compte tenu du scénario actuel, cela va être plus flexible et plus clair pour les utilisateurs de la classe pour ce faire:

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

Ou bien (pour quelque chose comme une ressource):

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
la source
dans votre cas si le chargement (..) échoue, nous avons un objet totalement inutile. Je ne suis pas sûr de vouloir un sprite sans sprite ... Mais la question ressemble plus à un sujet Holywar qu'à une vraie question :)
avtomaton
7

En Java, vous pouvez utiliser des exceptions ou utiliser le modèle d'usine, ce qui vous permettrait de retourner null.

Dans Scala, vous pouvez renvoyer une option [Foo] à partir d'une méthode d'usine. Cela fonctionnerait aussi en Java, mais serait plus lourd.

Kim
la source
Utilisation d'exceptions ou de fabrique dans les langages .NET également.
Beth Whitezel
3

Jetez une exception.

Null doit être vérifié si vous pouvez le retourner (et il ne sera pas vérifié)

C'est à cela que servent les exceptions vérifiées. Tu sais que ça pourrait échouer. Les appelants doivent gérer cela.

Tim Williscroft
la source
3

En C ++ , les constructeurs sont utilisés pour créer / initialiser des membres de la classe.

Il n'y a pas de bonne réponse à cette question. Mais ce que j'ai observé jusqu'à présent, c'est que la plupart du temps, c'est le client (ou celui qui va utiliser votre API) qui choisit la façon dont vous devez gérer ce genre de choses.

Parfois, ils peuvent vous demander d'allouer toutes les ressources dont l'objet pourrait avoir besoin sur le constructeur et lever une exception si quelque chose échoue (abandonner la création de l'objet), ou ne rien faire sur le constructeur, et vous assurer que la création réussira toujours , laissant ces tâches à certaines fonctions membres.

Si c'est à vous de choisir le comportement, les exceptions sont le moyen C ++ par défaut pour gérer les erreurs et vous devez les utiliser quand vous le pouvez.

karlphillip
la source
1

Vous pouvez également instancier l'objet sans paramètres, ou avec uniquement des paramètres qui ne manqueront jamais, puis vous utilisez une fonction ou une méthode d'initialisation à partir de laquelle vous pouvez lever une exception en toute sécurité ou faire ce que vous voulez.

gablin
la source
6
Je pense que retourner un objet inutilisable qui nécessite une initialisation supplémentaire est le pire choix. Vous disposez maintenant d'un fragment de plusieurs lignes qui doit être copié chaque fois que la classe est utilisée ou prise en compte dans une nouvelle méthode. Vous pouvez aussi demander au constructeur de faire tout le travail et de lever une exception si l'objet ne peut pas être construit.
kevin cline
2
@kevin: dépend de l'objet. Les échecs seront probablement dus à des ressources externes, donc une entité pourrait vouloir enregistrer l'intention (par exemple, le nom de fichier) mais permettre des tentatives répétées d'initialisation complète. Pour un objet de valeur, je pencherais probablement pour signaler une erreur qui peut capturer l'erreur sous-jacente, donc probablement lancer une exception (encapsulée?).
Dave
-4

Je préfère ne pas initialiser de classe ou de liste dans le constructeur. Initialisez une classe ou une liste chaque fois que vous en avez besoin. Par exemple.

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

Ne fais pas ça

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

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

Au lieu de cela, initialisez-le si nécessaire.

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

    public Test()
    { 
    }

    public void SomeFunction()
    {
         emp = new List<Employee>();
    }
}
tester
la source
1
C'est un mauvais conseil. Vous ne devez pas autoriser les objets à être dans un état non valide. C'est à ça que sert le constructeur. Si vous avez besoin d'une logique complexe, utilisez le modèle d'usine.
AlexFoxGill
1
Les constructeurs sont là pour établir les invariants de classe, l'exemple de code n'aide pas du tout à affirmer cela.
Niall