web-dev-qa-db-fra.com

Comment puis-je forcer un constructeur à être défini dans toutes les sous-classes de ma classe abstraite

J'ai une classe abstraite A qui définit les méthodes abstraites. Cela signifie que, pour qu'une classe soit instanciable, toute la méthode abstraite doit être implémentée.

J'aimerais que toutes mes sous-classes implémentent un constructeur avec 2 pouces comme paramètres.

Déclarer un constructeur va à l'encontre de mon objectif, car je veux que le constructeur soit défini dans les sous-classes et je ne sais rien de l'implémentation. De plus, je ne peux pas déclarer un constructeur comme étant abstrait;

Y a-t-il un moyen de faire cela ?

Exemple de ce que je veux:

Disons que je définis l'API d'une classe Matrix. Dans mon problème, Matrix ne peut pas changer leurs dimensions.

Pour qu'une matrice soit créée, je dois fournir sa taille.

Par conséquent, je veux que tous mes implémenteurs fournissent au constructeur la taille en tant que paramètre. Ce constructeur est motivé par le problème et non par un souci d'implémentation. L'implémentation peut en faire ce qu'elle veut, à condition que toute la sémantique des méthodes soit conservée.

Disons que je veux fournir une implémentation de base de la méthode invert() dans ma classe abstraite. Cette méthode créera une nouvelle matrice avec this dimensions inversées. Plus précisément, comme il est défini dans la classe abstraite, il créera une nouvelle instance de la même classe que this, en utilisant un constructeur qui prend deux ints. Comme il ne connaît pas l'instance, il utilisera la réflexion (getDefinedConstructor) et je veux un moyen de garantir que je vais l'obtenir et qu'il sera significatif pour l'implémentation.

64
dodecaplex

Vous ne pouvez pas forcer une signature particulière de constructeur dans votre sous-classe - mais vous pouvez la forcer à passer par un constructeur de votre classe abstraite en prenant deux entiers. Les sous-classes pourraient appeler ce constructeur à partir d'un constructeur sans paramètre, en passant des constantes, par exemple. C'est le plus proche que vous puissiez venir.

De plus, comme vous le dites, vous ne savez rien de l'implémentation - alors comment savez-vous qu'il est approprié pour eux d'avoir un constructeur qui nécessite deux entiers? Et si l'un d'eux avait également besoin d'une chaîne? Ou peut-être qu'il est logique d'utiliser une constante pour l'un de ces nombres entiers.

Quelle est la plus grande image ici - pourquoi voulez-vous forcer une signature de constructeur particulière sur vos sous-classes? (Comme je l'ai dit, vous ne pouvez pas réellement faire cela, mais si vous expliquez pourquoi vous le souhaitez, une solution pourrait se présenter.)

Une option consiste à avoir une interface distincte pour une usine:

interface MyClassFactory
{
    MyClass newInstance(int x, int y);
}

Ensuite, chacune de vos sous-classes concrètes de MyClass aurait également besoin d'une fabrique qui savait comment construire une instance avec deux entiers. Ce n'est pas très pratique cependant - et vous auriez encore besoin de créer des instances des usines elles-mêmes. Encore une fois, quelle est la situation réelle ici?

53
Jon Skeet

Vous pouvez essayer quelque chose comme ci-dessous. Le constructeur lèvera une exception si la classe d'implémentation n'a pas de constructeur avec les arguments appropriés.

C'est idiot. Comparez OK et Bad. Les deux classes sont les mêmes, sauf que OK répond à vos besoins et passe ainsi les vérifications d'exécution. Ainsi, l'application de cette exigence favorise un travail occupé contre-productif.

Une meilleure solution serait une sorte d'usine.

abstract class RequiresConstructor
{
    RequiresConstructor( int x, int y ) throws NoSuchMethodException
    {
    super();
    System.out.println( this.getClass().getName() ) ;
    this.getClass(). getConstructor ( int.class , int.class ) ;
    }

    public static void main( String[] args ) throws NoSuchMethodException
    {
    Good good = new Good ( 0, 0 );
    OK ok = new OK ();
    Bad bad = new Bad ();
    }
}

class Good extends RequiresConstructor
{
    public Good( int x, int y ) throws NoSuchMethodException
    {
    super( x, y ) ;
    }
}

class OK extends RequiresConstructor
{
    public OK( int x, int y ) throws NoSuchMethodException
    {
    super( x, y ) ;
    throw new NoSuchMethodException() ;
    }

    public OK() throws NoSuchMethodException
    {
    super( 0, 0 ) ;
    }
}

class Bad extends RequiresConstructor
{
    public Bad() throws NoSuchMethodException
    {
    super( 0, 0 ) ;
    }
}
6
emory

Si vous devez définir dans votre interface la représentation interne que les classes d'implémentation utiliseront, alors vous vous trompez. Veuillez lire encapsulation et abstraction de données .

Si votre implémentation abstraite repose sur certains détails d'implémentation, alors ils appartiennent à cette classe abstraite. Cela signifie que la classe abstraite doit définir un constructeur qui lui permet d'initialiser l'état interne nécessaire pour permettre aux méthodes abstraites de fonctionner.

Généralement, les constructeurs sont destinés à créer une instance d'une classe en fournissant quelques détails sur l'état initial de cette instance d'objet. Cela ne signifie pas que l'instance en cours de construction doit copier une référence à chaque argument individuel comme c'est souvent le cas dans la plupart des logiciels que je vois. Par conséquent, même si Java offrait une construction pour forcer l'implémentation de certaines signatures de constructeur sur les sous-classes, ces sous-classes pourraient facilement rejeter les arguments.

3
Tim Bender

Un peu tard, mais ...

Créez simplement un constructeur par défaut dans votre classe qui est toujours appelé super constructeur. Dans ce constructeur par défaut, vous pouvez vérifier tous les constructeurs définis en réfléchissant à son propre objet de classe (qui n'est alors pas la super classe abstraite mais la sous-classe concrète). Si le constructeur que vous souhaitez implémenter est manquant, lancez une exception d'exécution.

Je ne suis pas un grand ami de la réflexion car il a le goût de pirater par la porte arrière, mais parfois ça aide ...

Jetez un œil à cet exemple:

import Java.lang.reflect.Constructor;

public abstract class Gaga {
  public Gaga() {
    boolean found = false;
    try {
      Constructor<?>[] constructors = getClass().getConstructors();
      for (Constructor<?> c : constructors) {
        if (c.getParameterTypes().length==2) {
          Class<?> class0 = c.getParameterTypes()[0];
          Class<?> class1 = c.getParameterTypes()[1];
          if ( (class0.getName().equals("int") || class0.isAssignableFrom(Integer.class))
              &&  (class1.getName().equals("int") || class1.isAssignableFrom(Integer.class)) )
            found = true;
        }
      }
    } catch (SecurityException e)
    {
      found = false;
    }

    if (!found)
      throw new RuntimeException("Each subclass of Gaga has to implement a constructor with two integers as parameter.");

    //...
  }

}

Et une classe de test:

public class Test {
  private class Gaga1 extends Gaga {
    public Gaga1() { this(0, 0); }
    public Gaga1(int x, Integer y) { }
  }

  private class Gaga2 extends Gaga {

  }

  public static void main(String[] args)
  {
    new Gaga1();
    new Gaga1(1, 5);
    new Gaga2();
    System.exit(0);
  }
}

Dans la fonction principale, les objets de Gaga1 seront créés, mais la création de Gaga2 lèvera une exception d'exécution.

Mais vous ne pouvez pas être sûr que ce constructeur est appelé - vous ne pouvez même pas vous assurer qu'il fait les choses que vous voulez.

Ce test n'est utile que si vous travaillez avec la réflexion.

3
Abdullah