Preferences - Best Practice

Moin. Ich habe ein größeres Projekt bei dem der Benutzer jede Menge Einstellungen im Programm für seinen Arbeitsplatz setzen kann. Dazu verwende ich bisher Preferences. Die Preferences-API scheint da aber deutlich mehr zu bieten…

Jedenfalls habe ich verschiedene Klassen in verschiedenen Packages, in denen Daten verwendet werden, die ich zum Aufbau eines Dialoges mit Programmeinstellungen für den Benutzer heranziehen möchte.
Es handelt sich hier um Arbeitsverzeichnisse, Farben, Verhalten usw.

Ich bin mir jetzt aber noch nicht im Klaren, wie man das sauber aufbaut.
Darf jede Klasse, die von Einstellungen betroffen ist, ihre Einstellungen lesen und schreiben? Oder sollte das besser durchgereicht bzw. delegiert werden und nur eine Klasse hat den Zugriff auf die Einstellungsdaten?

Ich freue mich auf eure Meinungen.

Würde ich so nicht umsetzen. Angenommen du hast eine Klasse EditorController und es gibt Einstellungen für die präferierte Textfarbe. Dem Controller kann es ja komplett egal sein, woher die Einstellung kommt - seine Zuständigkeit betrifft lediglich den Editor selber. Also bekommt er die Information zu seinen Settings von außen. Die können dann auch sonst woher kommen (Preferences, Server, Vorschau-Werte …). Nehmen wir hier mal das Beispiel Vorschau: es wäre jetzt nicht mehr so einfach, soetwas anzubieten ohne direkt die Einstellungen zu manipulieren.

Desweiteren schaffst du dir noch ein Problem: Deine Klasse wird schwieriger zu testen.

1 „Gefällt mir“

Gut, kannst du mal einen sauberen fiktiven Aufbau bzw. Ablauf schildern?

Hilft der Code?

import java.awt.*;
import java.util.prefs.Preferences;

public class Example {
    public static void main(String[] args) {
        // Dieser Editor wird produktiv verwendet. Er nutzt also die Prefs die vom Nutzer gesetzt worden sind
        MyAwesomeEditorController productionEditor
                = new MyAwesomeEditorController(new EditorPreferencesByPref());

        // Dieser Editor könnte innerhalb eines Wysiwyg-Editors angezeigt werden. Er dient also einfach nur
        // zum testen der Einstellungen.
        MyAwesomeEditorController editorWithinADesigner
                = new MyAwesomeEditorController(new EditorPreferencesByController());

        /**
         * Und für deine Tests musst du lediglich das interface mocken.
         *
         * @Mock
         * private EditorPreferences prefs;
         *
         * when(prefs.allowEmptyMessage()).thenReturn(true);
         */
    }
}

interface EditorPreferences {
    boolean allowEmptyMessage();
}

class MyAwesomeEditorController {
    private String text;
    private final EditorPreferences preferences;

    MyAwesomeEditorController(EditorPreferences preferences) {
        this.preferences = preferences;
    }
    public boolean canSave() {
        boolean canSave = true;

        if(text.isEmpty()) {
            canSave = preferences.allowEmptyMessage();
        }

        return canSave;
    }

}

class EditorPreferencesByController implements EditorPreferences {
    private Checkbox checkbox;

    @Override
    public boolean allowEmptyMessage() {
        return checkbox.getState();
    }
}
class EditorPreferencesByPref implements EditorPreferences {

    @Override
    public boolean allowEmptyMessage() {
        return Preferences.userRoot().node("/my-path").getBoolean("allowEmptyMessage", true);
    }
}

Kaum. Ich kann mir da jetzt keine Anwendung vorstellen.
Zumindest konnte ich die Flexibilität deines Beispiels erkennen. In meinem Programm ist das ziemlich unhandlich.
Was mich aber hauptsächlich interessiert ist: hat man eine Klasse, die lesenden/schreibenden Zugriff auf die Preferences hat, und alle an Einstellungen interessierten Klassen kommunizieren mit dieser?
Wenn eine Klasse mehrere Einstellungen braucht, benutzt man dann bspw. Maps für Zwischenspeichern und Transport oder eher direkt?
Der zündende Funke, der das „Ahaaa“ auslöst kam noch nicht… :wink:

Wenn ich die Frage beantworten wollte, würde ich mal schauen wie das zum Beispiel in Eclipse/Eclipse RCP gemacht wird, dort gibt es im Prinzip das selbe Problem.

Als Einstieg kann man sich zum Beispiel das Tutorial von Lars Vogel anschauen. Da kann man dann auch schon einigermassen rauslesen wie dort vorgegangen wird und welche Mechanismen es gibt. Man muss dazu ja keine Eclipse RCP Anwendung bauen. :wink:

http://www.vogella.com/tutorials/EclipsePreferences/article.html

Netbeans wird das gleiche irgendwo in Swing haben und vielleicht einen etwas anderen Weg gehen.

Im wesentlichen basiert mein Ratschlag auf dem SRP (Single responsibility principle). Überleg dir welche Aufgabe eine Klasse haben soll und belasse es dabei.

Auf das obige Beispiel:
Controller: seine Aufgabe ist die Logik hinter einer UI-Komponente. Seine Aufgabe ist es aber nicht Einstellungen zu lesen. Er muss weder etwas über die Herkunft seiner Einstellungen wissen noch wieviel Pixel ein Button haben soll.

EditorPreferences: Ein Interface hat hier einfach ein paar Vorteile. Dem Editor ist egal woher die Einstellungen kommen - Ihm reichen die Methoden. Und du hast es mit dem testen einfacher (ein Interface kann nicht final sein - was das mocken erleichtert).

Das ist auf einem sehr hohen Abstraktionslevel. Ich antworte mit JEIN. Sowas ergibt sich imho aus dem SRP Gedanken. Angenommen deine Anwendung kennt 2 Module: Auftragserfassung und Kampagnenmanagement. Beide haben Ihre Einstellungen - die gehören aber sicherlich nicht zusammen! In dem Fall würde ich vermutlich 3 Klassen haben:

  • Eine für allgemeine Anwendungseinstellungen
  • Eine für die Auftragserfassung
  • Eine für das Kampagnenmanagement

Es kann durchaus Sinn machen, dass diese sich sogar noch weiter unterteilen. Hängt halt von der jeweiligen Anwendung ab. Ich denke, hier könnte die Baumstruktur aber eine gute Hilfe sein. Damit bildest du ja im besten Fall automatisch Gruppen. Und so ausm Bauch raus würde ich mal Behaupten: Gruppe = Eigene Klasse.

OK, das bringt etwas Licht ins Dunkel. Ich probiere das mal aus.
Danke vorerst. :slight_smile: