Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Hallo,
In verschiedenen Threats hir habt ihr mir empfohlen meine Dateien in verschiedene Packages zu verteilen, also nicht mehr alle in Ordner/Packet x/ sondern in den Ordner/Packeten x/a/, x/b/, x/c/...
Nun wollte ich dies bei mir tun, aber:
Um von einer Classe in x/a/... auf eine Variable in x/Classe.java zuzugreifen muss diese als public deffiniert sein, jedoch habt ihr mir auch in zahlreiche Threats empfohlen nicht alle Variablen mehr oder wahllos als public zu deklarieren!
Hi,
warum musst du denn direkt auf eine Variable zugreifen? Ist hier wirklich eine ernst gemeinte Frage.
Eine Klasse sollte immer eine gewissen zusammen gehörige Funktionalität kapseln. Dass heißt wenn du eine Klasse X hast, dann bietet diese die Methoden a, b und c an. Die machen alle etwas ganz bestimmtes. Dazu greifen die vielleicht auf andere Klassen und garantiert auf private Variablen zu. Ein direkter Zugriff auf Variablen ist also nie nötig.
Gut sehen kannst du es, wenn du Interfaces verwendest. Diese sind sehr abstrakt, hier stehen nur die öffentlichen (also von aussen sichtbaren) Methoden mit Rückgabewert und Parameter drin. Das reicht aus (ist sogar zu empfehlen) um auf dieser Basis sehr komplexe Programme zu erstellen. Du modellierst dein eigentliches Programm so abstrakt wie möglich (also z.B. mit Interfaces). Im Programm wird dann zwar mit konkreten Instanzen gearbeitet, da diese aber das entsprechende Interface implentieren wären all diese Instanzen einfach austauschbar. Die eigentliche Implementierung kann dir dort also egal sein. Der klare Vorteil ist, du musst gar nicht wissen wie die Variablen solcher Klassen aussehen.
Warum also willst du direkt auf Variablen zugreifen? Das einzigste wo so etwas sinnvoll ist, ist imho es bei konstanten zu tun, diese sind dann aber eben auch nicht variabel und hätten die Form:
Code:
public static final int EINE_BSP_KONSTANTE = 1;
public static final int EINE_ANDERE = 2;
Nun, ich wollte die ActionListener ausgliederen in ein Packet 'Listeners'. Und da muss ich nun mahl auf die Variablen des Programms zugreifen und diese verändern...
Ich verstehe allerdings ehrlich gesagt deine Lösung nicht ganz... Irgentwie scheine ich zu blöd zu sein um zu verstehen was es mit den Interface auf sich hat!
Ein package x gibt es zwar in deinem Beispiel nicht (also auch keine Klasse "x/Classe.java"), aber dein Problem hat ja auch nichts mit packages zu tun, sondern einfach mit der Ausgliederung einer Klasse, das könnte ebensogut in demselben package geschehen. Um die Variablen einer anderen Klasse anzuzapfen kannst die sie im Konstruktor übergeben oder mit public get../set.. Methoden.
Hm... Um mich noch mahl genauer aus zu drücken:
ich hab ein Packet x
mit der Klasse y in der Datei y, welche die Main-Methode und ein ganze reihe von Variablen enthält.
Und eine Klasse z in der Datei z, welches ein Actionlistener ist.
Nun will ich die Datei z in ein extra Packet nammens 'Listeners' verschieben.
In z wird an vielen Stellen aut die Variablen von der Klasse y zugegriffen.
Eine Ubergebung der Dateien mitels Konstruktor geht also schonmahl nicht! Und wenn ich für jede Variable eine get und eine set methode deffiniere bläht das mein (ohnehin schon viel zu große) Klasse z extrem auf....
Da ist halt die Frage, ob man wirklich die ActionListener auslagern sollte. Ein Alternatives Design ist es doch, dass du nicht den Listener selbst auslagerst, sondern die Logik des Listeners (also was genau gemacht wird wenn das Ereignis auftritt).
Der Teil mit den Interfaces ist auch etwas weniger kompletter Anfänger. Aber es bietet sich halt dieses Design so früh wie möglich an und ist gar nicht weiter kompliziert. Was eine Klasse ist weißt du ja sicherlich schon. Ich weiß nicht wie es dann um dein Wissen um Vererbung aussieht? Oder abstrakte Klassen?
Ein Interface ist jedenfalls komplett abstrakt. Alles was da drin stehen darf sind Methoden. Die Sichtbarkeit ist dabei automatisch auf public festgelegt. Keine der Methoden wird aber in einem Interface implementiert, du gibst so zu sagen nur die Signatur bekannt.
Ein sehr einfaches Beispiel wäre ein Stack. Werde den mal als Stapel bezeichnen um evtl. Namensüberschneidungen zu vermeiden (einen Stack gibt ja in Java schon). Die Eigenschaften eines Stack sind sehr einfach, ich kann ein Element mit push auf den Stapel legen und mit pop das oberste zurück holen. Sagen wir ich möchte noch wissen wie voll der Stack ist.
Diese drei Dinge wären die volle Funktionalität eines Stack. Dazu muss ich aber nicht Wissen ob die Elemente in einem Array gespeichert werden oder in einer Liste, in einem Baum oder sonst wie.
Ein Interface würde hier wie folgt aussehen:
Code:
public interface IStapel<T> {
void push(T t);
T pop();
int getCount();
}
IStapel ist jetzt so ein abstraktes Interface. Du weißt, dass alle IStapel (also "Nachfolger") diese öffentlichen Methoden haben. Ein solcher "Nachfolger" muss diese Schnittstelle implementieren. Wie du siehst steht im Interface noch kein Code, was z.B. push macht. Zwei mögliche Implementierungen könnten so aussehen:
Code:
class StapelArray<T> implements IStapel<T> {
private T[] objects;
private void vergroesserArray() {
// irgendwas
}
private void verkleinererArray() {
// irgendwas
}
public void push(T t) {
// schlechte Implementierung, aber hier egal
this.vergroesserArray();
this.objects[this.objects.length - 1] = t;
}
public T pop() {
if (this.getCount() < 1) {
return null;
}
T result = this.objects[this.objects.length - 1];
this.verkleinererArray();
return result;
}
public int getCount() {
return this.objects.length;
}
}
class StapelVector<T> implements IStapel<T> {
private Vector<T> vector = new Vector<T>();
public void push(T t) {
this.vector.add(t);
}
public T pop() {
if (this.getCount() < 1) {
return null;
}
T result = this.vector.lastElement();
this.vector.remove(this.vector.lastElement());
return result;
}
public int getCount() {
return this.vector.size();
}
}
Hier hättest du jetzt ein einfaches Beispiel für zwei sehr verschiedene Implementierungen. Bei dem einen wird für den Stapel ein Array verwendet. Dieses muss in der Größe angepasst werden, wenn du ein Element hinzufügst oder entfernst. Bei der anderen Implementierung wird ein Vector verwendet (der kümmert sich selbst um die Größe).
Für den Stapel ist das jedoch unwichtig. Du möchtest nur ein Element mit push oben rauf legen und mit pop entfernen, mit getCount() die Größe bekommen.
In deinem Programm kannst du jetzt mit Variablen vom Typ IStapel arbeiten. Was für eine Implementierung dahinter steckt ist dir egal
Code:
public void foo(final IStapel<Integer> stapel) {
// tu irgenwas mit dem Stapel
stapel.push(1);
stapel.push(2);
stapel.push(new Integer(10283));
stapel.pop();
...
}
// eine Main zum Testen:
public static void main(String[] args) {
IStapel<Integer> stapel1 = new StapelArray<Integer>();
IStapel<Integer> stapel2 = new StapelVector<Integer>();
foo(stapel1);
foo(stapel2);
}
Jetzt könntest du leicht die Implementierung des Stapels austauschen und foo würde immer funktionieren. Wie dein Stapel intern arbeitet interessiert dich einfach mal garnicht. Ok, beim Stapel gibt es sicherlich nur begrenzt viele sinnvolle Möglichkeiten. Aber Interfaces kannst du für jedes Programm verwenden. java.util.List<T> ist zum Beispiel ein sehr schönes Interface. Es kann von dir überall dort verwendet werden, wo du die Eigenschaften einer Liste benötigst. Ob die Implementierung die du verwendest dann mit einer verketteten Liste, einem dynamischen Array (etwas effizienter gemacht als hier) oder vielleicht mittels Hashes arbeitet kann dir egal sein. Das Interface sichert dir nur zu, dass du die Schnittstelle (die öffentlichen Methoden) kennst. Natürlich kann eine Klasse die ein Interface implementiert mehr öffentliche Methoden als die des Interface haben, aber eben nie weniger.
Ich hoffe der Vorteil ist grob klar (wahrscheinlich erklärt dir jedes Javabuch das deutlich besser als ich).
Mit dieser Abstraktion gewinnst du jedenfalls eine Menge. Du hast ein sehr Abstraktes Design, dass heißt die Wahl der Klassen die du einsetzt ist recht wenig eingeschränkt. Du kannst so in Anwendungen leicht neue bessere und perfomantere Klassen einsetzen und änderst nur an einer Stelle etwas (den Aufruf des Konstruktors). Die ganze Logik bleibt erhalten. Zudem siehst du hier, dass du dir um Variablen gar keine Kopf machen brauchst. Z.B. speichern beide Implemeniterungen Elemente in einer Datenstruktur, das sollen sie ja auch. Aber sie verwalten diese Datenstruktur (die zu ihnen gehört). Von aussen möchtest du dich darum gar nicht kümmern. Du schmeist einen Wert in die Schwarze Kiste IStapel und er landet auf einem Stapel. Du drückst auf der Kiste den Knopf pop und das oberste Element kommt raus (null wenn Stapel leer ist).
Ein weiterer Vorteil ist, dass du so Arbeit leicht verteilen kannst. Steht die Schnittstelle fest, kannst du schonmal diese als Variable in einer Klasse verwenden. Das es noch gar keine Implemtierung dieser Schnittstelle geben muss führt zu keinem Problem mit dem Compiler (die Schnittstelle sichert schließlich zu, dass jede verwendete Implentierung diese Methoden haben muss). So kannst du auch leicht eine Dummy-Implementierung für erste Tests schreiben während eine andere Gruppe eine effiziente Implementierung erstellt. Ist die irgendwann fertig, das Tauschen ist kein Problem!
Natürlich sind das nicht alle Vorteile, aber imho schon mal genügend um ein wenig Motivation für den Einsatz zu geben. Gerade in kleinen Projekten fehlt diese häufig. Wenn man mal nur was schreibt, gerade auch zu hause, dann denkt man häufig der Aufwand lohnt sich gar nicht. Aber wenn man sich das nicht angewöhnt, fällt es einem nur später umso schwerer. Gerade im Beruf wirst du schnell merken, wo du im Design geschlampt hast. Da denkt man dann schnell mal, dass die Zeit knapp wird und macht etwas an der ein oder anderen Stelle unsauber. Dann kommt man damit zwar über einen Meilenstein und der Kunde ist zufireden. Dann kommen jedoch die neuen Wünsche und Nachbesserungen und ruck-zuck kostet das alles unnötig viel Zeit (deutlich mehr als wenn man es gleich richtig gemacht hätte).
Ok, genug abgeschweift. Kommen wir mal zurück zur eigentlichen Frage.
Was genau muss gemacht werden? Es tritt ein Ereignis ein. Dein Listener weiß erstmal nicht genau wer oder was dieses Ereignis ausgelöst hat (ist dem einfach mal egal). Er weiß nur, Ereignis xyz eingetreten und ruft die entsprechende Methode auf. Nehmen wir hier (o.B.d.A.) einfach mal an, du hast einen Button. Für diesen registrierst du einen MouseListener der auf ein Click reagiert. Dazu überschreibst du einfach die Methode eine MouseAdapters:
Code:
public class DummyMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
// Aufruf irgendeiner Klasse, die irgendwas berechnen soll
new Foo().doFoo();
}
}
}
Ok, diesen Listener registrierst du jetzt beim entsprechenden Button (oder bei allen Elementen, die dieses Event aufrufen). Diese brauchen dazu nur diese Klasse kennen (kein Variable in ihr) und die Klassen selbst muss gar nicht wissen bei wem sie registriert wird.
Jetzt wird bei einem Click mit dem linken MouseButton die Methode doFoo() der Klasse Foo aufgerufen. Diese kann jetzt in Ruhe einen Wert berechnen. Dazu greift sie nur auf eigenen Methoden zurück. Irgendwann ist sie fertig und möchte nun das Ergebnis ausgeben. Da du eine GUI hast, bietet sich diese dafür natürlich an.
Jetzt ist also die Frage, wie zeigst du das Ergebnis in deiner GUI an. Sagen wir hier wieder der Einfachheit halber, du möchtest das Ergebnis auf einem JLabel ausgeben.
Jetzt hast du mind. zwei Möglichkeiten. doFoo() kümmert sich selbst drum. Es kennt das JLabel direkt (also die Variable) und ruft von dem aus die Methode setText auf. Das gleiche machen doFoo2 und doFoo3, die berechnen was anderes und geben es auf diesem Label aus.
Ok, ich hab jetzt ein schönes Beispiel an dem ich das weiter erklären möchte: Ein einfacher Taschenrechner. Wie der Funktioniert ist erstmal egal. Du hast die Button und ihre Listener schon fertig. Oben hast du ein JLabel (um bei dem zu bleiben), dass immer das aktuelle Ergebnis anzeigt. Statt den Foos gibt es jetzt die unterschiedlichen Berechnungen. Du hast eine Methode addiere, eine substrahiere und eine multipliziere. Aber natürlich geben die alle immer den Wert auf dem gleichen Label aus (halt oben die Zeile des Taschenrechners). Greifen die Methoden direkt auf dieses Label zu klappt das soweit auch ohne Probleme. Dazu müssen sie natürlich die Variable kennen, auch kein Problem, die eine wäre dann public. Aber was wenn du jetzt dein Design änderst? Du gibst das Ergebnis jetzt z.B. nicht mehr auf einem JLabel aus. Oder sagen wir du änderst jetzt die Farbe wenn der Wert negativ ist in rot, sonst schwarz. Dann hast du hier schon drei Stellen an denen die Prüfung ob positiv oder negativ rein muss. Und auch die Logik zum ändern muss hier rein. Natürlich hast du auch deutlich mehr Operationen als 3, dem entsprechend viele Stellen wo du es änderst. Sieht dann sicherlich auch gut aus, wenn du eine Stelle davon zu ändern vergisst...
Du siehst es ist viel schöner eine eigene Methode zu erstellen, die nichts anderes macht als eben sich um die Ausgabe zu kümmern. Ruft jede Operation diese Methode auf, so kann jede Änderung an dieser einen Stelle gemacht werden und wirkt sich automatisch auf alle Stellen im Programm aus.
Diese Methode könnte ja z.B. als Argument das Ergebnis bekommen. Somit muss addiere, substrahiere,... nur die Methode kennen. Sie berechnen ihr Ergebnis und übergeben es an die Methode, z.B.
Code:
// liegt in package1
public class Anzeige {
// muss nicht direkt die GUI sein!
public void zeigeErgebnis(int value) {
gui.getResultLabel().setText((new Integer(value)).toString());
}
}
// liegt in package2
import package1.Anzeige;
// oh gott, sollte dringend an den Namen arbeiten
class RechnerLogik {
pubic void addiere(int a, int b) {
// nur der Übersicht wegen
int result = a + b;
// hier wäre natürlich ein Singletion oder so sinnvoller
new Anzeige().zeigeErgebnis(result);
}
pubic void subtrahiere(int a, int b) {
// nur der Übersicht wegen
int result = a - b;
// hier wäre natürlich ein Singletion oder so sinnvoller
new Anzeige().zeigeErgebnis(result);
}
}
Natürlich kannst du die reine Anzeige in das gleiche Package packen wie die GUI. Alles was du an der Anzeigen ändern möchtest, änderst du dann in der Klasse Anzeige. Damit die Anzeige auch ein Element hat, auf dem etwas dargestellt wird, muss diese Klasse natürlich dann noch das Label (oder was auch immer) kennen. Auch hier ist kein direkter Zugriff nötig. Du kannst dir einfach setter und getter schreiben (hier reichen natürlich getter), die dir den Zugriff auf die Elemente kapseln. Hast du zum beispiel den getter getLabelXYZ(), so kann hier ein beliebiges Label nach aussen gereicht werden. Anders als beim direkt Zugriff auf die Variable, kann hier aber auch nur lesend auf diesen Wert zugegriffen werden. Du kannst dieses Label nicht durch ein anderes ersetzen! Natürlich kannst du zusätztlich die Sichtbarkeit getrennt einschränken (setter dann nur bei Nachfahren, getter dagegen öffentlich oder nur im Package,...)
Wenn du nun noch alles ein wenig mehr mit Interfaces designt hättest, kannst du auch leicht die Anzeige austauschen.
Hoffe ich hab wenigstens ein Teil so erklären können, dass es dir weiter hilft!