Zugriff: von einem Objekt zum Anderen

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen

Oft hat man folgendes Problem: Man besitzt eine *Hauptklasse (z.B. ein Fenster) und eine Nebenklasse (z.B. einen Dialog). Man will, dass das Hauptobjekt irgendwann dem Nebenobjekt sagen kann "mach dies und jenes". Sobald das Nebenobjekt diesen Befehl ausgeführt hat, soll es das Hauptobjekt davon unterrichten.

Es gibt zwei unterschiedliche Varianten dem Nebenobjekt Zugriff auf das Hauptobjekt zu gewähren:

Variante static

Man kann ein statisches Hauptobjekt benutzen.

Das hat den Vorteil, dass es schnell geschrieben und überall im Programm verfügbar ist.
Der Nachteil ist, dass es im gesamten Programm nur ein einziges mal dieses Hauptobjekt geben kann.
Ist das Hauptobjekt vom Typ "Auto" und das Nebenobjekt vom Typ "Steuerrad", ergibt dies Probleme: es kann schliesslich mehr als nur ein Auto geben...

Es sollte, wo immer es möglich ist, auf eine statische Variante verzichtet werden!

Beispiel:

Das Codeschnippsel zeigt einen Dialog, welcher auf immer das selbe Frame zugreifen kann. Wichtige Zeilen sind 27, wo das Hauptobjekt mit einer statischen Variable gespeichert wird, und die Zeilen 92/94, wo das Nebenobjekt auf das Hauptobjekt zugreift.

import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JTextField;
 
public class Test  {
    public static void main( String[] args ){
        // Hier wird das Hauptfenster sichtbar gemacht
        Mainclass main = Mainclass.MAIN;
        main.pack();
        main.setLocationByPlatform( true );
        main.setVisible( true );
    }
}
 
/**
 * Die Hauptklasse
 */
class Mainclass extends JFrame implements ActionListener{
    // Die statische Instanc der Hauptklasse -> das Hauptobjekt
    public static final Mainclass MAIN = new Mainclass();  // << wichtig
    
    private JTextField field = new JTextField();
    
    public Mainclass(){
        // Titel setzen, und das Ganze schön aussehen lassen
        setTitle( "Hauptobjekt" );
        setDefaultCloseOperation( EXIT_ON_CLOSE );
        
        JButton button = new JButton( "Dialog öffnen" );
        field.setEditable( false );
        field.setText( "Es ist noch nichts geschehen" );
        
        Container content = getContentPane();
        content.setLayout( new GridLayout( 2, 1 ));
        content.add( field );
        content.add( button );
        
        button.addActionListener( this );
    }
    
    public void actionPerformed( ActionEvent event ) {
        // Wenn der Knopf gedrückt wurde, eine neue Instanz eines
        // Nebenobjektes (=Dialog) anlegen, und sichtbar machen
        Miniclass mini = new Miniclass( this );
        mini.pack();
        mini.setLocationRelativeTo( this );
        mini.setVisible( true );
    }
    
    public void setText( String text ){
        field.setText( text );
    }
}
 
/**
 * Die Nebenklasse
 */
class Miniclass extends JDialog implements ActionListener{
    private JCheckBox box = new JCheckBox( "Eingabe" );
    
    public Miniclass( JFrame owner ){
        // Der Konstruktor von JDialog benötigt ein Frame, 
        // also geben wir ihm eines
        super( owner );
        
        // Den Dialog schön aussehen lassen
        setTitle( "Nebenklasse" );
        setDefaultCloseOperation( DO_NOTHING_ON_CLOSE );
        
        JButton button = new JButton( "Dialog schliessen" );
        
        Container content = getContentPane();
        content.setLayout( new GridLayout( 2, 1 ));
        content.add( box );
        content.add( button );
        
        button.addActionListener( this );
    }
    
    public void actionPerformed( ActionEvent event ) {
        // Wenn der Knopf gedrückt wurde, auf das Hauptobjekt zugreiffen, und
        // eine Einstellung des Hauptobjektes verändern
        
        if( box.isSelected() )
            Mainclass.MAIN.setText( "Die Box wurde selektiert" );    // << wichtig
        else
            Mainclass.MAIN.setText( "Der Benutzer hat nichts selektiert" );    // << wichtig
        
        setVisible( false );
    }
}

Variante Referenzübergabe

Bei der Referenzübergabe besitzt das Nebenobjekt einen Konstruktor, der als Argument das Hauptobjekt wünscht. Wenn das Hauptobjekt ein neues Nebenobjekt generiert, übergibt es sich selbst dem Nebenobjekt, welches nun immer weiss, mit wem es kommunizieren kann.

Vorteil: ist die "Nähe" zwischen Haupt- und Nebenobjekt: ihre Beziehung muss einmal modeliert werden, und wenn man danach das ganze Programm umstellt,
diese Beziehung funktioniert immernoch. Auch ist es so möglich mehrere Hauptobjekte zu haben.
Nachteil: ist die Unübersichtlichkeit, viele Argumente und Variablen die umherschwirren, und man kommt trotzdem nicht von überall an das Hauptobjekt ran.

Beispiel 1

Um den Sachverhalt an einem einfachen 1. Beispiel zu erklären, folgender Code:

Ein Hersteller:

public class Hersteller {
   private Produkt produkt; //ein Erzeugnis
   private String name; //der Herstellername
 
   public Hersteller() {
   //neues Produkt anlegen, mit this geben wir einem Produkt-Objekt
   //die Möglichkeit für den Zugriff auf Informationen zu seinem Hersteller
      produkt = new Produkt(this); 
      name = "Ein toller Hersteller"; //Herstellername
   }
 
   public String getName() {
      return name;
   }
}

Ein Produkt:

public class Produkt {
   private Hersteller hersteller; //eine Referenz auf das Hersteller-Objekt
 
   public Produkt(Hersteller hersteller) {
      this.hersteller = hersteller;
   }
 
   //diese Methode kann sowohl von "außen" als auch von der Klasse selbst
   //dazu benutzt werden, um den Hersteller-Namen zurückzugeben.
   public String getHerstellerName() {
      return hersteller.getName();
   }
}

Beispiel 2

Das ist das selbe Beispiel wie in Variante static, nur wird der Miniclass eine Referenz auf die Mainclass übergeben. Es ist auch zu sehen, dass man nun mehrere Hauptobjekte generieren kann. Die wichtigen Unterschiede sind in Zeile 101, und 122/124. In Zeile 101 wird nun die Referenz auf ein Mainclass gespeichert, und in den Zeilen 122/124 wird auf eben diese Referenz zugegriffen.

  1 import java.awt.Container;
  2 import java.awt.GridLayout;
  3 import java.awt.event.ActionEvent;
  4 import java.awt.event.ActionListener;
  5  
  6 import javax.swing.JButton;
  7 import javax.swing.JCheckBox;
  8 import javax.swing.JDialog;
  9 import javax.swing.JFrame;
 10 import javax.swing.JTextField;
 11  
 12 public class Test  {
 13     public static void main( String[] args ){
 14         // Hier wird das Hauptfenster sichtbar gemacht
 15         Mainclass main = new Mainclass();
 16         main.pack();
 17         main.setLocationByPlatform( true );
 18         main.setVisible( true );
 19     }
 20 }
 21  
 22 /**
 23  * Die Hauptklasse
 24  */
 25 class Mainclass extends JFrame implements ActionListener{
 26     // Anzahl Mainclasses, welche schon initialisiert wurden
 27     private static int mainCount = 0;
 28     
 29     private static final String OPEN = "open";
 30     private static final String NEW  = "new";
 31     
 32     private int number = 0;
 33     private JTextField field = new JTextField();
 34     
 35     public Mainclass(){
 36         // Die Nummer dieses Frames speichern
 37         number = mainCount++;
 38         
 39         // Titel setzen, und das Ganze schön aussehen lassen
 40         setTitle( "Hauptobjekt " + number );
 41         setDefaultCloseOperation( EXIT_ON_CLOSE );
 42         
 43         JButton button = new JButton( "Dialog öffnen" );
 44         JButton newButton = new JButton( "Neues Fenster" );
 45         field.setEditable( false );
 46         field.setText( "Es ist noch nichts geschehen" );
 47         
 48         Container content = getContentPane();
 49         content.setLayout( new GridLayout( 3, 1 ));
 50         content.add( field );
 51         content.add( button );
 52         content.add( newButton );
 53         
 54         button.setActionCommand( OPEN );
 55         newButton.setActionCommand( NEW );
 56         
 57         button.addActionListener( this );
 58         newButton.addActionListener( this );
 59     }
 60     
 61     public void actionPerformed( ActionEvent event ) {
 62         if( event.getActionCommand().equals( OPEN )){
 63             // Wenn der Knopf gedrückt wurde, eine neue Instanz eines
 64             // Nebenobjektes (=Dialog) anlegen, und sichtbar machen
 65             Miniclass mini = new Miniclass( this );
 66             mini.pack();
 67             mini.setLocationRelativeTo( this );
 68             mini.setVisible( true );
 69         }
 70         else if( event.getActionCommand().equals( NEW )){
 71             // Ein neues, unabhängiges Hauptobjekt erstellen
 72             Mainclass main = new Mainclass();
 73             main.pack();
 74             main.setLocationByPlatform( true );
 75             main.setVisible( true );
 76         }
 77     }
 78     
 79     public void setText( String text ){
 80         field.setText( text );
 81     }
 82     
 83     public int getNumber(){
 84         return number;
 85     }
 86 }
 87  
 88 /**
 89  * Die Nebenklasse
 90  */
 91 class Miniclass extends JDialog implements ActionListener{
 92     private JCheckBox box = new JCheckBox( "Eingabe" );
 93     private Mainclass owner;             // << wichtig
 94     
 95     public Miniclass( Mainclass owner ){
 96         // Der Konstruktor von JDialog benötigt ein Frame, 
 97         // also geben wir ihm eines
 98         super( owner );
 99         
100         // Nun die Referenz auf das Haupobjekt speichern
101         this.owner = owner;               // << wichtig
102         
103         // Den Dialog schön aussehen lassen
104         setTitle( "Nebenklasse " + owner.getNumber() );
105         setDefaultCloseOperation( DO_NOTHING_ON_CLOSE );
106         
107         JButton button = new JButton( "Dialog schliessen" );
108         
109         Container content = getContentPane();
110         content.setLayout( new GridLayout( 2, 1 ));
111         content.add( box );
112         content.add( button );
113         
114         button.addActionListener( this );
115     }
116     
117     public void actionPerformed( ActionEvent event ) {
118         // Wenn der Knopf gedrückt wurde, auf das Hauptobjekt zugreiffen, und
119         // eine Einstellung des Hauptobjektes verändern
120         
121         if( box.isSelected() )
122             owner.setText( "Die Box wurde selektiert" );         // << wichtig
123         else
124             owner.setText( "Der Benutzer hat nichts selektiert" );      // << wichtig
125         
126         setVisible( false );
127     }
128 }

Mehrere Ebenen

Bisher hatten wir nur zwei Ebenen: Haupt- und Nebenobjekt. Aber das Nebenobjekt könnte ja wiederrum das Hauptobjekt im Vergleich zu einem anderen Objekt sein. Z.B.: Hauptfenster > Neue-Datei-Dialog > Dateiauswahl-Dialog oder: Wagen > Fahrgestell > Rad

Wie könnte nun im letzten Beispiel das Rad auf den Wagen zugreifen? Abgesehen von static, mit der Referenzübergabe kann man dies auf zwei Wegen lösen:

Durchhangeln Dabei macht man einfach in der Klasse "Fahrgestell" eine Methode "getWagen", welche den Wagen zurückgibt, zu dem das Fahrgestell gehört.

Vorteil: die Objekte sind "nahe", die Änderung an einem Teil des Programmes hat keine Auswirkungen auf den Rest.
Auch kann z.B. das Fahrgestell sehr einfach einem anderen Wagen einbauen (weil keine Variablen in Nebenobjekten von Fahrgestell verändert werden müssen).
Nachteil: ist wiederum eine gewisse Unübersichtlichkeit: viele Variablen, aber bis man z.B. von Rad nach Wagen kommt, muss man dennoch viel schreiben.
public class Wagen{
    public void call(){
        System.out.println( "Ich wurde aufgerufen" );
    }
}
public class Fahrgestell{
    private Wagen owner;
    
    public Fahrgestell( Wagen owner ){
        this.owner = owner;
    }
    
    public Wagen getWagen(){
        return owner;
    }
}
public class Rad{
    private Fahrgestell owner;
    
    public Rad( Fahrgestell owner ){
        this.owner = owner;
    }
    
    public void doSomething(){
        owner.getWagen().call();
    }
}

Super-Objekt durchreichen

Die andere Variante: man macht in Rad zwei Variablen: eine auf das "relative Hauptobjekt", und eine auf das "allumfassende Hauptobjekt". In diesem Fall wird beim Herstellen eines Rades der Wagen ebenfalls übergeben.

Vorteil: ist der "schnelle Zugriffsweg" von Rad zu Wagen. Besitzt man ein "Super-Objekt" (ein Hauptobjekt zu dem alle anderen Objekte "niedriger" sind),
so kann man mit Durchreichen jedem Objekt vollen Zugriff auf das Programm (und zwar auf jedes Detail) gewähren, ohne die Nachteile von static mitzuschleppen.
Nachteil: ist die Stabilität. Hat man solch eine Struktur erstmals aufgebaut, kann man sie fast nicht mehr auflösen (sowohl im Code, als auch während der Laufzeit),
man muss sich im Voraus überlegen, welche Klasse man wohin platziert.
public class Wagen{
    public void call(){
        System.out.println( "Ich wurde aufgerufen" );
    }
}
public class Fahrgestell{
    private Wagen owner;
    
    public Fahrgestell( Wagen owner ){
        this.owner = owner;
    }
    
    public Wagen getWagen(){
        return owner;
    }
    
    public void newRad(){
        Rad rad = new Rad( owner, this );
    }
}
public class Rad{
    private Fahrgestell owner;
    private Wagen wagen;
    
    public Rad( Wagen wagen, Fahrgestell owner ){
        this.wagen = wagen;
        this.owner = owner;
    }
    
    public void doSomething(){
        wagen.call();
    }
}
  • Klassen und Objekte sind nicht dasselbe: eine Klasse ist der Bauplan eines Objektes (auch Instanz genannt).


Fragen

Das Thema wurde nicht ausreichend behandelt? Du hast Fragen dazu und brauchst weitere Informationen?

Besuche uns im Byte-Welt-Forum, wir helfen Dir gerne!


Dir hat dieser Artikel gefallen? Schreibe einen Kommentar

Du musst angemeldet sein, um einen Kommentar abzugeben.


--Beni | L-ectron-X 16.03.2005, 08:46 CET