[java] event communicatie tussen classes

Pagina: 1
Acties:

  • TD-er
  • Registratie: Januari 2000
  • Laatst online: 28-08 16:22
Ik ben hier met een programma bezig, wat in grote lijnen bestaat uit een main-class, waaruit 5 andere classes worden geinitialiseerd als threads.
We willen de communicatie via de main class laten gaan.
Bijv. in de GUI wordt een knopje ingedrukt, die eigenlijk ervoor moet zorgen dat er uit de database iets wordt gelezen.

Om te voorkomen dat op het zelfde niveau classes worden aangesproken, via procedures, willen we bovenstaande. ;)

samengevat willen we onze eigen events genereren, met eigen inhoud

We hebben hiervoor gekozen, omdat voorkomen moet worden dat het programma niet reageert door een netwerk timeout. Het geheel moet namelijk robuust zijn.
De andere threads zijn "interfaces" naar randapparatuur.

Zijn er mensen die een idee hebben hoe we de organisatorische kant binnen het programma kunnen aanpakken?

Een goedkope voeding is als een lot in de loterij, je maakt kans op een paar tientjes korting, maar meestal betaal je de hoofdprijs. mijn posts (nodig wegens nieuwe layout)


  • Jelmer
  • Registratie: Maart 2000
  • Laatst online: 22:22
Ik denk dat je gewoon het standaard event meganisme kunt gebruiken toch? (<<Listener>>, EventObject)

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Ik zou me eerst bezig gaan houden met events (moet je even op de sun site kijken naar een tutorial). En verder zie ik dat jullie met threads willen werken. Persoonlijk vind ik threads vrij enge dingen ivm concurrency en deadlocks. Ik hoop dat jullie deze materie enigsinds beheersen anders krijg je hele gekke dingen in je programma.

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
TD-er: We hebben hiervoor gekozen, omdat voorkomen moet worden dat het programma niet reageert door een netwerk timeout.
Het enige wat je hiervoor moet doen, is zorgen dat je geen langdurige operaties op de event-dispatch thread uitvoert en daar uiteraard absoluut helemaal niet gaat blokkeren. Swing is single-threaded en alle operaties op de GUI worden dus op dezelfde thread uitgevoerd. Dat is op zich geen probleem, maar je moet wel goed weten waarmee je bezig bent.

Op de Swing Connection kan je wat leuke artikelen over threads in combinatie met GUIs lezen:

http://java.sun.com/products/jfc/tsc/articles/index.html

Als je in het algemeen meer wilt weten over hoe je programma's moet opzetten met concurrency, kan ik je dit boek zeer, zeer sterk aanbevelen: "Concurrent Programming in Java: Design Principles and Patterns" van Doug Lea. Zoals de titel al aangeeft behandelt dit boek op een buitengewoon prettige manier patronen die je kan gebruiken bij het ontwikkelen van concurrent programs. De library die bij dit boek hoort zal in een toekomstige versie van Java waarschijnlijk in de standaard Java API worden opgenomen.

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • TD-er
  • Registratie: Januari 2000
  • Laatst online: 28-08 16:22
Op woensdag 10 juli 2002 21:10 schreef Jelmer Barhorst het volgende:
Ik denk dat je gewoon het standaard event meganisme kunt gebruiken toch? (<<Listener>>, EventObject)
We hebben daadwerkelijk een nieuw EventObject gemaakt en Threads zijn voor ons (met onze huidige "kennis") geen direct probleem, daar we de gevaren ervan goed begrijpen.
We stellen ons voor dat data binnenkomt via een netwerk connectie met de database. Deze data vuurt in de desbetreffende class een event af dat er iets klaarstaat. Vervolgens willen wij een event (dus een nieuw event) genereren en sturen naar de class waar de verkregen info gewenst is met als inhoud onze data.

Natuurlijk kunnen we het simpeler maken door de EventListener in de andere class te implementeren, maar dit komt niet ten goede van de modulariteit voor het later makkelijk kunnen uitbreiden van de code.

Een goedkope voeding is als een lot in de loterij, je maakt kans op een paar tientjes korting, maar meestal betaal je de hoofdprijs. mijn posts (nodig wegens nieuwe layout)


  • TD-er
  • Registratie: Januari 2000
  • Laatst online: 28-08 16:22
Op woensdag 10 juli 2002 21:30 schreef mbravenboer het volgende:

[..]

Op de Swing Connection kan je wat leuke artikelen over threads in combinatie met GUIs lezen:

http://java.sun.com/products/jfc/tsc/articles/index.html

Als je in het algemeen meer wilt weten over hoe je programma's moet opzetten met concurrency, kan ik je dit boek zeer, zeer sterk aanbevelen: "Concurrent Programming in Java: Design Principles and Patterns" van Doug Lea. Zoals de titel al aangeeft behandelt dit boek op een buitengewoon prettige manier patronen die je kan gebruiken bij het ontwikkelen van concurrent programs. De library die bij dit boek hoort zal in een toekomstige versie van Java waarschijnlijk in de standaard Java API worden opgenomen.
thanx voor de tip.
We hebben zojuist ff de omschrijving van het boek op amazon doorgespit en gaan van de week ff kijken of Scholtens Wristers (* een "inkijk-exemplaar" heeft ;)

Een goedkope voeding is als een lot in de loterij, je maakt kans op een paar tientjes korting, maar meestal betaal je de hoofdprijs. mijn posts (nodig wegens nieuwe layout)


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

het boek is echt een aanrader, maar wat voor kennis hebben jullie op het gebied van concurrency control? Overal een synchronized keyword omheen plakken is geen probleem, maar hoe wou je je systeem deadlock vrij houden?

  • TD-er
  • Registratie: Januari 2000
  • Laatst online: 28-08 16:22
Het deadlock vrij (proberen) we te houden door geen locks te gebruiken. Dat is hier namelijk niet nodig, doordat er in feite maar een process is, wat in de DB schrijft.
Verder is de IO naar/van de rand apperatuur in princiepe een richtings verkeer, dus we zitten niet constant te wachten....

We gebruiken dus geen locks, omdat we events willen gebruiken.

Een goedkope voeding is als een lot in de loterij, je maakt kans op een paar tientjes korting, maar meestal betaal je de hoofdprijs. mijn posts (nodig wegens nieuwe layout)


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op woensdag 10 juli 2002 22:24 schreef TD-er het volgende:
Het deadlock vrij (proberen) we te houden door geen locks te gebruiken.
En hoe wou je dan gaan synchroniseren? Je krijgt concurrency problemen als je het niet doet. Heb je nog geen 'inter process communicatie' gehad van Meulendijk?
Dat is hier namelijk niet nodig, doordat er in feite maar een process is, wat in de DB schrijft.
Verder is de IO naar/van de rand apperatuur in princiepe een richtings verkeer, dus we zitten niet constant te wachten....
Het gaat erom dat je bepaalde stukken van je systeem wel moet afschermen als je meerdere threads gaat gebruiken. Als je namelijk niet een lock op een object kan krijgen, dan kunnen andere threads op dat zelfde moment ook wijzigingen doorvoeren, en dan krijg je race problemen. Neem je boek:"Moderne Operating Systems" van Tanenbaum maar eens door.
We gebruiken dus geen locks, omdat we events willen gebruiken.
Je kan ze uitstekend met elkaar combineren hoor.

  • TD-er
  • Registratie: Januari 2000
  • Laatst online: 28-08 16:22
Op woensdag 10 juli 2002 22:33 schreef Alarmnummer het volgende:

[..]

En hoe wou je dan gaan synchroniseren? Je krijgt concurrency problemen als je het niet doet. Heb je nog geen 'inter process communicatie' gehad van Meulendijk?
Nee, want ik volg informatica op de RuG. Daar loopt volgens mij geen Meulendijk rond.
[..]

Het gaat erom dat je bepaalde stukken van je systeem wel moet afschermen als je meerdere threads gaat gebruiken. Als je namelijk niet een lock op een object kan krijgen, dan kunnen andere threads op dat zelfde moment ook wijzigingen doorvoeren, en dan krijg je race problemen. Neem je boek:"Moderne Operating Systems" van Tanenbaum maar eens door.
[..]
Dat boek heb ik al eens door genomen (en de nodige andere boeken van A.S. Tanenbaum), oa. voor de vakken OS en OS-lab
Ik maak me vooralsnog geen zorgen over deadlocks ed. daar onze programma-opzet daarin al de nodige voorzieningen bevat.
Maar weer ff on-topic.
Ons probleem is dus dat we nergens terug kunnen vinden hoe we zelf gemaakte events naar andere classes kunnen zenden (vb: de gebufferde data die in de DB-class is verzameld naar de main-class)

Een goedkope voeding is als een lot in de loterij, je maakt kans op een paar tientjes korting, maar meestal betaal je de hoofdprijs. mijn posts (nodig wegens nieuwe layout)


  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 28-08 12:00

Janoz

Moderator Devschuur®

!litemod

Op woensdag 10 juli 2002 22:54 schreef TD-er het volgende:
Nee, want ik volg informatica op de RuG. Daar loopt volgens mij geen Meulendijk rond.
Sterker nog, je bent in hetzelfde jaar begonnen als Alarmnummer ;)...

Foei Janoz, met je offtopic gelul

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op woensdag 10 juli 2002 22:54 schreef TD-er het volgende:

[..]

Nee, want ik volg informatica op de RuG. Daar loopt volgens mij geen Meulendijk rond.
Ik wist niet dat Informatica op de RuG technische informatica heet. Maar dan ken je inderdaad geen lex meulendijk.
Ons probleem is dus dat we nergens terug kunnen vinden hoe we zelf gemaakte events naar andere classes kunnen zenden (vb: de gebufferde data die in de DB-class is verzameld naar de main-class)
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public WorstjesZijnKlaarListener implements EventListener{
    public void worstjesKlaar(WorstEvent e);
}

public void WorstEvent extends EventObject{
    public WorstEvent(Object source){
      super(source);
    }
}

public WorstjesBakker{
  
    public Vector _worstjesListeners = new Vector();

    private void fireWorstjesZijnKlaar(){
     Vector clone = null;    
     synchronized(_worstesListener){
         clone = (Vector)_worstjesListener.clone();
     }
     
     WorstEvent e = new WorstEvent(this);
     Iterator itt = _clone.iterator();
     while(itt.hasNext()){
        ((WorstjesZijnKLaarListener)itt.next()).worstjesKlaar(e);
     }
    }

    public void addWorstjesListener(WorstjesZijnKLaarListener l){
       ..checks op null en dubbel.
       worstjesListeners.add(l);
    }

    ..remove dito.
}

en dan hoef je alleen maar implementaties van die worstjes listener toe te voegen.

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
:D .

Ik vind trouwens wel dat je in zulke voorbeelden even generics moet promoten ;) .

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op woensdag 10 juli 2002 23:13 schreef mbravenboer het volgende:
:D .

Ik vind trouwens wel dat je in zulke voorbeelden even generics moet promoten ;) .
Dat had inderdaad beter met generics gekunt, maar dat is op dit moment even minder belangrijk :) Ze weten nu hoe je een event kan versturen.

Jullie moeten trouwens wel goed opletten dat op deze manier je geen memory leaks gaat krijgen. Het kan namelijk gebeuren dat een object nergens meer naar refereerd, en dan blijft hij toch als listener geregistreerd staan. Op zich is dit niet zo`n probleem, want je bent maar een beetje geheugen kwijt, maar dit kan op lange termijn wel problemen veroorzaken, en het is natuurlijk super lelijk. Dit is een voorbeeld controller van mij waar ik op dit moment mee aan het werk ben waar dit probleem niet meer kan voorkomen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
public abstract class Controller<M extends Model, C extends ChangeListener,V extends VetoListener>{
    
    private List<WeakReference<C>> _changeListenerList = new LinkedList<WeakReference<C>>();
    private List<WeakReference<V>> _vetoListenerList = new LinkedList<WeakReference<V>>();
    private M _model;
    
    public Controller(M model){
        if(model == null){
            throw new NullPointerException("model can`t be null");
        }
        
        _model = model;
    }
    
    
    final public Model getModel(){
        return _model;
    }
    
    
    final public synchronized void addChangeListener(C listener){
        if(listener == null){
            throw new NullPointerException("listener can`t be null");
        }
        
        int indexOf = indexOfChangeListener(listener);
        if(indexOf!=-1){
            throw new IllegalArgumentException("listener:"+listener+" already is registered in controller");
        }
        _changeListenerList.add(new WeakReference<C>(listener));
    }
    
    
    final public synchronized void addVetoListener(V listener){
        if(listener == null){
            throw new NullPointerException("listener can`t be null");
        }
        
        int indexOf = indexOfVetoListener(listener);
        if(indexOf!=-1){
            throw new IllegalArgumentException("listener:"+listener+" already is registered in controller");
        }
        
        _vetoListenerList.add(new WeakReference<V>(listener));
    }
    
    
    final public synchronized void removeChangeListener(C listener){
        if(listener == null){
            throw new NullPointerException("listener can`t be null");
        }
        
        int indexOf = indexOfChangeListener(listener);
        if(indexOf == -1){
            throw new IllegalArgumentException("listener:"+listener+" is not found in controller");
        }
        _changeListenerList.remove(indexOf);
    }
    
    
    final public synchronized void removeVetoListener(V listener){
        if(listener == null){
            throw new NullPointerException("listener can`t be null");
        }
        
        int indexOf = indexOfVetoListener(listener);
        if(indexOf == -1){
            throw new IllegalArgumentException("listener:"+listener+" is not found in controller");
        }
        _changeListenerList.remove(indexOf);
    }
    
    
    final public synchronized void removeAllChangeListeners(){
        _changeListenerList.clear();
    }
    
    
    final public synchronized void removeAllVetoListeners(){
        _vetoListenerList.clear();
    }
    
    
    final public synchronized  boolean hasChangeListeners(){
        return _changeListenerList.size()>0;
    }
    
    
    final public synchronized  boolean hasVetoListeners(){
        return _vetoListenerList.size()>0;
    }
    
    
    final public synchronized List<C> getChangeListeners(){
        List<C> l = new LinkedList<C>();
        
        ListIterator<WeakReference<C>> itt = _changeListenerList.listIterator();
        while(itt.hasNext()){
            C listener = itt.next().get();
            if(listener == null){
                itt.remove();
            }else{
                l.add(listener);
            }
        }
        
        
        return l;
    }
    
    
    final public synchronized List<V> getVetoListeners(){
        List<V> l = new LinkedList<V>();
        
        ListIterator<WeakReference<V>> itt = _vetoListenerList.listIterator();
        while(itt.hasNext()){
            V listener = itt.next().get();
            if(listener == null){
                itt.remove();
            }else{
                l.add(listener);
            }
        }
                
        return l;
    }
    
    
    final private synchronized int indexOfChangeListener(C listener){
        assert listener!=null:"listener can`t be null";
        
        int index = 0;
        int result = -1;
        while(index<_changeListenerList.size()){
            if(_changeListenerList.get(index).get() == listener){
                result = index;
                index = _changeListenerList.size();
            }else{
                index++;
            }
        }
        
        return result;
    }
    
    
    final private synchronized int indexOfVetoListener(V listener){
        assert listener!=null:"listener can`t be null";
        
        int index = 0;
        int result = -1;
        while(index<_vetoListenerList.size()){
            if(_vetoListenerList.get(index).get() == listener){
                result = index;
                index = _vetoListenerList.size();
            }else{
                index++;
            }
        }
        
        return result;
    }
}

Ik werk hier met een WeakReference, waardoor objecten wel gerecycled worden als ze alleen nog maar bereikt kunnen worden door een WeakReference. De engiste voorwaarde voor dit systeem is dan dat je je listeners bij een object als member moet opnemen zodat de listener zo lang het object actief is, ook blijft bestaan.

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Die WeakReference is inderdaad een goede aanpak en zorgt er netjes voor dat het afmelden van listeners niet nodig is om objecten tot garbage te maken. Je zou ook kunnen zeggen dat je gewoon netjes je listener moet afmelden, maar eigenlijk vind ik de WeakReference ook wel iets erg lekkers hebben :) . Ik implementeer ook regelmatig caches rond een database met weak-references.

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • TD-er
  • Registratie: Januari 2000
  • Laatst online: 28-08 16:22
Sorry dat ik zo laat pas reageer, maar bedankt voor de voorbeeld code.
Ik denk dat we hiermee een stuk verder kunnen komen.
zo niet, dan weet ik dit topic gauw weer te vinden ;)

Een goedkope voeding is als een lot in de loterij, je maakt kans op een paar tientjes korting, maar meestal betaal je de hoofdprijs. mijn posts (nodig wegens nieuwe layout)


  • Baron
  • Registratie: Juli 2000
  • Laatst online: 21-06 17:02
Misschien is dit ook nog een nuttige link:
Communicating Sequential Processes for JavaTM (JCSP)

http://www.cs.ukc.ac.uk/projects/ofa/jcsp/

Verwijderd

Op woensdag 10 juli 2002 23:20 schreef Alarmnummer het volgende:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    final private synchronized int indexOfVetoListener(V listener){
        assert listener!=null:"listener can`t be null";
        
        int index = 0;
        int result = -1;
        while(index<_vetoListenerList.size()){
            if(_vetoListenerList.get(index).get() == listener){
                result = index;
                index = _vetoListenerList.size();
            }else{
                index++;
            }
        }
        
        return result;
    }
}
Die WeakReferences zijn idd erg grappig, had ze nog nooit gezien...Maar even een offtopic vraagje: waarom doe je bovenstaande code niet gewoon als:
code:
1
2
3
4
5
6
7
8
9
10
    final private synchronized int indexOfVetoListener(V listener){
        assert listener!=null:"listener can`t be null";
        
        for(int index=0;index<_vetoListenerList.size();index++)
        {
            if(_vetoListenerList.get(index).get() == listener)
                return index;
        }
        return -1;
    }

Okay, is maar ietsje anders, dus op zich boeit het niet zo, maar ik ben wel benieuwd wat je motivatie is om het op jouw manier te doen.
En als we dan toch bezig zijn; wat is het nut van een final private methode, ik bedoel, private impliceert al dat je de methode toch niet kan herdefinieren, waarom dan nog de final ervoor??

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op vrijdag 12 juli 2002 11:47 schreef hondass50 het volgende:

[..]

Die WeakReferences zijn idd erg grappig, had ze nog nooit gezien...Maar even een offtopic vraagje: waarom doe je bovenstaande code niet gewoon als:
code:
1
2
3
4
5
6
7
8
9
10
    final private synchronized int indexOfVetoListener(V listener){
        assert listener!=null:"listener can`t be null";
        
        for(int index=0;index<_vetoListenerList.size();index++)
        {
            if(_vetoListenerList.get(index).get() == listener)
                return index;
        }
        return -1;
    }

Okay, is maar ietsje anders, dus op zich boeit het niet zo, maar ik ben wel benieuwd wat je motivatie is om het op jouw manier te doen.
Regel: bij een for lus weet je het aantal iteraties, en bij een while lus niet. Het is daarom niet gebruikelijk om uit een for lus te springen op deze manier. Het kan in principe wel, maar ik doe het dus nooit.
En als we dan toch bezig zijn; wat is het nut van een final private methode, ik bedoel, private impliceert al dat je de methode toch niet kan herdefinieren, waarom dan nog de final ervoor??
Dat heeft inderdaad geen nut. Het komt omdat ik die klasses nog aan het ontwerpen bent, en die methodes zijn eerst publiekelijke geweest, en dan gooi ik altijd alles op final. Later zijn ze weer veranderd naar private, en die final ben ik vergeten weg te halen. :z

Verder is deze implementatie bij lange na nog niet klaar. Ik ben op dit moment bezig om hem weer om te gooien. Ik wil mijn collection support net zo generiek hebben als een de collection package zelf. Als je namelijk naar een lijst luisterd, met de 'ListChangeListener' dan zou je ook naar die lijst moeten kunnen luisteren met de 'CollectionChangeListener' omdat een lijst ook een Collection is. Het enigste waar ik op dit moment loop te klooien is de parametrisering (generics).

Verwijderd

Op vrijdag 12 juli 2002 11:52 schreef Alarmnummer het volgende:

Regel: bij een for lus weet je het aantal iteraties, en bij een while lus niet. Het is daarom niet gebruikelijk om uit een for lus te springen op deze manier.
Hmm, das nog niet echt duidelijk... ik snap de redenering: Bij een for lus weet je het aantal iteraties, dus is het niet gebruikelijk uit een lus te springen niet echt...
Dat heeft inderdaad geen nut. Het komt omdat ik die klasses nog aan het ontwerpen bent, en die methodes zijn eerst publiekelijke geweest, en dan gooi ik altijd alles op final. Later zijn ze weer veranderd naar private, en die final ben ik vergeten weg te halen. :z
Okay, das wel duidelijk ;)

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op vrijdag 12 juli 2002 12:08 schreef hondass50 het volgende:

[..]

Hmm, das nog niet echt duidelijk... ik snap de redenering: Bij een for lus weet je het aantal iteraties, dus is het niet gebruikelijk uit een lus te springen niet echt...
Je weet bij een forlus het aantal iteraties, en dan moet je ook dat aantal iteraties doorlopen en niet vroegtijdig eruit springen. Dit is een van de regeltjes die ik geleerd heb toen ik nog op de universiteit zat, en die pas ik altijd nog toe :)

Verwijderd

Op vrijdag 12 juli 2002 12:13 schreef Alarmnummer het volgende:

[..]

Je weet bij een forlus het aantal iteraties, en dan moet je ook dat aantal iteraties doorlopen en niet vroegtijdig eruit springen. Dit is een van de regeltjes die ik geleerd heb toen ik nog op de universiteit zat, en die pas ik altijd nog toe :)
Vreemde regels daar dan...Maar goed, over smaak valt niet te twisten (for what it's worth; hier op de UU wordt de for loop aanpak met return gepromoot en zou die while constructie je ongetwijfeld een aantekeningetje hebben opgeleverd bij je ingeleverde praktikum opgave... :) )

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op zich wel eigenaardig om een functie te verlaten voor het einde. Ik pas zelf meestal een beetje op met het overal exiten van een methode. Meestal die ik dat of aan het begin (checks ed) of helemaal aan het einde. (en dan vaak met een 'result' variable).

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 15:34
Op vrijdag 12 juli 2002 11:52 schreef Alarmnummer het volgende:
Regel: bij een for lus weet je het aantal iteraties, en bij een while lus niet. Het is daarom niet gebruikelijk om uit een for lus te springen op deze manier. Het kan in principe wel, maar ik doe het dus nooit.
Gebruik dan alsjeblieft wel een break en niet zo'n ranzige assignment om te zorgen dat je loopje beëindigd wordt:
code:
1
2
3
4
5
6
7
8
while(index<_vetoListenerList.size()){
  if(_vetoListenerList.get(index).get() == listener){
    result = index;
    index = _vetoListenerList.size();
  }else{
    index++;
  }
}

Deze code is ontzettend lelijk en onduidelijk. Als je een listener gevonden hebt, wil je helemaal niets met die index doen. Je wilt gewoon je uit je lus breken. Nu voer je een operatie uit die als bijeffect het beëindigen van de lus heeft, terwijl er ook een statement is dat die intentie (en alleen die intentie) uitdrukt. Het is niet alsof het beëindigen van de lus iets met de grootte van de lijst te maken heeft.

Het is allemaal leuk en aardig om geen return's en break's in je code te hebben staan, maar in dit geval is het middel erger dan de kwaal. Zolang dit soort statements binnen een beperkte scope worden uitgevoerd is er niets mis mee.
Op woensdag 10 juli 2002 23:20 schreef Alarmnummer het volgende:
Jullie moeten trouwens wel goed opletten dat op deze manier je geen memory leaks gaat krijgen. Het kan namelijk gebeuren dat een object nergens meer naar refereerd, en dan blijft hij toch als listener geregistreerd staan. Op zich is dit niet zo`n probleem, want je bent maar een beetje geheugen kwijt, maar dit kan op lange termijn wel problemen veroorzaken, en het is natuurlijk super lelijk.
Dat moet je niet tegen een fatsoenlijke C++ programmeur zeggen! Dit soort slordigheden maken een applicatie onbruikbaar voor grootschalig gebruik; dat lijkt me niet de bedoeling. Je hebt gelijk dat een memory super lelijk is en problemen kan veroorzaken; het is dus zeker niet zo dat het "op zich niet zo'n probleem" is.

Dat is gelijk ook het bezwaar dat ik tegen garbage collected talen heb: een garbage collector doet niet meer dan een best-effort poging om ongebruikte objecten vrij te geven. Aangezien niet gegarandeerd wordt dat alle objecten vrijgegeven worden, heb je hier niets aan als programmeur en moet je je dus toch weer met de details van het vrijgeven van objecten bezig houden. Wanneer je al je referenties expliciet op null moet zetten, had je net zo goed een delete operatie uit kunnen voeren.

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op vrijdag 12 juli 2002 13:21 schreef Soultaker het volgende:

[..]

Gebruik dan alsjeblieft wel een break en niet zo'n ranzige assignment om te zorgen dat je loopje beëindigd wordt:
code:
1
2
3
4
5
6
7
8
while(index<_vetoListenerList.size()){
  if(_vetoListenerList.get(index).get() == listener){
    result = index;
    index = _vetoListenerList.size();
  }else{
    index++;
  }
}

Deze code is ontzettend lelijk en onduidelijk. Als je een listener gevonden hebt, wil je helemaal niets met die index doen. Je wilt gewoon je uit je lus breken. Nu voer je een operatie uit die als bijeffect het beëindigen van de lus heeft, terwijl er ook een statement is dat die intentie (en alleen die intentie) uitdrukt. Het is niet alsof het beëindigen van de lus iets met de grootte van de lijst te maken heeft.

Het is allemaal leuk en aardig om geen return's en break's in je code te hebben staan, maar in dit geval is het middel erger dan de kwaal. Zolang dit soort statements binnen een beperkte scope worden uitgevoerd is er niets mis mee.
Ik vind een break eerlijk gezegd een constructie die volledig uit de taal verwijderd had kunnen worden icm het goto statement. Ik ben het hier dus niet mee eens. Break goto en return statements en op alle plekken, maken je code slecht om te lezen omdat je niet een bepaald pad in je methode code kan volgen. Ik vind dat dus een typisch voorbeeld van slechte code.
Dat moet je niet tegen een fatsoenlijke C++ programmeur zeggen! Dit soort slordigheden maken een applicatie onbruikbaar voor grootschalig gebruik; dat lijkt me niet de bedoeling. Je hebt gelijk dat een memory super lelijk is en problemen kan veroorzaken; het is dus zeker niet zo dat het "op zich niet zo'n probleem" is.
Ik snap waar je heen wilt, daarom heb ik dus nu ook andere support classes gemaakt, waarin een programmeur niet meer hoeft te zorgen dat zijn listeners bij controllers afgemeld hoeven te worden. (zie uitleg hieronder).
Dat is gelijk ook het bezwaar dat ik tegen garbage collected talen heb: een garbage collector doet niet meer dan een best-effort poging om ongebruikte objecten vrij te geven. Aangezien niet gegarandeerd wordt dat alle objecten vrijgegeven worden
Je mag ervan uitgaan dat alle objecten gerecycled worden, en het probleem met die aangemelde listener heeft dus absoluut niets met een slecht gc algoritme te maken. De oorzaak van de 'memory leak' is dat een listener in de controller als luisteraar aangemeld blijft staan en daardoor zal het object waarin de listener zit (kan bv een button of label zijn) ook niet gerecycled worden. In principe doet de gc zijn werk goed (er is geen memory leaks zoals in c, c++) , omdat er dus nog 1 referentie naar dit stuk waste aanwezig is.

En dan begint die WeakReference interessant te worden. Als een object alleen nog maar te bereiken is via een WeakReference, dan mag dat object gerecycled worden. In de controller van mij, staat iedere listener in een lijst, maar ze zjin allemaal gewrapped in een WeakReference. Hierdoor kan de gc de listeners wel gaan recylen als niemand er meer naar refereerd.

Daarom deugt jouw argument, dat een gc taal niet alle objecten kan recyclen, ook niet.
heb je hier niets aan als programmeur en moet je je dus toch weer met de details van het vrijgeven van objecten bezig houden. Wanneer je al je referenties expliciet op null moet zetten, had je net zo goed een delete operatie uit kunnen voeren.
Het enigste je nu hoeft te doen om de gc wat te helpen, is de listeners als member op te nemen.

vb:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FieldLabel extends JLabel{
    
    private FieldChangeListener _changeListener = new ChangeListenerImpl();
    
    public FieldLabel(FieldModel model){
        if(model == null){
            throw new NullPointerException("model can`t be null");
        }
        
        model.getController().addChangeListener(_changeListener);
        setText(""+model.getValue());
    }
    
    private class ChangeListenerImpl implements FieldChangeListener{
        public void changed(FieldChangeEvent e){
            if(e == null){
                throw new NullPointerException("e can`t be null");
            }
            
            setText(""+e.getNewValue());
        }
    }
}

Je ziet dat ik hier de _changeListener registreer bij de FieldLabel. Dit doe ik omdat als ik de fieldListener als volgt zou toevoegen:
model.getController().addChangeListener(new ChangeListenerImpl());
hij meteen gerycled zou worden. Hij wordt meteen gerecycled omdat hij in de lijst van listeners komt, maar wel gewrapped in een WeakReference. Hierdoor komt hij meteen voor gc in aanmerking. Daarom moet er buiten die reference naar de listener in de controller, ook nog ergens anders een 'echte' reference aanwezig zijn, en dat is hier de meest voor de hand liggende plek.

Verder heb je niets meer te maken met het afmelding van objecten. Stel dat je een scherm hebt waar oa deze label op voorkomt, dan kan je gewoon de referenties naar het scherm weghalen. Aangezien label alleen via een weakreference via de controller te bereiken is, komt de label dus wel voor gc in aanmerking, en hierdoor is het probleem van 'memory leak' keurig opgelost.

Dit is veel minder gecompliceerd dan een signaal door te geven aan het scherm dat ieder component zichzelf moet vrijgeven.

Kortom ik vind dit een uitstekende oplossing.

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 15:34
Mozilla crashte bij het replyen. :( Poging 2.
Op vrijdag 12 juli 2002 13:56 schreef Alarmnummer het volgende:
Ik vind een break eerlijk gezegd een constructie die volledig uit de taal verwijderd had kunnen worden icm het goto statement. Ik ben het hier dus niet mee eens. Break goto en return statements en op alle plekken, maken je code slecht om te lezen omdat je niet een bepaald pad in je methode code kan volgen.
Waarom kun je niet 'een bepaald pad' volgen? Met jou constructie bereik je PRECIES dezelfde program flow als met een break statement, met het verschil dat een break statement wél uitdrukt wat je ermee wilt bereiken.

Het enige dat je wilt, is uit de lus breken. Jij doet dit, door index gelijk te maken aan het aantal elementen van de lijst. Ten eerste breek je daarmee de invariant, dat binnen de scope van de lus 'index' een geldige index aanduidt en ten tweede is de regel op zichzelf niet duidelijk.

Zonder de omsluitende code is het onduidelijk dat je hier feitelijk een 'break' simuleert. Je doet iets met een index variabele en de lengte van een lijst, maar wat je met het statement wilt bereiken heeft helemaal niets met de index of de lengte van de lijst te maken!

Als verder de for-lus wordt aangepast, zodat in plaats van een index en een array een iterator wordt gebruikt (om maar wat te noemen) moet jou wannabe-'break'-constructie ook aangepast worden, terwijl de betekenis ervan feitelijk niet veranderd is!

Daarbij wil je, voor de consistentie, al je for lussen op deze manier schrijven. Hier is het niet zo'n probleem, maar in C++ is de complexiteit van de size methode van een list O(n), waardoor het op deze manier uit de lus breken ook nog eens een dure operatie wordt.

In het algemeen ben ik met je eens dat break en return statements vermeden moeten worden (vooral als ze 'grote' sprongen maken), maar in dit geval is dat duidelijk niet het geval.
Ik snap waar je heen wilt, daarom heb ik dus nu ook andere support classes gemaakt, waarin een programmeur niet meer hoeft te zorgen dat zijn listeners bij controllers afgemeld hoeven te worden.
Leuk en aardig, maar als programmeur moet je dus actief onderzoeken of de betreffende controller dat wel of niet wil. Aangezien je je dan alweer met de destructie van je objecten bezig moet houden, is het nut van de garbage collector deels weggenomen.
Je mag ervan uitgaan dat alle objecten gerecycled worden, en het probleem met die aangemelde listener heeft dus absoluut niets met een slecht gc algoritme te maken.
Dat klopt; het heeft met het garbage collecten en sich te maken. Alleen de applicatieprogrammeur kan met 100% zekerheid vaststellen wanneer een object wel of niet verwijderd kan worden.
En dan begint die WeakReference interessant te worden. Als een object alleen nog maar te bereiken is via een WeakReference, dan mag dat object gerecycled worden.
En dat elimineert een volgend voordeel van een garbage collector, namelijk de garantie dat references geldig zijn. Van weak references moet expliciet gecontroleerd worden of ze nog geldig zijn, voordat ze gebruikt kunnen worden. Dat is erg duur en was juist een belangrijke reden dat platforms als Java en .NET een garbage collector invoerden.
Daarom deugt jouw argument, dat een gc taal niet alle objecten kan recyclen, ook niet.
Feit is dat dat niet KAN. Je moet expliciet reference variabelen op null zetten om ervoor te zorgen dat sommige objecten opgeruimd worden. Mijn punt is dat een reference op null zetten (en nadenken over de gevallen waarin dat moet gebeuren) vergelijkbaar is met ownership management van gealloceerd geheugen en het deleten van een object.

In een gc taal kun je dus wel netjes alle overbodige objecten opruimen, maar alleen als je dan terugvalt op principes die je toch ook al in een niet-gc taal toepaste (wat juist voorkomen zou moeten worden). Wat dat betreft is de gc dus alleen een hulpmiddel en geen volledige oplossing, wat een groot deel van de bruikbaarheid teniet doet.

Als ik dan toch voor een deel na moet denken over reference counting en memory ownership, dan moet ik dus skills hebben om daarover te redeneren. Als ik die skills toch moet hebben, kan ik ze net zo goed voor élk object toepassen. De gc is dan dus een extraatje geworden.
Kortom ik vind dit een uitstekende oplossing.
Voor Java is het een aardige oplossing. Niet ideaal, omdat je nu dus alsnog ongeldige references kunt krijgen in je lijst. Ik zou zeggen dat het de verantwoordelijkheid van de code die een object aanmeldt is, om dit object ook weer af te melden.

In C++ zou ik dit zelfs op kunnen lossen door een template class te maken, die een (specifiek) Listener-object encapsuleert en bij destructie automatisch afmeld. Het effect is hetzelfde, maar nu kan code buiten je event-genereer-klasse geen invloed meer hebben op de references die daarbinnen opgeslagen zijn (zoals met weak references wel het geval is).

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op vrijdag 12 juli 2002 15:43 schreef Soultaker het volgende:
Mozilla crashte bij het replyen. :( Poging 2.
Ik ken het, erg irritant. Ik had laatst een heel visitor verhaal voor iemand geschreven van een paar kantjes, en toen crash! Ik had daarna ook zoiets van: zak er om mij ook in ;)
Waarom kun je niet 'een bepaald pad' volgen?
Zo gauw jij gaat 'breaken' dan ben jij je programma flow kwijt, omdat je bepaalde code dat verder opstaat niet meer bereikt. Je springt volledig uit een bepaalde constructie. Breaks, labels en overal return statements zijn in mijn ogen nog steeds een teken van extreem slechte software. (Ik accepteer het ook niet van stagaires bij ons).
Met jou constructie bereik je PRECIES dezelfde program flow als met een break statement, met het verschil dat een break statement wél uitdrukt wat je ermee wilt bereiken.

Het enige dat je wilt, is uit de lus breken. Jij doet dit, door index gelijk te maken aan het aantal elementen van de lijst. Ten eerste breek je daarmee de invariant, dat binnen de scope van de lus 'index' een geldige index aanduidt
Op zich kan ik me hier wel een beetje in vinden.
en ten tweede is de regel op zichzelf niet duidelijk. Zonder de omsluitende code is het onduidelijk dat je hier feitelijk een 'break' simuleert.
Dit is eigelijk standaard code voor mij, en als ik zoiets zie dan weet ik hoe laat het is. Verder denk ik ook niet dat we elkaar echt goed kunnen overtuigen. Ik heb deze regels overgenomen toen ik nog op de universiteit zat, en heb ze altijd toegepast.
Als verder de for-lus wordt aangepast, zodat in plaats van een index en een array een iterator wordt gebruikt (om maar wat te noemen) moet jou wannabe-'break'-constructie ook aangepast worden, terwijl de betekenis ervan feitelijk niet veranderd is!
Ik heb eerlijk gezegd nog nooit een iterator vanuit een for statement gebruikt. Ik heb het altijd al een lelijke constructie gevonden. En pas het eigelijk ook nooit toe. Misschien dat ik een beetje ouderwets hierin ben, maar ik vind het oerlelijk.
Daarbij wil je, voor de consistentie, al je for lussen op deze manier schrijven. Hier is het niet zo'n probleem, maar in C++ is de complexiteit van de size methode van een list O(n), waardoor het op deze manier uit de lus breken ook nog eens een dure operatie wordt.
Dat kan je oplossen door de size eerst even op te slaan in een variable. En trouwens gebeurt dat maar 1 keer, en in de while constructie gebeurd het bij ieder iteratie. Dus als iets kostbaar is, dan is dat het wel. Maar dit is verder niet echt relevant. (het lijkt me trouwens wel een beetje vreemd dat ze de size in een c list niet cachen).
In het algemeen ben ik met je eens dat break en return statements vermeden moeten worden (vooral als ze 'grote' sprongen maken), maar in dit geval is dat duidelijk niet het geval.
Zijn we het nog ergens over eens ;)
Leuk en aardig, maar als programmeur moet je dus actief onderzoeken of de betreffende controller dat wel of niet wil. Aangezien je je dan alweer met de destructie van je objecten bezig moet houden, is het nut van de garbage collector deels weggenomen.
Er zijn inderdaad voorwaarden verbonden aan het gebruik van deze controller. Maar zelfs met dit beetje extra werk, is een gc taal toch nog veel makkelijker om in te programmeren dan een non gc taal en is niet reden genoeg om de gc dan maar geheel te verwijderen.
Dat klopt; het heeft met het garbage collecten en sich te maken. Alleen de applicatieprogrammeur kan met 100% zekerheid vaststellen wanneer een object wel of niet verwijderd kan worden.
Ik neem aan dat je wel iets weet over gc? Als je vanuit de root van je systeem een bepaald opject op geen enkele manier kan bereiken, dan kan dit object gegarbage collect worden, en dit kan het systeem uitstekend zelf bepalen met een goed gc algoritme. Ik ben het dus niet met je eens dat een applicatie programmeur alleen maar weet welke objecten gegarbage collect kunnen worden.

Verder gebeurt dit binnen een bepaalde context. Ik weet dat niemand belangstelling heeft om een listener weer uit een controller te halen om er weer iets mee te doen. Op dat moment mag je dus een extra aanname aannemen ;) Zo gauw een object niet meer te bereiken is, of als een object alleen te bereiken is via de controller, dan mag het object gegarbage collect worden.
En dat elimineert een volgend voordeel van een garbage collector, namelijk de garantie dat references geldig zijn. Van weak references moet expliciet gecontroleerd worden of ze nog geldig zijn, voordat ze gebruikt kunnen worden. Dat is erg duur en was juist een belangrijke reden dat platforms als Java en .NET een garbage collector invoerden.
De WeakReference is niets anders dan een Wrapper om een bepaald object heen. Deze WeakReference heeft in zich een verwijzing naar dat object. Als de gc eraan komt, en controleerd op hoeveel manier dat object te bereiken is, en hij komt erachter dat het alleen via WeakReferences is. Dan is dat voor hem voldoende om die referentie naar dat object in de WeakReference op null te zetten en het object te recyclen. Je kan dus heel eenvoudig als een bepaald object nog niet gegarbage collect is, je hoeft dan alleen te checken of die referentie !=null is.

Aangezien dit in een generieke api gebeurd en een eind gebruiker van deze api hier niets mee te maken heeft, zie ik hier het probleem niet van in. Uiteindelijk heeft de gebruiker van deze api hier niet eens weet van.
Feit is dat dat niet KAN. Je moet expliciet reference variabelen op null zetten om ervoor te zorgen dat sommige objecten opgeruimd worden.
Dat hoeft niet hoor :) Als we weer even kijken naar die label, dan zie je dat hij in zich een listener heeft. Stel dat je het volgende gaat doen met een scherm waarop die label zit:
code:
1
2
3
4
public void showLeukScherm(){
   LeukeModalDialog l = new LeakeModalDialog();
   l.setVisible(true);
}

Zie je dat ik helemaal niets bezonders hoef te doen om die listeners te laten verwijderen bij die controller als ik de methode verlaat? Dit komt omdat na het verlaten van de methode, er geen enkele referentie meer naar dat scherm is afgezien in de controller. In de controller zit dus nog een listener, die listerner zit in een label en de label zit weer aan het scherm. In 1e instantie zou het scherm niet gerecyled mogen worden, maar.. nu komt de grote maar ;)

De listener kan alleen bereikt worden via een WeakReference. Dit is een reden voor de gc om die referentie in die weakReference op null te zetten. Aangezien nu ook niemand meer naar die label en het scherm wijst, kunnen die ook meteen gegarbage collect worden.
Mijn punt is dat een reference op null zetten (en nadenken over de gevallen waarin dat moet gebeuren) vergelijkbaar is met ownership management van gealloceerd geheugen en het deleten van een object.

In een gc taal kun je dus wel netjes alle overbodige objecten opruimen, maar alleen als je dan terugvalt op principes die je toch ook al in een niet-gc taal toepaste (wat juist voorkomen zou moeten worden). Wat dat betreft is de gc dus alleen een hulpmiddel en geen volledige oplossing, wat een groot deel van de bruikbaarheid teniet doet.
Zoals ik boven heb uitgelegd, hoef je zelf helemaal geen referenties op null te zetten. Het enigste anders moet doen van de standaard aanpak is dat je een strong reference naar die listener moet bijhouden, thats is. (je hoeft dus helemaal 0.0 referenties op null te zetten)
Voor Java is het een aardige oplossing. Niet ideaal, omdat je nu dus alsnog ongeldige references kunt krijgen in je lijst. Ik zou zeggen dat het de verantwoordelijkheid van de code die een object aanmeldt is, om dit object ook weer af te melden.
In de listenerlist kunnen inderdaad WeakReferences voorkomen waar geen listener meer onder zit. Maar dit is toch niet zo`n probleem? Dit kan op een centrale plek keurig netjes worden opgelost, en daarmee is dit centraal opgelost, en is 100% geencapsulate in 1 object. En dat is toch precies zoals het moet :) Ik vind dus echt een zeer goeie oplossing om waste automatisch te laten verwijderen :)
In C++ zou ik dit zelfs op kunnen lossen door een template class te maken, die een (specifiek) Listener-object encapsuleert en bij destructie automatisch afmeld. Het effect is hetzelfde, maar nu kan code buiten je event-genereer-klasse geen invloed meer hebben op de references die daarbinnen opgeslagen zijn (zoals met weak references wel het geval is).
Ik hoef helemaal geen cleanup code te schrijven, net zoals in gewoon gc java :) Ik vind mijn manier dus een stuk handiger dan allerlei destructor code te schrijven.

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 15:34
Op vrijdag 12 juli 2002 16:37 schreef Alarmnummer het volgende:
Zo gauw jij gaat 'breaken' dan ben jij je programma flow kwijt, omdat je bepaalde code dat verder opstaat niet meer bereikt.
Ok, het is dus good practice om niet halverwege een blok code te break/return-en. Break/return biedt inderdaad mogelijkheden tot misbruik, maar dat geldt ook voor andere constructies. De discussie in hoeverre een taal 'lelijke' oplossingen moet voorkomen (ten koste van de situaties waarin een break wél een mooie oplossing is) is nauwelijks to the point en hebben we al meer gevoerd. Het is een beetje een kwestie van smaak waar de grens getrokken wordt.
Breaks, labels en overal return statements zijn in mijn ogen nog steeds een teken van extreem slechte software. (Ik accepteer het ook niet van stagaires bij ons).
Labels gebruik ik zelf nooit en 'overal return statements' gaat natuurlijk wat ver. Zoals je zelf al zei, is er bijvoorbeeld niets mis met een aantal initiële statements die checken of de argumenten van een functie geldig zijn en zoniet een standaardwaarde returnen. Het is een beetje omslachtig om dan een retval variabele te maken, daar het resultaat in te stoppen en de 'echte' code in een else-blok te stopppen. Feitelijk gebeurt er dan hetzelfde en hoewel je de extra returns hebt weggewerkt, is je code niet overzichtelijker geworden.
Ik heb eerlijk gezegd nog nooit een iterator vanuit een for statement gebruikt. Ik heb het altijd al een lelijke constructie gevonden. En pas het eigelijk ook nooit toe. Misschien dat ik een beetje ouderwets hierin ben, maar ik vind het oerlelijk.
While-lus mag ook hoor (die gebruikte je zelf al met een iterator in je voorbeeldcode), dat maakt voor mijn punt weinig uit.
(het lijkt me trouwens wel een beetje vreemd dat ze de size in een c list niet cachen).
Volgens de ANSI C++ STL specificatie mag de size operatie voor lists O(n) duren. In de SGI implementatie (die onder andere door g++ gebruikt wordt) is de implementatie ook daadwerkelijk O(n). Een constante tijd operatie zou aanzienlijke overhead opleveren, aangezien er een paar bytes extra opgeslagen worden (wat bij kleine lijsten significant verschil maakt) en bovendien bij elke operatie op de lijst waarbij een item toegevoegd of verwijderd wordt, de teller bijgewerkt moet worden.

In situaties waarin de lengte van de lijst niet van belang is (wat in C++ vaak zo is, aangezien je iterators gebruikt om lijsten te doorlopen), is het zonde om voor Jan met de korte achternaam elke keer een tellertje bij te werken. Als je dit echt nodig hebt, is het eenvoudig genoeg om 'm zelf te 'cachen', zoals je al noemde.

Maar goed, dit gaat wel érg off-topic. ;) (My bad.)
Ik neem aan dat je wel iets weet over gc? Als je vanuit de root van je systeem een bepaald opject op geen enkele manier kan bereiken, dan kan dit object gegarbage collect worden, en dit kan het systeem uitstekend zelf bepalen met een goed gc algoritme. Ik ben het dus niet met je eens dat een applicatie programmeur alleen maar weet welke objecten gegarbage collect kunnen worden.
Misschien drukte ik me niet zo handig uit. Ik bedoel dat alleen een applicatieprogrammeur kan inschatten wanneer een object niet meer gebruikt gaat worden (en dus verwijderd kan worden).

Overigens moet ik toegeven dat ik alleen hands-on ervaring heb met reference-counting garbage collectors.

Wanneer een garbage collector constateert dat een object verwijderd kan worden, dan is dit correct. Dit betekent echter niet, dat wanneer een object verwijdert kan worden, de garbage collector dit ook weet (het is een one-way verband; een implicatie). Hiervoor moet de programmeur (in sommige gevallen) expliciet references op null zetten.
Zoals ik boven heb uitgelegd, hoef je zelf helemaal geen referenties op null te zetten. Het enigste anders moet doen van de standaard aanpak is dat je een strong reference naar die listener moet bijhouden, thats is. (je hoeft dus helemaal 0.0 referenties op null te zetten)
Dat komt omdat je weak reference in principe de reference count van het object niet verhoogt (en dus ook niet meer hoeft te verlagen). Wel moet de weak reference variabele die naar het object verwijst, bij dat object opgeslagen worden (omdat de garbage collector moet weten welke weak reference variabelen op null moeten worden gezet).

Je hebt de eis om expliciet de reference count te verlagen door de reference variabele op null te zetten, omzeilt door de reference count ueberhaupt niet te verhogen, met als gevolg dat je niet meer zeker weet of je een geldige reference hebt (totdat je dat controleert). Het principe achter een garbage collector, dat zegt dat alle references altijd geldig zijn totdat ze niet meer in gebruik zijn, gaat dan niet op.

Ik vind dat geen erg mooie oplossing, maar het werkt praktisch gezien inderdaad wel aardig. Het blijft jammer dat references die intern opgeslagen zijn, toch nog gewijzigd kunnen worden. Dat gaat weer tegen het principe van data encapsulatie in: gegevens die binnen een object worden opgeslagen, mogen alleen via de methoden van dat object worden benaderd. Natuurlijk is dat nu feitelijk nog steeds het geval, maar de objecten die intern worden opgeslagen veranderen 'zomaar' van identiteit (van geldige reference naar null reference, in feite).

Een ander probleem is dat in principe elke operatie die iets met de lijst doet (en dus null-references kan tegenkomen) die null-references ook moet opruimen. In dit geval gebruik je daar itt.remove() voor, wat wel werkt, maar niet zo mooi is omdat dezelfde code in elke operatie die met de interne lijst werkt terugkomt. Ook worden null references nooit uit de lijst verwijderd als er geen operaties worden aangeroepen die de (hele) lijst doorlopen.

In veel gevallen wordt er in de lijst naar een specifiek object gezocht en kan het dus goed zijn dat null references aan het einde van de lijst nooit gedetecteerd worden en dus ook nooit verwijderd, wat jammer is omdat het tenkoste van het geheugengebruik gaat. Ook is het op deze manier niet meer mogelijk om de grootte van de vector/list te gebruiken om het aantal actieve objecten te weten te komen.

Een groot voordeel van eisen dat een object zichzelf deregistreert (is dat een woord?) is dat de code om een object te verwijderen op één plek gelocaliseerd wordt. De methoden kunnen er dan van uitgaan dat elk item in de list een geldige, niet-null reference is en hoeven niets meer te controleren of aan te passen.
Ik hoef helemaal geen cleanup code te schrijven, net zoals in gewoon gc java :) Ik vind mijn manier dus een stuk handiger dan allerlei destructor code te schrijven.
Smaken verschillen en het is ook een kwestie van gewenning. In C++ ben je gewend om verantwoordelijkheden duidelijk uit te werken en dan gaat het schrijven van destructors al snel vanzelf. De meeste destructors bevatten slechts een paar regels code.

Ik heb trouwens grote delen van je reactie niet gequote; voor die delen geldt dat ik het op die punten wel met je eens ben (leek me niet zo zinnig om die allemaal te quoten en 'accoord' onder te zetten).

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op vrijdag 12 juli 2002 17:14 schreef Soultaker het volgende:

Misschien drukte ik me niet zo handig uit. Ik bedoel dat alleen een applicatieprogrammeur kan inschatten wanneer een object niet meer gebruikt gaat worden (en dus verwijderd kan worden).

Wanneer een garbage collector constateert dat een object verwijderd kan worden, dan is deze correct. Dit betekend echter niet, dat wanneer een object verwijdert kan worden, de garbage collector dit weet. Hiervoor moet de programmeur expliciet references op null zetten.
Of gebruik maken van WeakReference oplossingen.
Dat komt omdat je weak reference in principe de reference count van het object niet verhoogt (en dus ook niet meer hoeft te verlagen). Wel moet de weak reference variabele die naar het object verwijst, bij dat object opgeslagen worden.

Je hebt de eis om expliciet de reference count te verlagen door de reference variabele op null te zetten, omzeilt door de reference count ueberhaupt niet te verhogen, met als gevolg dat je niet meer zeker of je een geldige reference hebt (totdat je dat controleert).
Ik vind het niet zo`n probleem om binnen een afgesloten object een extra check te gaan doen. Als je weet waar ik gewoonlijk op loop te checken dan is dit peanuts ;) Buiten dit object komt niemand met die weakReferences (en dus extra checks) in aanraking, en vind ik dit een hele schone oplossing.
Ik vind dat geen erg mooie oplossing, maar het werkt inderdaad wel aardig. Het blijft jammer dat references die intern opgeslagen zijn, toch nog gewijzigd kunnen worden.
Hoe bedoel je dat ze gewijzigd kunnen worden? Je bedoelt dat de gc die referentie op null kan zetten? Dat is toch ook de bedoeling. Als je die referentie niet op null zou zetten, zou je een verwijzing hebben naar een object dat al is gerecycled. (Ik denk dat de vm heel hard gaat gillen :+ ). Verder zal niemand die referentie in die weakReference aanpassen. Dus ik snap niet waarom jij dit veranderen slecht vind.
Het probleem daarmee, is dat in principe elke operatie die iets met de lijst doet (en dus null-references kan tegenkomen) die null-references ook moet opruimen. In dit geval gebruik je daar itt.remove() voor, wat wel werkt, maar niet zo mooi is omdat dezelfde code in elke operatie die met de interne lijst werkt terugkomt.
In principe zul je alleen in het 'controller' object die weakReferences zien. Geen enkel object kan bij die WeakReferences komen, zelfs niet een classe die extends van Controller. En omdat het dus allemaal opgesloten blijft binnen het controller object, vind dit geen argument om deze aanpak niet te gebruiken. De code blijft zelfs binnen het controller object zelfs nog erg eenvoudig.

voorbeeld controller.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class FieldController<T> extends Controller<FieldModel<T>,FieldChangeListener<T>,FieldVetoListener<T>>{
    
    public FieldController(FieldModel<T> model){
        super(model);
    }
    
    public void validate(FieldChangeEvent<T> e){
        if(e == null){
            throw new NullPointerException("e can`t be null");
        }
        
        Iterator<FieldVetoListener<T>> itt = getVetoListeners().iterator();
        while(itt.hasNext()){
            itt.next().validate(e);
        }
    }
    
    public void changed(FieldChangeEvent<T> e){
        if(e == null){
            throw new NullPointerException("e can`t be null");
        }
        
        Iterator<FieldChangeListener<T>> itt = getChangeListeners().iterator();
        while(itt.hasNext()){
            itt.next().changed(e);
        }
    }
}

Je ziet dat hier totaal geen extra checks meer zichtbaar zijn.
Het voordeel van eisen dat een object zichzelf deregistreert (is dat een woord?) is dat de code om een object te verwijderen op één plek gelocaliseerd wordt. De methoden kunnen er dan van uitgaan dat elk item in de list een geldige, niet-null reference is en hoeven niets meer te controleren of aan te passen.
Het probleem aan jouw aanpak is dat je overal een soort destructor methodes moet implementeren, en dat je die ook overal moet aanroepen om ook echt die objecten te verwijderen. Ten 1e is dit een hoop werk extra (zelfs soms vrij onmogelijk omdat je niet goed de destructor kan aanroepen), en ten 2e kan het op deze manier veel makkelijker en daarom ook veiliger omdat een eind gebruiker geen code kan vergeten aan te roepen (er is namelijk geen destructor code). Het enigste waar ze om moeten denken is dus een StrongReference van hun listeners bij te houden.
Smaken verschillen en het is ook een kwestie van gewenning. In C++ ben je gewend om verantwoordelijkheden duidelijk uit te werken en dan gaat het schrijven van destructors al snel vanzelf. De meeste destructors bevatten slechts een paar regels code.
Ik neem aan dat je ook overal een destructor hebt geimplementeerd. Een scherm zal al zijn panels aanroepen met destruct, de panels zijn componenten en dus de labels. Helaas zit zo`n destructor methode helaas niet in java (finalize mag je niet voor dit doel gebruiken, deze methode mag alleen worden aangeroepen vanuit de gc). Je zou wel een interface kunnen maken die ieder component van je scherm moet implementeren om alles weer vrij te geven. Maar dit is een hoop onnodig werk. Mijn oplossing is de oplossing waar het minste fout kan gaan, en die het minste werk verricht en zo weinig mogelijk invloed heeft op de code van het systeem. Daarom preferees ik ook mijn oplossing boven die 'destructor' aanpak.

Ik denk ook dat het verschil van mening is ontstaan, dat jij een non gc programmeur bent en ik een gc programmeur. Als de rollen waren omgedraaid dan zat de kans erin dat ik ook op jouw manier hier tegen aan had gekeken.

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 15:34
Op vrijdag 12 juli 2002 17:44 schreef Alarmnummer het volgende:
Of gebruik maken van WeakReference oplossingen.
Maar dan kan ik dus weer zeggen dat zo'n weak reference het reference counting mechanisme ondermijnt. ;)
Het probleem aan jouw aanpak is dat je overal een soort destructor methodes moet implementeren, en dat je die ook overal moet aanroepen om ook echt die objecten te verwijderen. Ten 1e is dit een hoop werk extra (zelfs soms vrij onmogelijk omdat je niet goed de destructor kan aanroepen), en ten 2e kan het op deze manier veel makkelijker en daarom ook veiliger omdat een eind gebruiker geen code kan vergeten aan te roepen (er is namelijk geen destructor code).
Ik ben dan ook groot voorstander van stack-gealloceerde objecten en auto-pointers, die voorkomen dat je destructors hoeft aan te roepen.

Auto-pointers worden op dit moment nog niet overal gebruikt in C++, omdat vroeger dat soort features niet al te portable waren (C++ was vroeger nogal een zootje; dat valt tegenwoordig erg mee).
Mijn oplossing is de oplossing waar het minste fout kan gaan, en die het minste werk verricht en zo weinig mogelijk invloed heeft op de code van het systeem. Daarom preferees ik ook mijn oplossing boven die 'destructor' aanpak.
Voor Java is het waarschijnlijk een geschikte aanpak. In C++ zou ik zeker voor de variant met deregistratie-methode (en optioneel een template class daarvoor) kiezen, al is het alleen al omdat er niet 'standaard' zoiets is als een garbage collector en references niet automagisch op null gezet kunnen worden.
Ik denk ook dat het verschil van mening is ontstaan, dat jij een non gc programmeur bent en ik een gc programmeur. Als de rollen waren omgedraaid dan zat de kans erin dat ik ook op jouw manier hier tegen aan had gekeken.
Hmmmja, dat zou best kunnen. Ik heb zelf het idee dat garbage collection eigenlijk vooral uitstekend geschikt is voor 'simpele' toepassingen; in PHP of Perl stoor ik me er nooit aan. Als ik Java programmeer, erger ik me altijd aan het ontbreken van de destructiefase in de lifecycle van objecten (er wordt namelijk niets gegarandeerd over het al dan niet tijdig en in volgorde aanroepen van finalize-methoden). Ik heb het idee dat een destructor een volwaardige feature is van een rijke object-georiënteerde taal.

Het is in Java bijvoorbeeld niet mogelijk om in een finalize methode gebruik te maken van references die in member variables opgeslagen zijn, aangezien het best zou kunnen dat die references wel eens eerder vrijgegeven zouden kunnen zijn. Destructor-taken kunnen in Java dus niet op een standaard-manier plaatsvinden (daarvoor moet een expliciete close/release methode worden opgenomen in het object).

Maar inderdaad; als ik meer Java en minder C++ geprogrammeerd zou hebben, zou ik misschien van mening zijn dat door een andere manier te ontwerpen destructors overbodig zouden zijn.

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Soultaker: Ik heb zelf het idee dat garbage collection eigenlijk vooral uitstekend geschikt is voor 'simpele' toepassingen
Hum....Hier past maar 1 smilie: :X ;)

Hiermee geef je een groep toepassingen het stempel 'niet-simpel' (en andere 'simpel') waar ik toch liever andere termen zou willen gebruiken.

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op vrijdag 12 juli 2002 19:34 schreef Soultaker het volgende:

[..]

Maar dan kan ik dus weer zeggen dat zo'n weak reference het reference counting mechanisme ondermijnt. ;)
Ik zie het als een feature ;)
Ik ben dan ook groot voorstander van stack-gealloceerde objecten en auto-pointers, die voorkomen dat je destructors hoeft aan te roepen.

Auto-pointers worden op dit moment nog niet overal gebruikt in C++, omdat vroeger dat soort features niet al te portable waren (C++ was vroeger nogal een zootje; dat valt tegenwoordig erg mee).
Ik weet zelf niets van autopointers af, dus ik kan hier niets over zeggen.
Hmmmja, dat zou best kunnen. Ik heb zelf het idee dat garbage collection eigenlijk vooral uitstekend geschikt is voor 'simpele' toepassingen; in PHP of Perl stoor ik me er nooit aan. Als ik Java programmeer, erger ik me altijd aan het ontbreken van de destructiefase in de lifecycle van objecten (er wordt namelijk niets gegarandeerd over het al dan niet tijdig en in volgorde aanroepen van finalize-methoden). Ik heb het idee dat een destructor een volwaardige feature is van een rijke object-georiënteerde taal.
Ik vind java uitermate geschikt om allerlei applicaties in te maken. Ok, op de desktop hebben ze nog geen sterke plek veroverd, maar misschien dat daar met SWT verandering in kan komen. Ik heb persoonlijk geen problemen om java voor allerlei soorten applicaties te gebruiken (alleen geen machine specifiek spul ed).
Maar inderdaad; als ik meer Java en minder C++ geprogrammeerd zou hebben, zou ik misschien van mening zijn dat door een andere manier te ontwerpen destructors overbodig zouden zijn.
precies :)

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 15:34
Op vrijdag 12 juli 2002 19:52 schreef Alarmnummer het volgende:
Ik weet zelf niets van autopointers af, dus ik kan hier niets over zeggen.
't Komt er op neer dat 't een pointer is, waarvan het doel wordt gedelete als 'ie out of scope gaat (en niet gereturned wordt). Het equivalent van de Java reference variabele dus (zonder verdere reference counting oid).
Ik vind java uitermate geschikt om allerlei applicaties in te maken. Ok, op de desktop hebben ze nog geen sterke plek veroverd, maar misschien dat daar met SWT verandering in kan komen. Ik heb persoonlijk geen problemen om java voor allerlei soorten applicaties te gebruiken (alleen geen machine specifiek spul ed).
Dat bedoel ik ook niet; meer dat de garbage collector het leven niet eenvoudiger maakt als de applicaties complexer worden, terwijl dat wel de bedoeling was. Het feit alleen al dat je in je code moet nadenken of je strong of weak references wilt gebruiken (en omgekeerd, als 'gebruiker' moet bedenken of je zelf nog strong references moet aanhouden) laat wel blijken dat je in een garbage collected language nog net zo goed moet nadenken over management van je objecten, terwijl je wel een stukje flexibiliteit verliest.

Compleet (maar dan ook compleet!) off-topic:

Ik zag je in een draadje op Javahova.net beweren dat in Clean semicolons/accolades ontbreken. Dit is zeker niet waar![quote]
CLEAN programs can be written in two modes: layout sensitive mode 'on' and 'off'. The layout sensitive mode is switched off when a semi-colon is specified after the module name. In that case each definition has to be ended with a semicolon ';'. A new scope has to begin with '{' and ends with a '}'. This mode is handy if CLEAN code is generated automatically (e.g. by a compiler).

Example of a CLEAN program not using the layout rule.
[code]module primes;

import StdEnv;

primes:: [Int];
primes = sieve [2..];

where
{ sieve:: [Int] -> [Int];
sieve [pr:r] = [pr:sieve (filter pr r)];

filter:: Int [Int] -> [Int];
filter pr [n:r] | n mod pr == 0 = filter pr r;
| otherwise = [n:filter pr r];
}[/code]
Programs look a little bit old fashion C-like in this way. Functional programmers generally prefer a more mathematical style. Hence, as is common in modern functional languages, there is a layout rule in CLEAN. When a semicolon does not end the header of a module, a CLEAN program has become layout sensitive.
[/quote]

Dat moest ik even kwijt en aangezien dit toch ons privé-draadje aan 't worden was... ;)

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Ik weet dat het optioneel is hoor :), maar ik zou het verplicht stellen. Zal misschien wel komen omdat ik nooit talen heb meegemaakt waarin de layout van de code ook een bepaalde semantiek heeft.

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Dat bedoel ik ook niet; meer dat de garbage collector het leven niet eenvoudiger maakt als de applicaties complexer worden, terwijl dat wel de bedoeling was. Het feit alleen al dat je in je code moet nadenken of je strong of weak references wilt gebruiken (en omgekeerd, als 'gebruiker' moet bedenken of je zelf nog strong references moet aanhouden) laat wel blijken dat je in een garbage collected language nog net zo goed moet nadenken over management van je objecten, terwijl je wel een stukje flexibiliteit verliest.
Je moet nu inderdaad zelf wat nadenken. Maar dat vind ik ook niet zo`n probleem hoor. Het is voorlopig eerst nog even een experiment.Ik ben nu bezig om een heel framework te maken die het collection framework uitbreid met events.

Ik weet op dit moment niet in hoeverre het bruikbaar gaat worden ivm complexiteit, transacties, snelheid etc. Maar ik had zin om met generics te gaan pielen, en daar leek me dit wel een geschikt begin voor. En het is heel leuk om een bepaald design pattern (mvc) veel verder door te voeren. Ik zie wel waar het schip strand :)

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 15:34
Op vrijdag 12 juli 2002 20:15 schreef Alarmnummer het volgende:
Ik weet dat het optioneel is hoor :), maar ik zou het verplicht stellen. Zal misschien wel komen omdat ik nooit talen heb meegemaakt waarin de layout van de code ook een bepaalde semantiek heeft.
Eigenlijk zou ik (behalve functionele programmeertalen) alleen BASIC kunnen noemen (newlines hebben betekenis). :)

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Ik geloof dat vb ook geen semicolons heeft, en ook werk met identatie. Maar dit weet ik verder niet zeker (heb (gelukkig) nog nooit iets met vb hoven te doen))

  • Stephan Oudmaijer
  • Registratie: Oktober 2000
  • Laatst online: 16-08-2023
MVC?

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Op zaterdag 13 juli 2002 00:09 schreef CK het volgende:
MVC?
Model View Controller. Is een design pattern waarmee je de gui loskoppeld van je data en communiceerd mbv events. Er zijn genoeg tutorials over te vinden :)

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Er staat op javalobby een link naar een event tutorial waar ze helemaal beginnen bij de basics en verder ook netwerk events behandelen ed.

http://www.javalobby.org/thread.jsp?forum=17&thread=4512

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 00:13
Op vrijdag 12 juli 2002 20:28 schreef Soultaker het volgende:

[..]

Eigenlijk zou ik (behalve functionele programmeertalen) alleen BASIC kunnen noemen (newlines hebben betekenis). :)
Meende dat Python ook zoiets had ...

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.

Pagina: 1