/**
   POGL : programmation objet et génie logiciel
   Classes abstraites
   
   @author Thibaut Balabonski @ Université Paris-Sud
   @version 28 janvier 2019
*/

/**
   Une définition de classe ordinaire est constituée :
   - de déclarations d'attributs (avec éventuellement des valeurs initiales)
   - de définitions de constructeurs
   - de définitions de méthodes

   Une /classe abstraite/ peut en outre contenir des déclarations de méthodes
   non accompagnées de définitions, qu'on appelle /méthodes abstraites/. Le
   caractère abstrait d'une méthode ou d'une classe doit impérativement être
   indiqué, à l'aide du mot-clé [abstract].

   Une classe abstraite peut être vue comme une classe "à trous", destinée à
   être complétée pour former une vraie classe .
*/

abstract class A {
    // Attribut ordinaire
    protected int a;
    /* La visibilité [protected] rend cet attribut accessible depuis une
       éventuelle classe fille. */
    
    // Constructeur ordinaire
    public A(int a) {
        this.a = a;
    }

    // Méthode ordinaire
    public int getA() {
        return a;
    }

    // Méthode abstraite
    /**
       On ne déclare que la signature (on dit parfois aussi le profil),
       c'est-à-dire le type du résultat et les noms et types des paramètres.
       On ne donne en revanche aucun code.
    */
    abstract public int f(int x);

    public static void main(String[] args) {
        // A obj = new A(3);
        /**
           Il n'est pas possible de créer une instance d'une classe abstraite.
           En effet, certaines des méthodes déclarées dans la classe ne sont
           pas définies : on pourrait envisager un appel à une méthode définie
           comme
             obj.getA()
           mais un appel à une méthode abstraite tel
             obj.f(4)
           ne pourrait être exécuté, faute de code pour [f].
           Pour éviter ce genre de problème plus tard, Java restreint en amont
           la création d'objets.
        */
    }
}

/**
   Lors de sa définition, toute classe peut être déclarée comme /étendant/ une
   autre classe, ce qui est noté avec le mot-clé [extends].
   
   Une classe B étendant une classe A possède tous les attributs et méthodes de
   la classe A (on dit aussi qu'elle en /hérite/). De plus, la définition de la
   classe B peut apporter de nouveaux éléments parmi notamment :
   - la déclaration de nouveaux attributs
   - la définition de nouvelles méthodes
   - de nouvelles définitions pour des méthodes déjà définies dans A (on en
     reparlera plus tard)
   - des définitions pour les méthodes abstraites de A

   C'est par la définition de la totalité de ses méthodes abstraites qu'on
   crée une classe concrète à partir d'une classe abstraite donnée.
*/

class B extends A {
    /* L'attribut [a] et la méthode [getA] existent déjà, ne surtout pas les
       mentionner à nouveau (au risque de créer de nouveaux attributs ou 
       méthodes interférant avec ceux déjà présents). */

    // Définition d'un constructeur
    public B(int a) {
        /* On fait appel au constructeur de la classe mère [A], qui se charge
           d'initialiser l'attribut [a]. */
        super(a);
    }

    // Définition pour la méthode abstraite [f]
    /**
       On répète la signature à l'identique, mais on y adjoint une définition
       sous la forme habituelle d'un bloc de code.
    */
    public int f(int x) {
        this.a += x;
        return this.a;
        /* Note : si l'attribut [a] était privé dans [A] il ne serait pas
           possible de faire cette mise à jour depuis la classe [B]. */
    }
}

/**
  Une classe [B] étendant la classe abstraite [A] peut ne pas définir toutes
  les méthodes abstraites de [A]. Dans ce cas, la classe [B] hérite des
  déclarations de méthodes abstraites restantes, et doit elle-même être
  déclarée abstraite.
*/