[Java] "Delegates"? Methodes als parameters doorgeven...

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 21:05
Hey,

Ik zit aan een Android app te werken, en ben bezig met een schermpje dat een aantal verschillende zaken over het internet moet ophalen en uiteindelijk aan de gebruiker moet tonen. De app roept via een http post een webservice aan die wat json terug stuurt. Dit moet natuurlijk in een achtergrond thread gebeuren om de UI niet te laten vastlopen.

Nu weet ik dat Android een aantal handige classes heeft om dit te doen, met name de AsyncTask class. Ik weet hoe ik die gebruik, er zijn alleen een aantal 'problemen':
  • Voor elke operatie moet ik een nieuwe class maken die AsyncTask extend
  • Ik moet elke keer weer de Activity mee geven aan die class zodat ik toegang heb tot bijvoorbeeld UI views (labels etc) of een ProcessDialog kan sluiten
  • Het is een hele sloot code die (volgens mij) niet nodig is
Het hele proces zou (voor mij) een stuk eenvoudiger zijn als ik een class kon maken waaraan ik twee 'methodes' kon doorgeven, die dan op een later tijdstip aangeroepen worden. Ik zit te denken aan iets als dit:
Java:
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
public class GetDataTask<E> extends AsyncTask<Integer, Void, E>
{
    private ???<Integer, E> getDataMethod;
    private ???<E, Void> finishedMethod;
    
    public GetDataTask(??? method1, ??? method2)
    {
        getDataMethod = method1;
        finishedMethod = method2;
    }
    
    @Override
    public E doInBackground(Integer... args)
    {
            // Deze methode draait in een achtergrond thread
        
                // Hier wil ik de methode aanroepen die de data ophaalt, deze methode kan dus ELKE methode zijn
                // zolang hij maar een int als argument heeft en een 'E' terug geeft
                int id = args[0];
                E result = getDataMethod.invoke(id);
                return result;
    }
    
    @Override
    public void onPostExecute(E value)
    {
                // Deze methode wordt aangeroepen zodra de achtergrond thread klaar is
        
                // hier wil ik dus de methode aanroepen die de UI kan updaten 
        finishedMethod.invoke(value);
    }
}


Ik zou deze class dan op deze manier kunnen gebruiken:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private ProcessDialog dialog;

public void onSomeButtonClicked()
{
        dialog = ProcessDialog.show("Retrieving user...");
     
    // Get user details:
        int userId = 34;
    GetDataTask<User> task = new GetDataTask<User>(loadUser, loadUserFinished);
    task.execute(userId);
}

public User loadUser(int id)
{
        // Laad User details over het internet, duurt een tijdje
    return Networking.getUserFromNetwork(id);
}

public void loadUserFinished(User user)
{
        // Klaar met user details ophalen, update de UI
        txtUserName.setText(user.username);
        dialog.dismiss();
}


Dat is een stuk minder code en een stuk makkelijker schrijven dan weer een nieuwe AsyncTask te moeten implementeren (even aangenomen dat ik een dergelijke operatie op heel veel plekken vaker moet doen, wat uiteraard wel zo is).


Ik heb echter geen idee hoe ik dit moet doen. Ik zit teveel in .NET termen te denken, waar de ??? fields dingen als Actions en Funcs kunnen zijn. Voor zover ik weet bestaat dit niet in Java.

De oplossing die ik steeds tegen kom is om de Activity (waar ik deze code dus in draai) een interface te laten implementeren (zeg IDataLoader) die een methode 'getData' en 'getDataFinished' voorschrijft. De GetDataTask class kan dan (in plaats van twee methodes) een IDataLoader referentie accepteren in z'n constructor, en daarop die twee methodes aanroepen.

Dit werkt in principe wel, maar er is een probleem waardoor ik dit niet kan gebruiken. Dit interface zal namelijk generic moeten zijn, een input en een output type parameter (in dit geval Integer als input, User als output). Stel nou dat ik in dezelfde view ook nog een lijst met berichtjes (Message) moet ophalen, dan heb ik dus nog een GetDataTask instantie nodig, deze keer met Message als parameter. Dat betekent dat mijn Activity dus IDataLoader<Integer, User> en ook IDataLoader<Integer, ArrayList<Message>> moet implementeren. En dat kan blijkbaar niet, Java gaat zeuren dat ik niet twee keer hetzelfde interface met verschillende type parameters mag implementeren.

Dus dit gaat zo niet werken...


Is er een andere manier om dit te doen? Bedankt!

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Cobalt
  • Registratie: Januari 2004
  • Laatst online: 08-10 18:51
De term waar je op moet zoeken is 'Reflection' dat staat voor hetzelfde in .Net en Java

Op de ??? moet volgens mij java.lang.reflect.Method komen te staan. De methods kan je simpel verkrijgen via getClass().getDeclaredMethods() of getClass().getMethod(...)

Al zou je reflectie qua security misschien willen vermijden. Kan je niet het object waar de twee methoden in zitten megeven aan GetDataTask?

[ Voor 47% gewijzigd door Cobalt op 06-11-2011 22:52 ]


Acties:
  • 0 Henk 'm!

  • RayNbow
  • Registratie: Maart 2003
  • Laatst online: 21:22

RayNbow

Kirika <3

NickThissen schreef op zondag 06 november 2011 @ 22:19:
Ik zou deze class dan op deze manier kunnen gebruiken:
Java:
8
9
10
/*snip*/
    GetDataTask<User> task = new GetDataTask<User>(loadUser, loadUserFinished);
/*snip*/
Dit werkt trouwens in Java helaas niet omdat methoden in een eigen namespace leven.
Dit werkt in principe wel, maar er is een probleem waardoor ik dit niet kan gebruiken. Dit interface zal namelijk generic moeten zijn, een input en een output type parameter (in dit geval Integer als input, User als output). Stel nou dat ik in dezelfde view ook nog een lijst met berichtjes (Message) moet ophalen, dan heb ik dus nog een GetDataTask instantie nodig, deze keer met Message als parameter. Dat betekent dat mijn Activity dus IDataLoader<Integer, User> en ook IDataLoader<Integer, ArrayList<Message>> moet implementeren. En dat kan blijkbaar niet, Java gaat zeuren dat ik niet twee keer hetzelfde interface met verschillende type parameters mag implementeren.

Dus dit gaat zo niet werken...
Waarom moet de Activity per se dan IDataLoader implementeren? Je kunt ook iets als het volgende doen:
 
Java:
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 interface DataLoader<T,R> {
    public R getData(T in);
    public void onFinished(R out);
}

public abstract class Example {
    abstract public User loadUser(int id); 
    abstract public void loadUserFinished(User user);
    
    abstract public List<Message> loadMessages(int id);
    abstract public void loadMessagesFinished(List<Message> messages);
    
    DataLoader<Integer, User> loadUserTask = new DataLoader<Integer, User>() {
        @Override
        public User getData(Integer in) {
            return loadUser(in.intValue());
        }
        @Override
        public void onFinished(User out) {
            loadUserFinished(out);
        }
    };
    
    DataLoader<Integer, List<Message>> loadMessagesTask = new DataLoader<Integer, List<Message>>() {
        @Override
        public List<Message> getData(Integer in) {
            return loadMessages(in.intValue());
        }
        @Override
        public void onFinished(List<Message> out) {
            loadMessagesFinished(out);
        }
    };
}


Dit verdient niet de schoonheidsprijs vanwege de boilerplate, maar het is denk ik nog wel te doen.

(Het alternatief is wachten op support voor lambda's in Java? :p)

[ Voor 1% gewijzigd door RayNbow op 07-11-2011 08:24 . Reden: code example fix ]

Ipsa Scientia Potestas Est
NNID: ShinNoNoir


Acties:
  • 0 Henk 'm!

  • CoolGamer
  • Registratie: Mei 2005
  • Laatst online: 12-10 15:22

CoolGamer

What is it? Dragons?

Java kent het principe van delegates niet. In Java worden dit soort problemen gewoonlijk opgelost door gebruik te maken van een interface die de callback functies beschrijft. Deze interface zou je dan, zoals RayNbow laat zien, kunnen implementeren door gebruik te maken van een inline class.

Het alternatief van reflectie is niet echt handig, aangezien je dan een reference naar het object nodig hebt en een reference naar de method.

¸.·´¯`·.¸.·´¯`·.¸><(((º>¸.·´¯`·.¸><(((º>¸.·´¯`·.¸.·´¯`·.¸.·´¯`·.¸<º)))><¸.·´¯`·.¸.·´¯`·.¸.·´¯`·.¸


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 21:05
RayNbow schreef op maandag 07 november 2011 @ 07:30:
[...]

Dit werkt trouwens in Java helaas niet omdat methoden in een eigen namespace leven.


[...]

Waarom moet de Activity per se dan IDataLoader implementeren? Je kunt ook iets als het volgende doen:
 
Java:
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 interface DataLoader<T,R> {
    public R getData(T in);
    public void onFinished(R out);
}

public abstract class Example {
    abstract public User loadUser(int id); 
    abstract public void loadUserFinished(User user);
    
    abstract public List<Message> loadMessages(int id);
    abstract public void loadMessagesFinished(List<Message> messages);
    
    DataLoader<Integer, User> loadUserTask = new DataLoader<Integer, User>() {
        @Override
        public User getData(Integer in) {
            return loadUser(in.intValue());
        }
        @Override
        public void onFinished(User out) {
            System.out.println(out);
        }
    };
    
    DataLoader<Integer, List<Message>> loadMessagesTask = new DataLoader<Integer, List<Message>>() {
        @Override
        public List<Message> getData(Integer in) {
            return loadMessages(in.intValue());
        }
        @Override
        public void onFinished(List<Message> out) {
            System.out.println(out);
        }
    };
}


Dit verdient niet de schoonheidsprijs vanwege de boilerplate, maar het is denk ik nog wel te doen.

(Het alternatief is wachten op support voor lambda's in Java? :p)
Ah, inline classes dus :p ik zie ze wel eens voorbij komen maar ik wist niet hoe ik ze zelf moest gebruiken en of dat hier wel van toepassing was. Qua boilerplate valt het ook erg mee vind ik, wat is er boilerplate dan? Die twee methods zou ik sowieso moeten aanmaken, of ik dat nou in mijn activity doe of op deze manier maakt verder niet uit.

Wel nog een vraagje, ik kan op het moment niks proberen, maar heb ik in die onFinished methode in zo'n online class toegang tot mijn activity, bijv de private fields, of moet ik dan alsnog de activity mee geven? (de activity is de class waarin ik deze inline classes declareer, jou Example dus eigenlijk)

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • RayNbow
  • Registratie: Maart 2003
  • Laatst online: 21:22

RayNbow

Kirika <3

NickThissen schreef op maandag 07 november 2011 @ 08:12:
[...]
Qua boilerplate valt het ook erg mee vind ik, wat is er boilerplate dan? Die twee methods zou ik sowieso moeten aanmaken, of ik dat nou in mijn activity doe of op deze manier maakt verder niet uit.
Regels 13-22 en regels 24-33 zou je elk eigenlijk het liefst willen reduceren naar 1 regel.
Wel nog een vraagje, ik kan op het moment niks proberen, maar heb ik in die onFinished methode in zo'n online class toegang tot mijn activity, bijv de private fields, of moet ik dan alsnog de activity mee geven? (de activity is de class waarin ik deze inline classes declareer, jou Example dus eigenlijk)
Ja, de inner classes in m'n codevoorbeeld hebben toegang tot de private fields van Example.

Ipsa Scientia Potestas Est
NNID: ShinNoNoir


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 21:05
Bedankt, het lijkt prima te werken zo, een stuk handiger dan elke keer een nieuwe class implementeren en slechts een paar regels code meer dan wat ik als 'ideaal' beschouwde (direct doorgeven van methods). Ik heb het nog niet getest maar denk dat het wel gaat werken :)

Mijn iRacing profiel

Pagina: 1