Zugriff: von einem Objekt zum Anderen

Status
Nicht offen für weitere Antworten.
B

Beni

Gast
Oft hat man folgendes Problem: Man besitzt eine *Hauptklasse (z.B. ein Fenster) und eine Nebenklasse (z.B. ein 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 gesammten 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...
In den meisten Fällen ist von der statischen Variante abzuraten!

Beispiel:
Das Codeschnippsel zeigt ein Dialog, welcher auf immer dasselbe Frame zugreiffen 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 zugreifft.

Java:
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.
Der 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.
Der Nachteil ist die Unübersichtlichkeit, viele Argumente und Variablen die umherschwirren, und man kommt trotzdem nicht von überall an das Hauptobjekt ran.

Um den Sachverhalt an einem einfachen 1. Beispiel zu erklären, folgender Code:
Ein Hersteller:
Java:
public class Hersteller {
   private Produkt produkt; //ein Erzeugnis
   private String name; //der Herstellername
   [...]

   //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); 
   [...]

   public String getName() {
      return name;
   }
}

Ein Produkt:
Java:
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();
   }
}
Danke für Beispiel an L-ectron-X.

2. Beispiel
Das ist dasselbe 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.

Java:
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 = new Mainclass();
		main.pack();
		main.setLocationByPlatform( true );
		main.setVisible( true );
	}
}

/**
 * Die Hauptklasse
 */
class Mainclass extends JFrame implements ActionListener{
	// Anzahl Mainclasses, welche schon initialisiert wurden
	private static int mainCount = 0;
	
	private static final String OPEN = "open";
	private static final String NEW  = "new";
	
	private int number = 0;
	private JTextField field = new JTextField();
	
	public Mainclass(){
		// Die Nummer dieses Frames speichern
		number = mainCount++;
		
		// Titel setzen, und das Ganze schön aussehen lassen
		setTitle( "Hauptobjekt " + number );
		setDefaultCloseOperation( EXIT_ON_CLOSE );
		
		JButton button = new JButton( "Dialog öffnen" );
		JButton newButton = new JButton( "Neues Fenster" );
		field.setEditable( false );
		field.setText( "Es ist noch nichts geschehen" );
		
		Container content = getContentPane();
		content.setLayout( new GridLayout( 3, 1 ));
		content.add( field );
		content.add( button );
		content.add( newButton );
		
		button.setActionCommand( OPEN );
		newButton.setActionCommand( NEW );
		
		button.addActionListener( this );
		newButton.addActionListener( this );
	}
	
	public void actionPerformed( ActionEvent event ) {
		if( event.getActionCommand().equals( OPEN )){
			// 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 );
		}
		else if( event.getActionCommand().equals( NEW )){
			// Ein neues, unabhängiges Hauptobjekt erstellen
			Mainclass main = new Mainclass();
			main.pack();
			main.setLocationByPlatform( true );
			main.setVisible( true );
		}
	}
	
	public void setText( String text ){
		field.setText( text );
	}
	
	public int getNumber(){
		return number;
	}
}

/**
 * Die Nebenklasse
 */
class Miniclass extends JDialog implements ActionListener{
	private JCheckBox box = new JCheckBox( "Eingabe" );
	private Mainclass owner;             // << wichtig
	
	public Miniclass( Mainclass owner ){
		// Der Konstruktor von JDialog benötigt ein Frame, 
		// also geben wir ihm eines
		super( owner );
		
		// Nun die Referenz auf das Haupobjekt speichern
		this.owner = owner;               // << wichtig
		
		// Den Dialog schön aussehen lassen
		setTitle( "Nebenklasse " + owner.getNumber() );
		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() )
			owner.setText( "Die Box wurde selektiert" );         // << wichtig
		else
			owner.setText( "Der Benutzer hat nichts selektiert" );      // << wichtig
		
		setVisible( false );
	}
}

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 > Dateiwahldialog
oder: Wagen > Fahrgestell > Rad

Wie könnte nun im letzten Beispiel das Rad auf den Wagen zugreiffen? Abgesehen von static, mit der Referenzübergabe kann man dies auf zwei Wege 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.

Java:
public class Wagen{
	public void call(){
		System.out.println( "Ich wurde aufgerufen" );
	}
}
Java:
public class Fahrgestell{
	private Wagen owner;
	
	public Fahrgestell( Wagen owner ){
		this.owner = owner;
	}
	
	public Wagen getWagen(){
		return owner;
	}
}
Java:
public class Rad{
	private Fahrgestell owner;
	
	public Rad( Fahrgestell owner ){
		this.owner = owner;
	}
	
	public void doSomething(){
		owner.getWagen().call();
	}
}

Superobjekt 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 "Superobjekt" (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 vorraus überlegen, welche Klasse man wohin platziert.

Java:
public class Wagen{
	public void call(){
		System.out.println( "Ich wurde aufgerufen" );
	}
}

Java:
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 );
	}
}

Java:
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).
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben