Ответ 1
Да, концепция обратных вызовов также существует на Java. В Java вы определяете обратный вызов следующим образом:
public interface TaskListener {
public void onFinished(String result);
}
Внутри AsyncTask
часто бывают такие определения слушателей:
public class ExampleTask extends AsyncTask<Void, Void, String> {
public interface TaskListener {
public void onFinished(String result);
}
...
}
И полная реализация обратного вызова в AsyncTask
будет выглядеть так:
public class ExampleTask extends AsyncTask<Void, Void, String> {
public interface TaskListener {
public void onFinished(String result);
}
// This is the reference to the associated listener
private final TaskListener taskListener;
public ExampleTask(TaskListener listener) {
// The listener reference is passed in through the constructor
this.taskListener = listener;
}
@Override
protected String doInBackground(Void... params) {
return doSomething();
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
// In onPostExecute we check if the listener is valid
if(this.taskListener != null) {
// And if it is we call the callback function on it.
this.taskListener.onFinished(result);
}
}
}
onPostExecute()
вызывается, как только заканчивается фоновое задание. Вы можете использовать все это так:
ExampleTask task = new ExampleTask(new ExampleTask.TaskListener() {
@Override
public void onFinished(String result) {
// Do Something after the task has finished
}
});
task.execute();
Или вы можете полностью определить TaskListener
следующим образом:
ExampleTask.TaskListener listener = new ExampleTask.TaskListener() {
@Override
public void onFinished(String result) {
// Do Something after the task has finished
}
};
ExampleTask task = new ExampleTask(listener);
task.execute();
Или вы можете подклассом TaskListener
следующим образом:
public class ExampleTaskListener implements TaskListener {
@Override
public void onFinished(String result) {
}
}
И затем используйте его следующим образом:
ExampleTask task = new ExampleTask(new ExampleTaskListener());
task.execute();
Конечно, вы можете просто переопределить метод onPostExecute()
AsyncTask
, но это не рекомендуется и в большинстве случаев на самом деле довольно плохая практика. Например, вы можете сделать это:
ExampleTask task = new ExampleTask() {
@Override
public void onPostExecute(String result) {
super.onPostExecute(result);
// Your code goes here
}
};
Это будет работать так же хорошо, как и реализация выше с помощью отдельного интерфейса прослушивателя, но есть несколько проблем с этим:
Прежде всего вы можете сломать ExampleTask
все вместе. Все сводится к вызову super.onPostExecute()
выше. Если вы как разработчик переопределите onPostExecute()
, как указано выше, и забудьте включить супервызов или просто удалить его по любой причине, что оригинальный метод onPostExecute()
в ExampleTask
больше не будет вызываться. Например, реализация всего прослушивателя с помощью TaskListener
внезапно перестанет работать, поскольку вызов обратного вызова реализован в onPostExecute()
. Вы также можете разбить TaskListener
многими другими способами, неосознанно или невольно влияя на состояние ExampleTask
, чтобы он больше не работал.
Если вы посмотрите на то, что на самом деле происходит, когда вы переопределяете метод, подобный этому, становится намного понятнее, что происходит. Переопределяя onPostExecute()
, вы создаете новый подкласс ExampleTask
. Это было бы точно так же, как это делать:
public class AnotherExampleTask extends ExampleTask {
@Override
public void onPostExecute(String result) {
super.onPostExecute(result);
// Your code goes here
}
}
Все это просто скрывается за языковой функцией, называемой анонимными классами. Внезапно переопределение метода, подобного этому, кажется не таким чистым и быстрым, не делает этого?
Подводя итог:
- Переопределение такого метода фактически создает новый подкласс. Вы не просто добавляете обратный вызов, вы изменяете, как работает этот класс, и можете бессознательно сломать так много вещей.
- Отладка таких ошибок может быть намного больше, чем просто боль в **. Потому что внезапно
ExampleTask
может бросатьExceptions
или просто не работать больше без видимых причин, потому что вы никогда не изменяли его код. - Каждый класс должен обеспечивать реализацию прослушивателя в тех местах, где он подходит и предназначен. Конечно, вы можете просто добавить их позже, переопределив
onPostExecute()
, но это всегда очень опасно. Даже @flup с его 13-кратной репутацией забыл включить вызовsuper.onPostExecute()
в свой ответ, представьте, что может сделать другой, как опытный разработчик! - Маленькая абстракция никогда никому не повредит. Написание конкретных слушателей может быть немного больше кода, но это гораздо лучшее решение. Код будет более чистым, более читаемым и намного более удобным. Использование ярлыков, таких как переопределение
onPostExecute()
, по существу приносит в жертву качество кода для небольшого удобства. Это никогда не будет хорошей идеей, а просто вызовет проблемы в конечном итоге.