71 votes

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 des méthodes abstraites. Cela signifie que, pour qu'une classe soit instanciable, toutes les méthodes abstraites doivent être implémentées.

Je voudrais que toutes mes sous-classes implémentent un constructeur avec 2 ints 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 le faire ?

Exemple de ce que je veux :

Disons que je suis en train de définir l'API d'une classe Matrix. Dans mon problème, les matrices ne peuvent pas modifier 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 de conserver toute la sémantique des méthodes.

Disons que je veux fournir une implémentation de base de la fonction invert() dans ma classe abstraite. Cette méthode va créer 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 en tant 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 l'obtiendrai et que ce sera significatif pour l'implémentation.

0 votes

Une solution élégante peut être trouvée ici : stackoverflow.com/questions/6028526/

56voto

Jon Skeet Points 692016

Vous ne pouvez pas forcer une signature particulière du constructeur dans votre sous-classe - mais vous pouvez peut le forcer à passer par un constructeur dans votre classe abstraite prenant deux entiers. Sous-classes pourrait appeler ce constructeur depuis un constructeur sans paramètre, en passant des constantes, par exemple. C'est le plus proche que vous puissiez faire.

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

Quelle est la situation dans son ensemble ? pourquoi voulez-vous imposer une signature de constructeur particulière à vos sous-classes ? (Comme je l'ai dit, vous ne pouvez pas réellement faire mais si vous expliquez pourquoi vous le voulez, une solution pourrait se présenter d'elle-même).

Une option consiste à disposer d'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 usine qui sache construire une instance à partir de deux entiers. Mais ce n'est pas très pratique - et il faudrait encore construire des instances des fabriques elles-mêmes. Encore une fois, quelle est la situation réelle ici ?

2 votes

Disons que je suis en train de définir l'API d'une classe Matrix. Dans mon problème, les matrices ne peuvent pas modifier 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 le constructeur avec la taille comme 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 de conserver toute la sémantique des méthodes.

0 votes

@dodecaplex : Mais que faire si vous voulez créer une FixedSizeMatrix qui est toujours 10 x 10 ? Tu ne peux pas. appelez Les constructeurs sont de toute façon polymorphes, alors pourquoi essayez-vous de restreindre l'implémentation ?

1 votes

Alors l'implémentation ne sera pas conforme à mon API... Si elle a de bonnes raisons de le faire, alors, elle fournira un constructeur à zéro argument et un constructeur à 2 arguments qui lèvera une exception si les arguments ne sont pas 10x10. Cela signifie que je serai toujours capable de créer une matrice vide de la même implémentation et de la même taille (sans connaître l'implémentation effective), mais j'obtiendrai une exception si j'essaie d'utiliser cette implémentation pour des matrices non 10x10.

6voto

emory Points 6319

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 identiques, sauf que OK répond à votre exigence et passe donc les contrôles d'exécution. Ainsi, l'application de l'exigence favorise un travail 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 ) ;
    }
}

3voto

Abdullah Points 31

Un peu tard, mais...

Il suffit de créer un constructeur par défaut dans votre classe qui est toujours appelé comme super constructeur. Dans ce constructeur par défaut, vous pouvez vérifier tous les constructeurs définis avec la réflexion sur son propre objet de classe (qui n'est donc pas la super classe abstraite mais la sous-classe concrète). Si le constructeur que vous souhaitez implémenter est manquant, une exception d'exécution est levée.

Je ne suis pas un grand ami de la réflexion, car elle a le goût du piratage par la porte de derrière, mais parfois, elle aide...

Jetez un coup d'œ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 d'essai :

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 entraînera 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.

3voto

Tim Bender Points 11611

Si vous devez définir dans votre interface la représentation interne que les classes d'implémentation utiliseront, alors vous vous y prenez mal. Veuillez lire ce qui suit encapsulation y abstraction de données .

Si votre implémentation abstraite repose sur certains détails d'implémentation, alors ils appartiennent à cette classe abstraite. Autrement dit, 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.

En général, les constructeurs sont destinés à créer une instance d'une classe en fournissant certains détails sur l'état initial de cette instance d'objet. Cela ne signifie pas que l'instance construite 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 permettant de forcer l'implémentation de certaines signatures de constructeur sur les sous-classes, ces sous-classes pourraient facilement rejeter les arguments.

1 votes

Eh bien, si je définis un point dans un espace à n dimensions, je sais avec certitude que ce point ne peut pas exister dans un espace à m dimensions (m != n). Par conséquent, il me semble que la dimension de l'espace est un attribut INHÉRENT du point, indépendamment de l'implémentation du point. Pour autant que je sache, il s'agit d'encapsulation et d'abstraction de données. Si la dimension est NECESSAIREMENT connue pour chaque instance à créer, il semble naturel d'EXIGER que toutes les implémentations fournissent un constructeur qui prend cet argument.

0 votes

@dodecaplex, Ahh, un point, excellent exemple. Supposons que vous parlez d'un point à 2 dimensions. Bien sûr, je peux utiliser une valeur x et y pour le représenter. Ou bien, je peux utiliser une valeur de radians/degrés et une valeur de distance pour le représenter. Ce n'est qu'un exemple. Il pourrait y avoir d'autres façons. Vous pensez seulement savoir comment l'une d'entre elles pourrait être mise en œuvre. De toute façon, par définition, si vous essayez d'imposer une représentation, vous n'utilisez pas l'abstraction de données.

1voto

mehdi akbarian Points 306

Cours de cuisine :

public abstract class SupperClass {
  protected Foo foo;

  //primary constructor
  public SupperClass(Foo foo) {
      this.foo = foo;
  }

  private SupperClass(){
    //with this private constructor, the subclass forced to implement primary constructor
  }

}

Sous-classe :

public class SubClass extends JLLayer {

  public SubClass(Foo foo) {
      super(foo);
  }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X