Best Practise: Create + Edit-Dialog

In meiner Anwendung verwalte ich Daten in einer Tabelle. Über einen Create-Button kann man einen neuen Datensatz hinzufügen (Create Dialog). Klickt man einen Datensatz in der Tabelle an, so öffnet sich ein Edit-Dialog. Soweit die Theorie.

Ich bin gerade ein wenig hin- und hergerissen, wie ich die beiden Dialoge implementiere:

Auf der einen Seite würde ich gerne für beide Dialoge die selbe Klasse verwenden, da ich die Daten-Felder für erzeugen und ändern ja eigentlich kaum voneinander unterscheiden. Zudem fühlt es sich irgendwie falsch an, zwei Klassen zu bauen, die zu gefühlten 80% aus dem selben Code bestehen.

Auf der anderen Seite gibt es eben auch Unterschiede (bspw. gibt es beim Edit-Dialog einen “Löschen” Button"), die, wenn ich nur eine Klasse verwenden, mit umständlichen if-else-Kosntrukten und einen zusätzlichen Parameter (boolean isEdit) gelöst werden könnten.

Als drittes könnte man natürlich einen Abstrakten Dialog machen, der alle gemeinsamen Felder beinhaltet, und dann jeweils ein Edit- und Create-Dialog davon ableiten. Aber drei Dialog-Klassen für einen Datentyp zu erstellen scheint mir irgendwie auch ein wenig viel.

Meine Frage: Wie macht ihr das? Gibt es eine “Best Practise”, wie man mit sich unterscheidenden Create- und Edit-Dialogen (teilw. andere Felder, Buttons) für den gleichen Datensatz umgeht, wenn ansonsten doch recht viel gleich ist!?

Wenn beide Dialoge die selbe Information auf die selbe Art darstellen, dann macht es keinen Sinn zwei verschiedene Dialoge zu definieren.
Letztendlich hängt es davon ab, was und wie groß die Unterschiede sind.

Ist das der einzige Unterschied? …und ob da überhaupt ein if notwendig ist…

Übergib dem Dialog im Konstruktor doch noch einen Hinweis (boolean, String, enum…) mit, ob es sich um einen Create- oder Edit-Dialog handelt. Je nachdem kannst du deinen Dialog gestalten, die Buttons anzeigen und drauf reagieren.

EDIT: Uuups, das stand ja schon da oben. Ich würde das aber wohl dann so machen, bevor ich da zwei Klassen erstelle, die ja fast identisch sind.

Der Create-Dialog hat bspw. ein “Import aus Datei”-Feld.
Der Edit-Dialg hat ein “Export in Datei”-Feld und einen Löschen-Button. Zudem unterscheiden sich die Dialoge natürlich noch im Header-Text und Icon.
Das sind die aktuellen Unterschiede. Alle andern Felder sind (erstmal) gleich.

Ich hätte jetzt geschrieben


  public MyDialog(boolean isEdit){ //meinetwegen auch nen enum
      // allgemeine Felder hinzufügen

      if(isEdit){ /*Spezielle Edit-Felder hinzufügen */}
      else{ /* Spezielle Create-Felder hinzufügen */ }
      
      // allgemeine Buttons hinzufügen
      if(isEdit){ /*Spezielle Edit-Buttons hinzufügen */}
      else{ /* Spezielle Create-Buttons hinzufügen */ }
  }
}```Wie soll das ohne "if" sonst gehen!?

Einen eigenen Dialog fuer Create, und einen fuer Edit.
Ist IME bei komplexeren Projekten unausweichlich.

Hm… das wäre dann der Punkt, an dem mir Argumente für die jeweilgen Positionen helfen würden, das für mich abzuwägen. :wink:

Ich würde das nicht als verschiedene Positionen sehen. Je größer die Unterschiede der Dialog in Darstellung und Zustand, desto sinnvoller ist die separate Implementierung.

Bei Deinem Beispiel „Datensatz“ einer Tabelle bei dem in beiden Dialogen die selben Felder angezeigt und editiert werden können wäre mir das zu blöd. (Wobei ich persönlich das Editieren in der Tabelle zulassen und nur für das Erzeugen neuer Datensätze evtl. einen Dialog verwenden würde)

Mal ein vielleicht etwas künstliches Beispiel ohne if ( gut ein bisschen gemogelt :wink: aber dafür nutze ich kein boolean zur Steuerung ) JFileChooser nutzt für Öffnen und Speichern ebenfalls einen gemeinsamen Dialog - verwendet aber ifs :slight_smile:

import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;

public class SingleDialogDemo {
	public static void main(String[] s) throws InterruptedException {
		MyDialog dialog = new MyDialog();
		dialog.setLocation(50, 150);
		dialog.showDialog(MyDialog.DialogType.CREATE);

		dialog = new MyDialog();
		dialog.setLocation(150, 50);
		dialog.showDialog(MyDialog.DialogType.EDIT);
	}
}

class MyDialog extends JDialog {

	public enum DialogType {
		EDIT, CREATE;
	}

	private JButton okButton, delButton;
	private String[] value = new String[] { "Speichern", "Erzeugen" };

	public MyDialog() {
		JPanel panel = new JPanel(new GridLayout(1, 3));
		panel.add(delButton = new JButton("Löschen"));
		panel.add(okButton = new JButton());
		panel.add(new JButton("Abbrechen"));
		add(panel, BorderLayout.SOUTH);
		setSize(300, 150);
	}

	public void showDialog(DialogType type) {
		okButton.setText(value[type.ordinal()]);
		setTitle(value[type.ordinal()]);
		delButton.setVisible(type == DialogType.EDIT);
		setVisible(true);
	}
}```

Hm… die Idee hat was.

Das heißt ich schreibe mir ne kleine Klasse “DialogVariation”:

   private String title;
   private Icon icon;

   public DialogVariation(String title, Icon icon){
    this.title=title; this.icon = icon;
   }

   public void apply(D dialog){
    dialog.setTitle(this.title);
    dialog.setIcon(this.icon);
    applyAdditionalButtons(dialog);
    applyAdditionalFields(dialog);

   protected abstract void applyAdditionalButtons(D dialog);

   protected abstract void applyAdditionalFields(D dialog);
}

Die ich im Dialog einfach entsprechend anwende:

  public enum Mode{CREATE, EDIT;}

  private static DialogVariation<MyDialog> CREATE= new DialogVariation("Create Entity", IconHandler.CREATE){
     public void applyAdditionalButtons(MyDialog dialog){}
     public void applyAdditionalFields(MyDialog dialog){
       // CREATE-Felder hinzufügen
     }
  }

  private static DialogVariation<MyDialog> EDIT= new DialogVariation("EDIT Entity", IconHandler.EDIT){
     public void applyAdditionalButtons(MyDialog dialog){
        // Add delete button
     }
     public void applyAdditionalFields(MyDialog dialog){
       // EDIT-Felder hinzufügen
     }
  }

  public void show(Mode mode){
   switch(mode){
    case CREATE: CREATE.apply(this); break;
    case EDIT : EDIT.apply(this); break;
  }
}```Dann hab ich zwar auch nen "if" drinne (in form eines switch-case), aber zumindest nur eines. Spricht irgendwas gegen diese Lösung? Sonst werde ich das so machen.

Dann haste ja aber auch 2 Klassen…

Ja, aber die DialogVaration kann ich generisch machen, und dann für alle Meine Dialoge nutzen. Hab ja nicht nur eine Tabelle… :smiley:

Mir kommt da gerade JOptionPane in den Sinn. Warum nicht eine von mir aus generische Dialog-Klasse implementieren, die konfigurierbar ist und über spezielle Methoden erzeugen lassen, die einen richtig konfiguerierten Dialog liefern und alles intern kapseln? Muss man zwar nicht so machen, aber es wäre ne Möglichkeit, denn so sehr unterscheiden sich Create- und Edit-Dialoge in der Regel nicht. Ich verwende meist sogar den selben Dialog (halt leicht konfigurierbar für kleinere Unterschiede), aber beide sind gleich. Der Hauptunterschied ist meist eigentlich nur, dass der Create-Dialog mit einem leeren Entity-Objekt erzeugt wird während ein Edit-Dialog eines bekommt, um die Felder zu füllen und dieses zu bearbeiten und beide Methoden liefern nach dem Schließen des Dialoges dann das Entity-Objekt, mit dem man anschließend sofort weiterarbeiten kann.

Ein Löschen-Button gehört meines Erachtens nicht in so einen Edit-Dialog, sondern als gesonderter Button zu der Tabelle, ebenso auch ein Button zum Bearbeiten, da man nicht davon ausgehen kann, dass jeder auf die Idee kommt einen Datensatz zu doppelklicken. Löschen zudem deshalb, weil ich so mehrere Datensätze löschen könnte anstatt immer jeden Datensatz einzeln im Bearbeitungs-Dialog zu öffnen und dann endlich löschen zu dürfen. Zu umständlich und nervig. Import- und Export-Funktionalitäten würde ich auch nicht an die Dialoge kleben, da das eine mit dem anderen nichts zu tun hat. Würde ich ebenfalls als Buttons zu der Tabelle hinzufügen und aus den Dialogen schmeißen. Vor allem, weil die Dialoge dann die Logik für Import und Export haben müssten und das ist in meinen Augen nicht die Kernaufgabe der Dialoge. KISS eben.

Und ich könnte mir auch vorstellen, dass es viele Nutzer verwirren könnte, wenn die Buttons mit Grundfunktionalitäten, die man irgendwo in der Nähe der Tabelle erwarten würde, sonst wo suchen muss. Usability gleich null.

Du machst dir für deinen Datensatz eine View (am besten ein JPanel mit allen Eingabefeldern). Für deine Dialoge kannst du dann die Buttons darunter setzten die die brauchst.

was haltet Ihr den
davon
[spoiler]```class MyDialog extends JDialog {
public enum DialogType {
EDIT {
protected void setTitle(MyDialog myDialog) {
myDialog.setTitle(“Datensatz bearbeiten”);
}

		@Override
		public List<JButton> getTypeButtons(final MyDialog myDialog) {
			return Arrays.asList(//
					new JButton(new AbstractAction("Speichern") {
						@Override
						public void actionPerformed(ActionEvent e) {
							myDialog.updateRecord();
						}
					}), new JButton(new AbstractAction("Löschen") {
						@Override
						public void actionPerformed(ActionEvent arg0) {
							myDialog.deleteRecord();
						}
					}));
		}
	},
	CREATE {
		@Override
		protected void setTitle(MyDialog myDialog) {
			myDialog.setTitle("neuer Datensatz");
		}

		@Override
		public List<JButton> getTypeButtons(final MyDialog myDialog) {
			return Arrays.asList(//
					new JButton(new AbstractAction("Erzeugen") {
						@Override
						public void actionPerformed(ActionEvent e) {
							myDialog.insertRecord();
						}
					}));
		}
	};

	protected abstract void setTitle(MyDialog myDialog);

	public abstract List<JButton> getTypeButtons(MyDialog myDialog);

	public List<JComponent> getButtons(final MyDialog myDialog) {
		ArrayList<JComponent> buttonList = new ArrayList<JComponent>(
				getTypeButtons(myDialog));
		buttonList.add(//
				new JButton(new AbstractAction("Abbrechen") {
					@Override
					public void actionPerformed(ActionEvent e) {
						myDialog.setVisible(false);
					}
				}));
		return buttonList;
	}
}

public MyDialog(DialogType type) {
	JPanel buttons = new JPanel(new GridLayout(1, 0, 5, 5));
	for (JComponent button : type.getButtons(this)) {
		buttons.add(button);
	}
	JPanel bottom = new JPanel(new BorderLayout());
	bottom.add(buttons, BorderLayout.EAST);
	add(bottom, BorderLayout.SOUTH);
	setSize(500, 150);
}

protected void deleteRecord() {
	JOptionPane.showMessageDialog(this, "Datensatz gelöscht");
	MyDialog.this.setVisible(false);
}

protected void updateRecord() {
	JOptionPane.showMessageDialog(this, "Datensatz geändet");
	MyDialog.this.setVisible(false);
}

protected void insertRecord() {
	JOptionPane.showMessageDialog(this, "Datensatz angelegt");
	MyDialog.this.setVisible(false);
}

}```[/spoiler]
?

bye
TT

[QUOTE=Akeshihiro]Mir kommt da gerade JOptionPane in den Sinn. Warum nicht eine von mir aus generische Dialog-Klasse implementieren, die konfigurierbar ist und über spezielle Methoden erzeugen lassen, die einen richtig konfiguerierten Dialog liefern und alles intern kapseln? [/QUOTE]Na das ist doch meine Frage: Wie konfiguriere ich so einen Dialog am besten.

[QUOTE=Akeshihiro;58854]Ein Löschen-Button gehört meines Erachtens nicht in so einen Edit-Dialog, sondern als gesonderter Button zu der Tabelle, ebenso auch ein Button zum Bearbeiten, da man nicht davon ausgehen kann, dass jeder auf die Idee kommt einen Datensatz zu doppelklicken. Löschen zudem deshalb, weil ich so mehrere Datensätze löschen könnte anstatt immer jeden Datensatz einzeln im Bearbeitungs-Dialog zu öffnen und dann endlich löschen zu dürfen. Zu umständlich und nervig. Import- und Export-Funktionalitäten würde ich auch nicht an die Dialoge kleben, da das eine mit dem anderen nichts zu tun hat. Würde ich ebenfalls als Buttons zu der Tabelle hinzufügen und aus den Dialogen schmeißen. Vor allem, weil die Dialoge dann die Logik für Import und Export haben müssten und das ist in meinen Augen nicht die Kernaufgabe der Dialoge. KISS eben.

Und ich könnte mir auch vorstellen, dass es viele Nutzer verwirren könnte, wenn die Buttons mit Grundfunktionalitäten, die man irgendwo in der Nähe der Tabelle erwarten würde, sonst wo suchen muss. Usability gleich null.[/QUOTE]Da setzt du aber gerade ne ganze Menge vorraus, wie meine Anwendung **möglicherweise **aufgebaut sein könnte, was hier aber auch gar nicht zur Diskussion steht. Und das Löschen eines Datensatzes bei der Bearbeitung mit anzubieten scheint mir gar nicht mal so weit hergeholt. Und falls es sich anbietet, werde ich bestimmte Dinge auch aus dem Dialog nehmen. Bin auch für eine hohe Usability.
Allerdings wären das alles Dinge, die größere Umbaumaßnahmen erfordern würden. Daher werden die Dialoge und ihre Funktionalität wohl erstmal so bleiben. Ich möchte hier auch ungern darüber diskutiueren, ob mein Problem sinnvoll ist oder nicht. Aktuell besteht es, und ich versuche es daher bestmöglich zu lösen (und bin dabei für jede Hilfe dankbar). :wink:

[QUOTE=Timothy_Truckle;58918]was haltet Ihr den
davon
[spoiler]```class MyDialog extends JDialog {
public enum DialogType {
EDIT {
protected void setTitle(MyDialog myDialog) {
myDialog.setTitle(„Datensatz bearbeiten“);
}

		@Override
		public List<JButton> getTypeButtons(final MyDialog myDialog) {
			return Arrays.asList(//
					new JButton(new AbstractAction("Speichern") {
						@Override
						public void actionPerformed(ActionEvent e) {
							myDialog.updateRecord();
						}
					}), new JButton(new AbstractAction("Löschen") {
						@Override
						public void actionPerformed(ActionEvent arg0) {
							myDialog.deleteRecord();
						}
					}));
		}
	},
	CREATE {
		@Override
		protected void setTitle(MyDialog myDialog) {
			myDialog.setTitle("neuer Datensatz");
		}

		@Override
		public List<JButton> getTypeButtons(final MyDialog myDialog) {
			return Arrays.asList(//
					new JButton(new AbstractAction("Erzeugen") {
						@Override
						public void actionPerformed(ActionEvent e) {
							myDialog.insertRecord();
						}
					}));
		}
	};

	protected abstract void setTitle(MyDialog myDialog);

	public abstract List<JButton> getTypeButtons(MyDialog myDialog);

	public List<JComponent> getButtons(final MyDialog myDialog) {
		ArrayList<JComponent> buttonList = new ArrayList<JComponent>(
				getTypeButtons(myDialog));
		buttonList.add(//
				new JButton(new AbstractAction("Abbrechen") {
					@Override
					public void actionPerformed(ActionEvent e) {
						myDialog.setVisible(false);
					}
				}));
		return buttonList;
	}
}

public MyDialog(DialogType type) {
	JPanel buttons = new JPanel(new GridLayout(1, 0, 5, 5));
	for (JComponent button : type.getButtons(this)) {
		buttons.add(button);
	}
	JPanel bottom = new JPanel(new BorderLayout());
	bottom.add(buttons, BorderLayout.EAST);
	add(bottom, BorderLayout.SOUTH);
	setSize(500, 150);
}

protected void deleteRecord() {
	JOptionPane.showMessageDialog(this, "Datensatz gelöscht");
	MyDialog.this.setVisible(false);
}

protected void updateRecord() {
	JOptionPane.showMessageDialog(this, "Datensatz geändet");
	MyDialog.this.setVisible(false);
}

protected void insertRecord() {
	JOptionPane.showMessageDialog(this, "Datensatz angelegt");
	MyDialog.this.setVisible(false);
}

}```[/spoiler]
?

bye
TT[/QUOTE]Ja, die Idee ist gut. Allerdings habe ich nicht nur den einen Dialog, sondern auch noch weitere 5 (oder mehr), für die ich dann ja jeweils ein Enum anlegen müsste!?

Allerdings erscheint mir die Idee, den Unterschied in eine Klasse oder Methode auszulagern schon sinnvoll. Die Frage ist eben noch, wie ich das am besten für viele Dialoge realisiere.

Mein aktueller Ansatz wäre, für jeden Dialog statisch eine Map<Type, DialogVariation> anzulegen, die ich dann mit den entsprechenden Instanzen fülle. Allerdings müsste ich diese Map dann für jede Subklasse anlegen, was mir auch ein wenig redundant erscheint (siehe dazu Thread hier).