/**
   POGL : programmation objet et génie logiciel
   Champs d'instance, champs de classe.

   @author Thibaut Balabonski @ Université Paris-Sud
   @version 21 janvier 2019
*/

/**
   Les attributs d'une classe définissent les états possibles des instances
   de cette classe : chaque instance (c'est-à-dire chaque objet) a ses propres
   valeurs pour chaque attribut, qui peuvent évoluer indépendamment des valeurs
   des mêmes attributs pour les autres instances.

   À l'inverse de ce comportement de référence, il existe des attributs dits
   'attributs de classe' qui ne sont pas liés à un objet mais directement à
   une classe, avec une valeur unique. Ces attributs sont qualifiés par le
   mot-clé [static].

   Ainsi, dans l'exemple suivant, un attribut de classe [instanceCount] est
   attaché à la classe [Count], et compte le nombre d'instances qui ont été
   créées.
*/

class Count {
    // Déclaration d'un attribut de classe, initialisé à [0]
    private static int instanceCount = 0;
    
    public Count() {
        // La valeur de l'attribut est incrémentée à chaque utilisation
        // du constructeur
        instanceCount++;
    }

    // Exemple : la valeur de [instanceCount] est partagée, et les
    // modifications sont visibles sur toutes les instances
    public static void main(String[] args) {
        Count c0 = new Count();
        System.out.println(c0.instanceCount); // Affiche 1
        Count c1 = new Count();
        System.out.println(c1.instanceCount); // Affiche 2
        Count c2 = new Count();
        System.out.println(c2.instanceCount); // Affiche 3
        System.out.println(c1.instanceCount); // Affiche 3
        System.out.println(c0.instanceCount); // Affiche 3
    }
}

/**
   Un attribut de classe n'étant pas lié à un objet particulier, on peut y
   accéder ou le modifier à partir de n'importe quelle instance de la classe,
   ou à partir de la classe elle-même avec une construction de la forme
       NomClasse . nomAttribut
*/

class Acc {
    private static int value = 0;
    public static void main(String[] args) {
        Acc a0 = new Acc();
        Acc a1 = new Acc();
        a0.value = 5;                  
        System.out.println(a1.value);  // Affiche 5
        a1.value += 2;
        System.out.println(Acc.value); // Affiche 7
        Acc.value += 3;
        System.out.println(a0.value);  // Affiche 10
    }            
}

/**
   La notion de champ de classe s'applique également aux méthodes. Pour
   comprendre ce qui signifie pour une méthode de "ne pas être rattachée à un
   objet", il faut se rappeler que tous les appels de méthodes vus jusqu'ici
   avaient la forme
       obj . m ( p1, ..., pn )
   avec [obj] une instance particulière appelée "paramètre implicite", dont on
   peut manipuler les attributs dans le corps de la méthode en y accédant avec
   une construction de la forme
       this . a
   (Java autorise également à laisser [this] implicite en écrivant juste [a]).

   Une méthode de classe se différencie d'une méthode d'instance en ce qu'elle
   n'a pas de paramètre implicite. En particulier, il n'est pas possible de
   faire référence à [this] dans le corps de la méthode, que ce soit
   explicitement ou implicitement. Une méthode d'instance ne peut accéder qu'aux
   attributs de classe, et aux attributs d'instances d'objets auxquels elle a
   accès par ailleurs.
*/

class LimitedEdition {
    
    // Un attribut de classe comptant le nombre d'instances créées
    private static int nb = 0;
    // Un attribut de classe fixant un nombre maximal d'instances
    private static final int MAX = 10;
    // Un attribut d'instance enregistrant le numéro de chaque instance
    private final int id;
    /**
       Note : le qualificatif [final], appliqué à un attribut, indique que
       l'attribut est "immuable", c'est-à-dire que sa valeur ne peut pas être
       modifiée. Un tel attribut reçoit une fois une valeur, soit par défaut
       comme [MAX] soit à la construction comme [id], et cette valeur est
       définitive.
     */

    // Constructeur qui incrémente le compteur et initialise l'attribut [id].
    public LimitedEdition() {
        assert LimitedEdition.nb < LimitedEdition.MAX;
	this.id = ++LimitedEdition.nb;
    }

    // Une méthode ordinaire (méthode d'instance) peut accéder à [this] et
    // aux attributs de classe
    public void printId() {
	System.out.println("Instance " + this.id + " sur " + LimitedEdition.nb);
    }

    // Une méthode de classe ne peut accéder qu'aux attributs de classe
    public static void printTotal() {
	System.out.println(nb + " sur " + LimitedEdition.MAX);
    }
    // De même que [this] peut être laissé implicite pour les attributs
    // d'instance, le nom de la classe peut être laissé implicite pour les
    // attributs de classe de la classe courante, ce qui est fait pour [id]
    // dans la méthode précédente.

    // Une méthode de classe peut accéder en revanche aux attributs de classe
    // des instances qui lui sont fournies
    public static void printInstance(LimitedEdition le) {
        System.out.println("Instance " + le.id + " sur " + LimitedEdition.nb);
    }

    // Au fait, les méthodes de classe vous connaissiez déjà.
    public static void main(String[] args) {
        LimitedEdition le1 = new LimitedEdition();
        LimitedEdition le2 = new LimitedEdition();
        LimitedEdition.printTotal();       // Affiche 2 sur 10
        le1.printId();                     // Affiche 1 sur 2
        LimitedEdition.printInstance(le1); // Affiche 1 sur 2
        le1.printInstance(le2);            // Affiche 2 sur 2
    }
    
}