Fonctionnement de l'extends

LaForêt

Bucheron
11 Septembre 2022
8
0
11
34
Salut j'ai un petit problème avec ma liste dans mon test :

Class GUItestManager

Code:
private List<ItemStack> listItemStack = new ArrayList<>();

public void initGui() {
    TestGui testGui = new TestGui();

    testLoadItemStack();
    testGui.SetupConfirmGUI();
}

private void testLoadItemStack() {
    ItemStack vide = new ItemStack(Material.BLACK_STAINED_GLASS_PANE, 1);
    ItemMeta videMeta = vide.getItemMeta();
    videMeta.setDisplayName(" ");
    vide.setItemMeta(videMeta);

    listItemStack.add(vide);
}

public ItemStack getItemFromList(int i) {
    return listItemStack.get(i);
}


Dans ma class TestGui

Code:
public class TestGui extends GUItestManager {


    public void SetupTestGUI() {
        System.out.println(getItemFromList(0));
}

Le problème est que System.out.println(getItemFromList(0)); est toujours une erreur peu import la valeur, après vérification lorsque je fais getItemFromList la liste a toujours un contenu totalement vide il n'y a rien dans la liste, pourquoi ma liste d'itemstack est reset a chaque fois ? Je pense que c'est a cause de l'extends mais j'aimerais confirmation
 
Salut,

Attends, dans la classe "GUItestManager", tu créé l'objet "TestGui".
Sauf que "TestGui" est enfant de "GUItestManager". Ca n'a aucun sens...

Au passage, les méthodes (fonction dans une classe) ne commencent jamais (sauf si c'est le constructeur) avec une majuscule.


Voila un exemple fonctionnel:
Java:
class GUItestManager {

    private List<ItemStack> listItemStack = new ArrayList<>();

    public void GUItestManager() {
        testLoadItemStack();
    }

    private void testLoadItemStack() {
        final ItemStack vide = new ItemStack(Material.BLACK_STAINED_GLASS_PANE, 1);
        final ItemMeta videMeta = vide.getItemMeta();
        videMeta.setDisplayName(" ");
        vide.setItemMeta(videMeta);
        listItemStack.add(vide);
    }

    public ItemStack getItemFromList(int i) {
        return listItemStack.get(i);
    }

}


public class TestGui extends GUItestManager {
    public void setupTestGUI() {
        System.out.println(getItemFromList(0));
    }
}


// Dans ta classe main qui extends JavaPlugin
final TestGui test = new TestGui();
test.setupTestGUI();



Cordialement,
Detobel36
 
  • J'aime
Reactions: LaForêt
Salut,

Attends, dans la classe "GUItestManager", tu créé l'objet "TestGui".
Sauf que "TestGui" est enfant de "GUItestManager". Ca n'a aucun sens...

Au passage, les méthodes (fonction dans une classe) ne commencent jamais (sauf si c'est le constructeur) avec une majuscule.


Voila un exemple fonctionnel:
Java:
class GUItestManager {

    private List<ItemStack> listItemStack = new ArrayList<>();

    public void GUItestManager() {
        testLoadItemStack();
    }

    private void testLoadItemStack() {
        final ItemStack vide = new ItemStack(Material.BLACK_STAINED_GLASS_PANE, 1);
        final ItemMeta videMeta = vide.getItemMeta();
        videMeta.setDisplayName(" ");
        vide.setItemMeta(videMeta);
        listItemStack.add(vide);
    }

    public ItemStack getItemFromList(int i) {
        return listItemStack.get(i);
    }

}


public class TestGui extends GUItestManager {
    public void setupTestGUI() {
        System.out.println(getItemFromList(0));
    }
}


// Dans ta classe main qui extends JavaPlugin
final TestGui test = new TestGui();
test.setupTestGUI();



Cordialement,
Detobel36
Salut merci du rappel concernant les fonctions.

Concernant le "sens" de ce que je voulais faire et de t'as remarque sur le main, je voulais justement éviter de faire ça dans le main, le main aurait alors appelé une seule class et une seule fonction dans GUItestManager, qui elle ensuite aurait fait le travail
Code:
final TestGui test = new TestGui();
test.setupTestGUI();
pour chaque GUI (en admettons qu'il y en ait beaucoup)

C'est peut-être une mauvaise approche de ma part ? Ca me semblais logique que GUItestManager s'occupe de charger tous les GUI et les différents itemstack nécessaire aux fonctionnements des GUI, et qu'ensuite les dits différents GUI via l'extends, puissent simplement get l'itemstack qu'ils souhaitent depuis une list déjà toute faite
 
Salut,


Si ton "GUItestManager" est un manager, pourquoi tu veux que ce soit un parent ?
Le but d'un parent est d'implémenter des choses que les enfants ne doivent du coup plus implémenter...

Exemple concret:
Java:
abstract class Game {

  protected final List<Player> listJoueurs;
  private final String name;

  protected Game(final String name) {
    this.name = name;
    this.listJoueurs = new ArrayList<>();
  }

  public void play() {
    Bukkit.broadcastMessage("Démarrage de la partie");
  }

  public boolean isName(final String name) {
    return this.name.equalsIngoreCaes(name);
  }

}

class RushGame extends Game {
  public RushGame() {
    super("rush");
  }

  @Override
  public void play() {
    super.play();
    Bukkit.broadcastMessage("Bonne chance");
  }

}

class TowerGame extends Game {
  public TowerGame() {
    super("tower");
  }
}

Et du coup, dans mon exemple, si je devais faire un "manager":
Java:
// Singleton
class GameManager {

  private static GameManager instance = null;

  private List<Game> listGame;

  private GameManager() {
    this.listGame = new ArrayList<>();
    initGames();
  }

  private void initGame() {
    this.listGame.add(new RushGame());
    this.listGame.add(new TowerGame());
  }

  private void playTheGame(final String gameName) {
    for(final Game game : this.listGame) {
      if(game.isName(gameName)) {
        game.play();
        return;
      }
    }
  }

  public static GameManager getGameManager() {
    if(instance == null) {
      instance = new GameManager();
    }
    return instance;
  }

}

Dans ton code, tu peux ensuite utiliser ton gestionnaire dans ton main en faisant:
Java:
// class Main extends JavaPlugin {
// ...

  final GameManager gm = GameManager.getGameManager();
  gm.playTheGame("Rush");

//}


Plusieurs remarques:
  • Au lieu de faire un "for", il est possible de faire la même chose avec des "stream".
  • Le "listJoueurs" n'est pas utilisé actuellement, c'est juste un exemple.
  • Je n'ai pas testé le code, j'ai tout fait de tête (même pas dans une IDE). Donc faute de frappe possible !


Cordialement,
Detobel36
 
  • J'aime
Reactions: LaForêt
Bonsoir,

Le but d'un manager est de gérer un ensemble homogène d'objets, par ex. BookManager représenterait une bibliothèque, et s'occuperait de rajouter les nouveaux livres au bon endroit (les tomes 2 à droite des tomes 1, et non dans le premier trou qu'il trouve).

Dire que TestGui est un GUItestManager revient à dire qu'un livre est un gestionnaire de livres/une bibliothèque, ce qui n'a pas beaucoup de sens en soit.

Il est possible en Java de créer des fonctions qui ne sont pas liées à des objets avec le mot-clef static, par ex. :
Java:
public class Math {
    // La constante π
    public static final float PI = 3.14f;
    
    // Renvoie la valeur absolue d'un nombre à virgule flottante
    public static float abs(float x) {
        if(x < 0) {
            return -x;
        else {
            return x;
        }
    }
}

float f = Math.abs(Math.PI);
System.out.println(f); // 3.14

Donc ce que tu veux serait plutôt une classe utilitaire (c'est-à-dire qu'on ne créer jamais ; tu ne feras jamais un new Math().cos(0)) :
Java:
public class GuiHelper {
    private static final List<ItemStack> ITEM_STACKS = new ArrayList();
    
    public static void init() {
        ItemStack empty = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
        ItemMeta meta = empty.getItemMeta();
        meta.setDisplayName(" ");
        
        empty.setItemMeta(meta);
        ITEM_STACKS.add(empty);
    }
    
    public static ItemStack getItem(int i) {
        return ITEM_STACKS.get(i);
    }
}

public class MyPlugin extends JavaPlugin {
    @Override
    public void onEnable() {
        GuiHelper.init();
        this.getLogger().info(GuiHelper.getItem(0));
    }
}

Et pour en revenir à ta question de pourquoi ce que tu avais fait ne marchait pas :
Java:
public class GUItestManager {
    private List<ItemStack> listItemStack = new ArrayList<>();
    
    public void initGui() {
        // créer un nouvel objet héritant de GUItestManager
        TestGui testGui = new TestGui();
        
        // modifie this.listItemStack
        testLoadItemStack();
        
        // testGui.listItemStack n'a jamais été modifié
        testGui.SetupConfirmGUI();
    }
}
Chaque instance de GUItestManager (et par conséquent TestGui, vu que c'est un GUItestManager) a sa propre variable listItemStack, et tu n'as jamais appelé la fonction testLoadItemStack() sur l'objet testGui que tu viens de créer. Même si un chat engendre un chaton, ceux-ci ont deux corps différents et ce n'est pas en puçant un parent que tu puceras le chaton ; si tu veux que toutes les instances d'une classe partagent une même variable, il faut la préfixer du mot-clef static.

Après il se trouve qu'en Java, il existe une fonction qui est automatiquement appelée lorsqu'une classe est chargée en mémoire pour la première fois ; c'est ce qu'on appelle le constructeur de classe ou initialiseur statique :
Java:
public class GuiHelper {
//  @Unmodifiable
    private static final List<ItemStack> ITEM_STACKS;
    
    static {
        ItemStack empty = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
        ItemMeta meta = empty.getItemMeta();
        meta.setDisplayName(" ");
        
        empty.setItemMeta(meta);
        
        ITEM_STACKS = new ImmutableList.Builder<ItemStack>().add(empty).build();
    }
    
    public static ItemStack getItem(int i) {
        return ITEM_STACKS.get(i);
    }
}

public class MyPlugin extends JavaPlugin {
    @Override
    public void onEnable() {
        // accéder à GuiHelper pour la première fois appelera automatiquement
        //   le constructeur de classe `static { ... }` de GuiHelper
        this.getLogger().info(GuiHelper.getItem(0));
    }
}

Ensuite, l'idée en programmant est quand même de s'approcher d'un langage naturel ;
Java:
System.out.println(getItemFromList(0));
Moi je lis « afficher le premier élément de la liste ». Or tu veux « afficher l'item vide/la bordure ».

Dans trois mois, tu vas rouvrir ton code pour voir ce genre de monstre :
Java:
ItemStack item = getItemFromList(666);
Et donc tu vas être obligé de relire comment est faite ta liste, récupérer le numéro de la ligne où est le premier élement, additionner 666 à ce nombre, puis regarder quel item se situe à cette nouvelle ligne pour savoir de quoi on parle.

C'est ce qu'on appelle les nombres magiques, c'est une très mauvaise idée d'en avoir, donc créer une constante magique pour y remédier :
Java:
public class GuiHelper {
    private static final List<ItemStack> ITEM_STACKS;
    public static final int ITEM_EMPTY_BORDER;
    
    static {
        ItemStack empty = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
        ItemMeta meta = empty.getItemMeta();
        meta.setDisplayName(" ");
        
        empty.setItemMeta(meta);
        
        ITEM_STACKS = new ImmutableList.Builder<ItemStack>().add(empty).build();
        ITEM_EMPTY_BORDER = ITEM_STACKS.indexOf(empty);
    }
    
    public static ItemStack getItem(int i) {
        return ITEM_STACKS.get(i);
    }
}

ItemStack item = GuiHelper.getItem(GuiHelper.ITEM_EMPTY_BORDER);

Et en soit je ne sais pas trop pourquoi tu t'emmerdes avec une liste :
Java:
public class GuiHelper {
    public static final ItemStack ITEM_EMPTY_BORDER;
    
    static {
        ItemMeta meta;
        
        ITEM_EMPTY_BORDER = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
        meta = ITEM_EMPTY_BORDER.getItemMeta();
        meta.setDisplayName(" ");
        
        ITEM_EMPTY_BORDER.setItemMeta(meta);
    }
}

public class MyPlugin extends JavaPlugin {
    @Override
    public void onEnable() {
        this.getLogger().info(GuiHelper.ITEM_EMPTY_BORDER);
    }
}

Après il faut noter que si jamais tu modifies l'item tu le modifieras pour tout le monde, donc n'oublie pas de dupliquer l'objet si jamais tu veux le modifier :
Java:
ItemStack stack = GuiHelper.ITEM_EMPTY_BORDER.clone();
stack.setAmount(64);

Mais en théorie si l'objet change en cours de route il faudrait plutôt en faire une fonction :
Java:
// Dans GuiHelper
public static ItemStack getPlayerHead(Player p) {
    // ...
}

Et ce serait aussi pas mal de créer deux classes utilitaires :
  • GuiItems avec tous les items globaux
  • GuiHelper avec tout ce qui touche l'inventaire (style remplir une ligne avec un même item)
Ça permettra d'écrire un code plus concis :
Java:
GuiHelper.fillLine(inventory, 0, GuiItems.BORDER);

Mais dans ce cas là, ce serait plus intéressant de faire une classe parente qui gèrera les évènements en plus :
Java:
public class Gui {
   private static final Map<Player, Gui> OPEN_GUIS = new WeakHashMap<>();
   
   protected final Inventory inventory;
   private final IntObjectMap<Consumer<Player>> events = new IntObjectHashMap<>();
   
   protected Gui(Inventory inventory) {
      if(inventory.getType() != InventoryType.CHEST) {
         throw new UnsupportedOperationException("Only chest inventories are supported");
      }
      
      this.inventory = inventory;
   }
   
   protected final void registerClickEvent(int slot, Consumer<Player> handler) {
      this.events.put(slot, handler);
   }
   
   protected final void fillBorder(@Nullable ItemStack stack) {
      final int rows = this.inventory.getSize() / 9;
      if(rows < 3) {
         throw new IllegalArgumentException("At least three rows are required for a border to be drawn");
      }
      
      for(int i = 0; i < 9; ++i) {
         this.inventory.setItem(i, stack);
      }
      
      for(int row = 1; row < (rows - 1); ++row) {
         final int rowStart = 9 * row;
         final int rowEnd = rowStart + 8;
         
         this.inventory.setItem(rowStart, stack);
         this.inventory.setItem(rowEnd, stack);
      }
      
      final int lastRowBegin = 9 * (rows - 1);
      final int lastRowEnd = lastRowBegin + 9;
      
      for(int i = lastRowBegin; i < lastRowEnd; ++i) {
         this.inventory.setItem(i, stack);
      }
   }
   
   public final void open(Player p) {
      OPEN_GUIS.put(p, this);
      p.openInventory(this.inventory);
   }
   
   public static void handleEvent(InventoryClickEvent e) {
      Gui gui;
      if(e.getWhoClicked() instanceof Player p && (gui = OPEN_GUIS.get(p)) != null) {
         e.setCancelled(true);
         
         Consumer<Player> handler;
         if((handler = gui.events.get(e.getSlot())) != null) {
            handler.accept(p);
         }
      }
   }
}

public class MyGui extends Gui {
   public static final MyGui INSTANCE;
   private static final ItemStack HELLO_ITEM;
   
   static {
      ItemMeta meta;
      
      HELLO_ITEM = new ItemStack(Material.OAK_SIGN);
      meta = HELLO_ITEM.getItemMeta();
      meta.setDisplayName("Hello");
      
      HELLO_ITEM.setItemMeta(meta);
      
      INSTANCE = new MyGui();
   }
   
   private MyGui() {
      super(Bukkit.createInventory(null, 27, "My GUI"));
      
      this.fillBorder(MyItems.BORDER);
      this.inventory.setItem(14, HELLO_ITEM);
      this.registerClickEvent(14, MyGui::sayHello);
   }
   
   private static void sayHello(Player p) {
      p.sendMessage("Hello !");
   }
}

public class MyPlugin extends JavaPlugin implements Listener {
   @Override
   public void onEnable() {
      this.getServer().getPluginManager().registerEvents(this, this);
   }
   
   @EventHandler
   public void onPlayerJoinEvent(PlayerJoinEvent e) {
      MyGui.INSTANCE.open(e.getPlayer());
   }
   
   @EventHandler
   public void onInventoryClickEvent(InventoryClickEvent e) {
      Gui.handleEvent(e);
   }
}
Aucune idée de si ça marche, mais l'idée est là, et il faut aussi que tu l'adaptes à ta sauce.

Cordialement,
ShE3py
 
  • J'aime
Reactions: LaForêt